Unity教程之-Unity教程之-Unity3d游戏与C#服务器异步Socket交互(一)

 

Unity3d中提供了Socket供开发者使用,语法和.net中的一致。

一般来说,对于手游客户端,分为两个线程,一个是GLES渲染,另一个就是Socket线程了。

不论是服务器,还是客户端。其间的数据包的接收与发送,都是通过Socket。

比如客户端要登录,我们就新建一个Socket,Connect到帐号服务器。帐号服务器一直在等待客户端的连接,客户端连接进来之后就准备发送接收数据包了。

这一篇只讲服务端的简单模型。

在.net中封装了Socket。

Socket又分为阻塞式和异步这两种。

阻塞,就是说你要往后面走,就必须过等我执行完成。

异步,是说你要往后走,看到我请绕路。

首先介绍Socket的基本用法:

1、新建Socket

2、激活Socket,置为等待客户端连接状态

3、异步Socket,一定要在一个客户端联入之后立即回到等待状态


private static int m_SocketCount = 0;

private static ManualResetEvent m_ManualResetEvent = new ManualResetEvent(false);

IPEndPoint: 是IP地址和端口的整合类型

Socket:创建Socket是根据IP地址和端口号来创建的

Protocoltype.Tcp:指定使用TCP协议。(关于TCP与UDP,简单说就是TCP确保一个数据包一定发送成功,UDP就不管。详情请百度谷歌360搜索等等)

BeginAccept:开始监听客户端的联入。注意这是一个异步函数。这个函数执行后马上就会执行后面的代码,如果后面没有循环的程序,那么程序就会退出!

所以这里用了ManualResetEvent来等待结果。

我们也可以单开线程来达到这个效果,文章后面会贴上两种方式的代码。


IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("172.16.30.167"), 1223);

Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

try
{
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(20);
Console.WriteLine("Server Start");
while (true)
{
m_ManualResetEvent.Reset(); //将线程置于非终止状态,也就是等待当前线程完成;
serverSocket.BeginAccept(new AsyncCallback(Accept), serverSocket);
m_ManualResetEvent.WaitOne(); //阻塞主线程,这里的作用是不退出程序;
}
}
catch (System.Exception ex)
{
Console.WriteLine("Exception" + ex);
}

异步Socket方式,每个操作都要求传入一个回调函数。(不然人家执行完了怎么接着往下执行……)

比如上面的BeginAccept 传入了一个回调函数 Accept,并且带了参数serverSocket 。意思就是 BeginAccept执行完毕后会调用Accept并且传入serverSocket。


public static void Accept(IAsyncResult result)
{
m_ManualResetEvent.Set(); //通知主线程继续;

Socket serverSocket = (Socket)result.AsyncState;


Console.WriteLine("Accept one Client "+(++m_SocketCount));

//已经Accept客户端之后就停止Accept;
Socket receiverSocket = serverSocket.EndAccept(result);

//开始Receive;
StateObject state = new StateObject();
state.m_CurSocket = receiverSocket;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
}

在有客户端联入之后会执行Accept。

这里注意:

一定要调用ManualResetEvent的Set方法通知主线程继续执行,也就是让Socket继续监听客户端的联入。

当客户端联入之后,就要开始客户端与服务器真正的数据交互啦。调用Socket.BeginReceive 。这也是一个异步操作,同样我们要传入一个回调函数。


public static void ReceiveCallBack(IAsyncResult result)
{
String content = String.Empty;
StateObject state = (StateObject)result.AsyncState;
Socket receiverSocket = state.m_CurSocket;
try
{
int byteRead = receiverSocket.EndReceive(result);

if (byteRead > 0)
{
//获取数据长度;
byte[] datalengtharr=new byte[4];
Buffer.BlockCopy(state.m_Buffer, 0, datalengtharr, 0, 4);
int datalength = BitConverter.ToInt32(datalengtharr, 0);
Console.WriteLine("receive data length = " + datalength.ToString());

//获取数据主体;
byte[] dataarr = new byte[byteRead-4];
Buffer.BlockCopy(state.m_Buffer, 4, dataarr, 0, byteRead - 4);
state.m_StringBuilder.Append(Encoding.ASCII.GetString(dataarr, 0, byteRead - 4));

content = state.m_StringBuilder.ToString();

//判断数据长度,是否接收完全;
if (!(byteRead - 4 < datalength))
{
Console.WriteLine("Receive " + content.Length + "  " + content);

//接收完全后发送数据给客户端;
Send(receiverSocket, "success from server");
}
else
{
//数据没有接收完,继续接收;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

}

以前没有做过网络游戏的朋友有疑问,在接收数据的时候,怎么知道到底接收完成了没有?

上面的代码中,有一个特殊的数字  4  。

这是我们用来存放数据包大小的一个变量。

数据包=数据大小(4字节)+真实数据

按照上面的结构组建数据包,在接收到数据的时候,我们只要判断接收到的数据大小-4是不是等于数据大小。

好了。下面是例子的源代码:

(一)使用ManualResetEvent协作:


using System;
using System.Net;
using System.Net.Sockets;
using System.IO ;
using System.Text;
using System.Threading;

public class Echoserver
{
private static int m_SocketCount = 0;

private static ManualResetEvent m_ManualResetEvent = new ManualResetEvent(false);


//entry point of main method.
public static void Main()
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("172.16.30.167"), 1223);

Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

try
{
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(20);
Console.WriteLine("Server Start");
while (true)
{
m_ManualResetEvent.Reset(); //将线程置于非终止状态,也就是等待当前线程完成;
serverSocket.BeginAccept(new AsyncCallback(Accept), serverSocket);
m_ManualResetEvent.WaitOne(); //阻塞主线程,这里的作用是不退出程序;
}
}
catch (System.Exception ex)
{
Console.WriteLine("Exception" + ex);
}
}


public static void Accept(IAsyncResult result)
{
m_ManualResetEvent.Set(); //通知主线程继续;

Socket serverSocket = (Socket)result.AsyncState;


Console.WriteLine("Accept one Client "+(++m_SocketCount));

//已经Accept客户端之后就停止Accept;
Socket receiverSocket = serverSocket.EndAccept(result);

//开始Receive;
StateObject state = new StateObject();
state.m_CurSocket = receiverSocket;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
}

public static void ReceiveCallBack(IAsyncResult result)
{
String content = String.Empty;
StateObject state = (StateObject)result.AsyncState;
Socket receiverSocket = state.m_CurSocket;
try
{
int byteRead = receiverSocket.EndReceive(result);

if (byteRead > 0)
{
//获取数据长度;
byte[] datalengtharr=new byte[4];
Buffer.BlockCopy(state.m_Buffer, 0, datalengtharr, 0, 4);
int datalength = BitConverter.ToInt32(datalengtharr, 0);
Console.WriteLine("receive data length = " + datalength.ToString());

//获取数据主体;
byte[] dataarr = new byte[byteRead-4];
Buffer.BlockCopy(state.m_Buffer, 4, dataarr, 0, byteRead - 4);
state.m_StringBuilder.Append(Encoding.ASCII.GetString(dataarr, 0, byteRead - 4));

content = state.m_StringBuilder.ToString();

//判断数据长度,是否接收完全;
if (!(byteRead - 4 < datalength))
{
Console.WriteLine("Receive " + content.Length + "  " + content);

//接收完全后发送数据给客户端;
Send(receiverSocket, "success from server");
}
else
{
//数据没有接收完,继续接收;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

}

public static void Send(Socket handler, String data)
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
try
{
handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

}

public static void SendCallback(IAsyncResult result)
{
try
{
Socket handler = (Socket)result.AsyncState;
int bytesSend = handler.EndSend(result);

Console.WriteLine("Sent {0} bytes to client.", bytesSend);

handler.Shutdown(SocketShutdown.Both);

handler.Close();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
}
}

public class StateObject
{
public Socket m_CurSocket = null;
public const int m_BufferSize = 1024;
public byte[] m_Buffer = new byte[m_BufferSize];
public StringBuilder m_StringBuilder = new StringBuilder();
}
}

(二)使用线程


using System;
using System.Net;
using System.Net.Sockets;
using System.IO ;
using System.Text;
using System.Threading;

public class Echoserver
{
private static int m_SocketCount = 0;

//entry point of main method.
public static void Main()
{
Thread thread = new Thread(new ThreadStart(StartListen));
thread.Start();
while (true)
{
Thread.Sleep(1);
}
}

static void StartListen()
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("172.16.30.167"), 1223);

Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

try
{
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(20);
Console.WriteLine("Server Start");
serverSocket.BeginAccept(new AsyncCallback(Accept), serverSocket);
}
catch (System.Exception ex)
{
Console.WriteLine("Exception" + ex);
}
}




public static void Accept(IAsyncResult result)
{
Socket serverSocket = (Socket)result.AsyncState;


Console.WriteLine("Accept one Client "+(++m_SocketCount));

//已经Accept客户端之后就停止Accept;
Socket receiverSocket = serverSocket.EndAccept(result);

//开始Receive;
StateObject state = new StateObject();
state.m_CurSocket = receiverSocket;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
serverSocket.BeginAccept(Accept, serverSocket);
}

public static void ReceiveCallBack(IAsyncResult result)
{
String content = String.Empty;
StateObject state = (StateObject)result.AsyncState;
Socket receiverSocket = state.m_CurSocket;
try
{
int byteRead = receiverSocket.EndReceive(result);

if (byteRead > 0)
{
//获取数据长度;
byte[] datalengtharr=new byte[4];
Buffer.BlockCopy(state.m_Buffer, 0, datalengtharr, 0, 4);
int datalength = BitConverter.ToInt32(datalengtharr, 0);
Console.WriteLine("receive data length = " + datalength.ToString());

//获取数据主体;
byte[] dataarr = new byte[byteRead-4];
Buffer.BlockCopy(state.m_Buffer, 4, dataarr, 0, byteRead - 4);
state.m_StringBuilder.Append(Encoding.ASCII.GetString(dataarr, 0, byteRead - 4));

content = state.m_StringBuilder.ToString();

//判断数据长度,是否接收完全;
if (!(byteRead - 4 < datalength))
{
Console.WriteLine("Receive " + content.Length + "  " + content);

//接收完全后发送数据给客户端;
Send(receiverSocket, "success from server");
}
else
{
//数据没有接收完,继续接收;
receiverSocket.BeginReceive(state.m_Buffer, 0, StateObject.m_BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

}

public static void Send(Socket handler, String data)
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
try
{
handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

}

public static void SendCallback(IAsyncResult result)
{
try
{
Socket handler = (Socket)result.AsyncState;
int bytesSend = handler.EndSend(result);

Console.WriteLine("Sent {0} bytes to client.", bytesSend);

handler.Shutdown(SocketShutdown.Both);

handler.Close();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
}
}

public class StateObject
{
public Socket m_CurSocket = null;
public const int m_BufferSize = 1024;
public byte[] m_Buffer = new byte[m_BufferSize];
public StringBuilder m_StringBuilder = new StringBuilder();
}
}

本篇文章到此结束