本来按着前面是想用nodejs来做游戏服务器的,但是后面查资料,看着看着,好像发现一个新东西.net core。似乎nodejs对帧同步这种要求服务器运算量较大的,不算很好(但是可以nodejs + c++),而且既然.net core是最新的技术,那就与时俱进的研究看看。(目前对服务器不太了解,对.net core也不了解,自己找的资料也不多,有错误请大神指点一二),本文还是从与unity的socket通信入手,效果图如下:
.net core 官网:https://dotnet.github.io/
socket连接资料:https://blog.csdn.net/linshuhe1/article/details/51386559
首先,我们根据官网的下载并安装好.net core(官网说的很详细,这里就不累赘了)。然后我们打开vs新建一个.net core控制台应用,用来做我们的服务器(vs要事先安装好.net core相关库)
在网络通讯中,数据在网络传输的格式必须以字节流的形式进行,因此需要我们对字节流进行写入和读出的操作,下面将会封装两个类,用来将各种类型的数据写入字节流,和从字节流中读取各种类型的数据。如下
读取类:NetBufferReader
using System;
using System.IO;
using System.Text;
namespace Tool {
class NetBufferReader {
MemoryStream m_stream = null;
BinaryReader m_reader = null;
ushort m_dataLength;
public NetBufferReader(byte[] data) {
if(data != null) {
m_stream = new MemoryStream(data);
m_reader = new BinaryReader(m_stream);
m_dataLength = ReadUShort();
}
}
public byte ReadByte() {
return m_reader.ReadByte();
}
public int ReadInt() {
return m_reader.ReadInt32();
}
public uint ReadUInt() {
return m_reader.ReadUInt32();
}
public short ReadShort() {
return m_reader.ReadInt16();
}
public ushort ReadUShort() {
return m_reader.ReadUInt16();
}
public long ReadLong() {
return m_reader.ReadInt64();
}
public ulong ReadULong() {
return m_reader.ReadUInt64();
}
public float ReadFloat() {
byte[] temp = BitConverter.GetBytes(m_reader.ReadSingle());
Array.Reverse(temp);
return BitConverter.ToSingle(temp, 0);
}
public double ReadDouble() {
byte[] temp = BitConverter.GetBytes(m_reader.ReadDouble());
Array.Reverse(temp);
return BitConverter.ToDouble(temp, 0);
}
public string ReadString() {
ushort len = ReadUShort();
byte[] buffer = new byte[len];
buffer = m_reader.ReadBytes(len);
return Encoding.UTF8.GetString(buffer);
}
public byte[] ReadBytes() {
int len = ReadInt();
return m_reader.ReadBytes(len);
}
public void Close() {
if(m_reader != null) {
m_reader.Close();
}
if(m_stream != null) {
m_stream.Close();
}
m_reader = null;
m_stream = null;
}
}
}
写入类:NetBufferWriter
using System;
using System.IO;
using System.Text;
namespace Tool {
class NetBufferWriter {
MemoryStream m_stream = null;
BinaryWriter m_writer = null;
int m_finishLength;
public int finishLength {
get { return m_finishLength; }
}
public NetBufferWriter() {
m_finishLength = 0;
m_stream = new MemoryStream();
m_writer = new BinaryWriter(m_stream);
}
public void WriteByte(byte v) {
m_writer.Write(v);
}
public void WriteInt(int v) {
m_writer.Write(v);
}
public void WriteUInt(uint v) {
m_writer.Write(v);
}
public void WriteShort(short v) {
m_writer.Write(v);
}
public void WriteUShort(ushort v) {
m_writer.Write(v);
}
public void WriteLong(long v) {
m_writer.Write(v);
}
public void WriteULong(ulong v) {
m_writer.Write(v);
}
public void WriteFloat(float v) {
byte[] temp = BitConverter.GetBytes(v);
Array.Reverse(temp);
m_writer.Write(BitConverter.ToSingle(temp, 0));
}
public void WriteDouble(double v) {
byte[] temp = BitConverter.GetBytes(v);
Array.Reverse(temp);
m_writer.Write(BitConverter.ToDouble(temp, 0));
}
public void WriteString(string v) {
byte[] bytes = Encoding.UTF8.GetBytes(v);
m_writer.Write((ushort)bytes.Length);
m_writer.Write(bytes);
}
public void WriteBytes(byte[] v) {
m_writer.Write(v.Length);
m_writer.Write(v);
}
public byte[] ToBytes() {
m_writer.Flush();
return m_stream.ToArray();
}
public void Close() {
m_writer.Close();
m_stream.Close();
m_writer = null;
m_stream = null;
}
///
/// 将已写入的数据流,封装成一个新的数据流(现有数据长度+现有数据)
/// 数据转换,网络发送需要两部分数据,一是数据长度,二是主体数据
///
public byte[] Finish() {
byte[] message = ToBytes();
MemoryStream ms = new MemoryStream();
ms.Position = 0;
BinaryWriter writer = new BinaryWriter(ms);
writer.Write((ushort)message.Length);
writer.Write(message);
writer.Flush();
byte[] result = ms.ToArray();
m_finishLength = result.Length;
return result;
}
}
}
创建好上面的类后,我们继续在入口类Program中创建Socket服务器的逻辑,大致的逻辑如下,首先启动一个服务器socket,然后创建一个线程,用于监听客户端的连接,每当有一个客户端连接的时候,创建一个socket对象存放在List中,并启动一个新的线程,用来发送和接收数据。代码如下:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Tool;
namespace MyApp {
class Program {
static byte[] m_result = new byte[1024];//存放接收的数据
const int m_port = 8078;//端口号
static string m_localIp = "127.0.0.1";
static Socket m_serverSocket;//服务器socket
static List m_clientSocketList = new List();//存放连接上的的客户端服务器
static void Main(string[] args) {
IPAddress ipAddress = IPAddress.Parse(m_localIp);
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, m_port);
//创建服务器Socket对象,并设置相关属性
m_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定ip和端口
m_serverSocket.Bind(ipEndPoint);
//设置最长的连接请求队列长度
m_serverSocket.Listen(10);
Console.WriteLine("启动监听{0}成功", m_serverSocket.LocalEndPoint.ToString());
//在新线程中监听客户端的连接
Thread thread = new Thread(ClientConnectListen);
thread.Start();
Console.ReadLine();
}
///
/// 客户端连接请求监听
///
static void ClientConnectListen() {
while(true) {
//为新的客户端连接创建一个Socket对象
Socket clientSocket = m_serverSocket.Accept();
m_clientSocketList.Add(clientSocket);
Console.WriteLine("客户端{0}成功连接", clientSocket.RemoteEndPoint.ToString());
//向连接的客户端发送连接成功的数据
NetBufferWriter writer = new NetBufferWriter();
writer.WriteString("Connected Server Success");
clientSocket.Send(writer.Finish());
//每个客户端连接创建一个线程来接受该客户端发送的消息
Thread thread = new Thread(RecieveMessage);
thread.Start(clientSocket);
}
}
///
/// 接收指定客户端Socket的消息
///
static void RecieveMessage(object clientSocket) {
Socket mClientSocket = (Socket)clientSocket;
while(true) {
try {
int receiveNumber = mClientSocket.Receive(m_result);
Console.WriteLine("接收客户端{0}消息, 长度为{1}", mClientSocket.RemoteEndPoint.ToString(), receiveNumber);
if(receiveNumber == 0) {
//断开连接
Console.WriteLine("连接已断开{0}", mClientSocket.RemoteEndPoint.ToString());
RemoveClientSocket(mClientSocket);
break;
}
NetBufferReader reader = new NetBufferReader(m_result);
string data = reader.ReadString();
Console.WriteLine("数据内容:{0}", data);
//给所有连接上的客户端返回数据
NetBufferWriter writer = new NetBufferWriter();
writer.WriteString("Get Message:" + data);
byte[] buffer = writer.Finish();
foreach(Socket socket in m_clientSocketList) {
socket.Send(buffer);
}
} catch(Exception ex) {
Console.WriteLine(ex.Message);
RemoveClientSocket(mClientSocket);
break;
}
}
}
static void RemoveClientSocket(Socket clientSocket) {
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
m_clientSocketList.Remove(clientSocket);
}
}
}
接着,我们点击vs的运行按钮,就可以启动我们的服务器了
接着,我们开始创建客户端,创建一个新的unity工程,然后将之前服务器用到的字节流的读写类NetBufferWriter和NetBufferReader也导入到工程中。然后创建一个单例类来管理socket,连接我们创建的服务器的ip和端口号,并且发送和接收消息,如下
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
namespace Tool {
public class ClientSocket : SingleClass {
public delegate void OnGetReceive(string message);//接收到消息的委托
public OnGetReceive onGetReceive;
byte[] m_result = new byte[1024];
Socket m_clientSocket;
//是否已连接的标识
public bool isConnected {
get {
return m_clientSocket.Connected;
}
}
public ClientSocket() {
m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
///
/// 连接指定IP和端口的服务器
///
///
///
public void ConnectServer(string ip, int port) {
IPAddress ipAddress = IPAddress.Parse(ip);
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);
try {
m_clientSocket.Connect(ipEndPoint);
Debug.Log("连接服务器成功");
Thread thread = new Thread(RecieveMessage);
thread.Start();
} catch {
Debug.Log("连接服务器失败");
return;
}
}
///
/// 发送数据给服务器
///
public void SendMessage(string data) {
if(isConnected == false) {
return;
}
try {
NetBufferWriter writer = new NetBufferWriter();
writer.WriteString(data);
m_clientSocket.Send(writer.Finish());
} catch {
Disconnect();
}
}
///
/// 发送数据给服务器
///
public void Disconnect() {
if(isConnected) {
m_clientSocket.Shutdown(SocketShutdown.Both);
m_clientSocket.Close();
}
}
///
/// 接收服务器端Socket的消息
///
void RecieveMessage() {
while(isConnected) {
try {
int receiveLength = m_clientSocket.Receive(m_result);
NetBufferReader reader = new NetBufferReader(m_result);
string data = reader.ReadString();
Debug.Log("服务器返回数据:" + data);
if(onGetReceive != null) {
onGetReceive(data);
}
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Disconnect();
break;
}
}
}
}
}
父类为单例工具类
namespace Tool {
public abstract class SingleClass where T : new() {
private static T m_instance;
public static T instance {
get {
if (m_instance == null) {
m_instance = new T();
}
return m_instance;
}
}
}
}
接下来,我们写UI逻辑,先添加UI控件,然后创建一个新类给这些控件添加逻辑
using Tool;
using UnityEngine;
using UnityEngine.UI;
namespace Examples {
public class TestSocket : MonoBehaviour {
[SerializeField] Button m_connectBtn;
[SerializeField] InputField m_input;
[SerializeField] Button m_sendBtn;
[SerializeField] Button m_disconnectBtn;
[SerializeField] Text m_receiveText;
string m_receiveMessage = "wait...";
void Start () {
m_connectBtn.onClick.AddListener(SocketConnect);
m_sendBtn.onClick.AddListener(SocketSendMessage);
m_disconnectBtn.onClick.AddListener(SocketDisconnect);
ClientSocket.instance.onGetReceive = ShowReceiveMessage;
}
void SocketConnect() {
ClientSocket.instance.ConnectServer("127.0.0.1", 8078);
}
void SocketSendMessage() {
string content = m_input.text;
if(!string.IsNullOrEmpty(content)) {
ClientSocket.instance.SendMessage(content);
}
}
void SocketDisconnect() {
ClientSocket.instance.Disconnect();
m_receiveMessage = "已断开连接";
}
void ShowReceiveMessage(string message) {
m_receiveMessage = message;
}
void Update() {
m_receiveText.text = m_receiveMessage;
}
}
}
绑定好脚本后即可开始运行测试了,效果图见开头。