Unity3D游戏制作学习记录02——丛林战争
Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61.
一、服务器端——消息接收的异步处理
由于之前使用Receive()方法来接受消息,是在等待消息的过程中一直执行,直到接收到消息了才会继续执行下面的代码,因此接收消息的方式是同步的。
这样的接收就只能一条一条的接收处理,不太符合实际的需求,所以就需要有异步的处理了。
异步处理调用的接收消息的方法是BeginReceive(),其中的参数是,接收到消息后会执行什么方法,由于这个方法的执行是不可知的,因此它是一个回调函数。
服务器端代码
class Program
{
static void Main(string[] args)
{
StartServerAsync();
}
// 用于异步接收消息的数组
static int bufferSize = 1024;
static byte[] readBuf = new byte[bufferSize];
/// <summary>
/// 启动客户端,异步处理消息接收
/// </summary>
static void StartServerAsync()
{
//用于通信的套接字: IP类型,套接字类型:管道通信, 协议:TCP
Socket serverScoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 定义IP
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
int port = 7788;
IPEndPoint iPEndPoint = new IPEndPoint(ipAddress, port);
// 绑定IP
serverScoket.Bind(iPEndPoint);
// 开启监听
serverScoket.Listen(0);
// 等待连接,直到接收到连接请求
Socket clientSocket = serverScoket.Accept();
// 向客户端发送一条消息(二进制)
string msgSend = "连接成功,服务器向客户端发送消息...";
byte[] msgByt = System.Text.Encoding.UTF8.GetBytes(msgSend);
clientSocket.Send(msgByt);
// 异步接收
// 读取的数据存储数组,存储的起始位置,存储数组的大小,套接字的标志位,接收到消息时要执行的函数,要给函数传递的信息
clientSocket.BeginReceive(readBuf,0,1024,SocketFlags.None, ReceiveCallback, clientSocket);
Console.ReadKey();
}
/// <summary>
/// 回调函数,有消息接收的时候要进行的操作
/// </summary>
/// <param name="ar"></param>
static void ReceiveCallback(IAsyncResult ar)
{
// 转换传递的对象为客户端连接的套接字
Socket clientSocket = (Socket)ar.AsyncState;
// 结束消息接收,获取消息
int count = clientSocket.EndReceive(ar);
string msgRecv = System.Text.Encoding.UTF8.GetString(readBuf,0,count);
Console.WriteLine("接收客户端消息:"+msgRecv);
// 重新开启和客户端的消息接收
clientSocket.BeginReceive(readBuf, 0, bufferSize, SocketFlags.None, ReceiveCallback, clientSocket);
}
}
客户端代码
我们通过死循环来实现客户端可以一直向服务器端发送消息。
static void Main(string[] args)
{
//用于通信的套接字: IP类型,套接字类型:管道通信, 协议:TCP
Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 服务器请求连接客户端
socketClient.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7788));
// 客户端接收消息
// 定义缓存区,存储接收到的服务器消息
byte[] readBuf = new byte[1024];
int count = socketClient.Receive(readBuf);
string msgRecv = System.Text.Encoding.UTF8.GetString(readBuf, 0, count);
// 将消息显示在控制台上
Console.WriteLine(msgRecv);
while (true)
{
// 客户端向服务器发送消息,发送的消息由用户在控制台输入
string msgSend = Console.ReadLine();
socketClient.Send(System.Text.Encoding.UTF8.GetBytes(msgSend));
}
Console.ReadKey();
// 关闭连接
socketClient.Close();
}
二、服务器端——多客户端连接
多客户端的连接我们同样是采取异步处理的方式,即BeginAccept()方法。
BeginAccept():它的参数有两个,一个是接收到连接请求的时候,要执行的函数,另一个是执行函数的时候要传递的内容。
同样这个要执行的函数也是回调函数,我们把客户端连接的获取,以及消息的收发都写在这个函数里面。
由于在BeginAccept()获取连接之后,消息收发都在获取连接的回调函数AcceptCallback()内执行,但在AcceptCallback()内,我们暂时的关闭了连接用以获取当前的客户端连接,所以我们需要在消息处理完后,重新开启连接接收,才能继续接收客户端连接申请。
服务器端代码
class Program
{
static void Main(string[] args)
{
StartServerAsync();
}
// 用于异步接收消息的数组
static int bufferSize = 1024;
static byte[] readBuf = new byte[bufferSize];
/// <summary>
/// 启动客户端,异步处理消息接收
/// </summary>
static void StartServerAsync()
{
//用于通信的套接字: IP类型,套接字类型:管道通信, 协议:TCP
Socket serverScoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 定义IP
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
int port = 7788;
IPEndPoint iPEndPoint = new IPEndPoint(ipAddress, port);
// 绑定IP
serverScoket.Bind(iPEndPoint);
// 开启监听
serverScoket.Listen(0);
// 异步处理客户端的连接
serverScoket.BeginAccept(AcceptCallback, serverScoket);
Console.ReadKey();
}
/// <summary>
/// 回调函数,有连接请求时要进行的操作
/// </summary>
/// <param name="ar"></param>
static void AcceptCallback(IAsyncResult ar)
{
// 获取服务器端
Socket serverSocket = ar.AsyncState as Socket;
// 关闭连接接收,获取当前客户端连接
Socket clientSocket = serverSocket.EndAccept(ar);
// 向客户端发送一条消息(二进制)
string msgSend = "连接成功,服务器向客户端发送消息...";
byte[] msgByt = System.Text.Encoding.UTF8.GetBytes(msgSend);
clientSocket.Send(msgByt);
// 异步接收客户端的消息
// 读取的数据存储数组,存储的起始位置,存储数组的大小,套接字的标志位,接收到消息时要执行的函数,要给函数传递的信息
clientSocket.BeginReceive(readBuf, 0, 1024, SocketFlags.None, ReceiveCallback, clientSocket);
// 重新开启连接接收
serverSocket.BeginAccept(AcceptCallback,serverSocket);
}
/// <summary>
/// 回调函数,有消息接收的时候要进行的操作
/// </summary>
/// <param name="ar"></param>
static void ReceiveCallback(IAsyncResult ar)
{
// 转换传递的对象为客户端连接的套接字
Socket clientSocket = (Socket)ar.AsyncState;
// 结束消息接收,获取消息
int count = clientSocket.EndReceive(ar);
string msgRecv = System.Text.Encoding.UTF8.GetString(readBuf,0,count);
Console.WriteLine("接收客户端消息:"+ msgRecv);
// 重新开启和客户端的消息接收
clientSocket.BeginReceive(readBuf, 0, bufferSize, SocketFlags.None, ReceiveCallback, clientSocket);
}
}
运行结果
-
运行服务器端
-
运行客户端1
-
运行客户端2
-
客户端2向服务器端发送消息
-
客户端1向服务器端发送消息
代码完善
在服务器端仍然开启,但关闭客户端的时候,会出现报错,
像这样子:
或者这样子:
这是由于客户端的不正常关闭导致哒。
那么我们就在服务器端通过异常来捕获它,并从服务器端这一侧来关闭连接。
/// <summary>
/// 回调函数,有消息接收的时候要进行的操作
/// </summary>
/// <param name="ar"></param>
static void ReceiveCallback(IAsyncResult ar)
{
Socket clientSocket = null;
try
{
// 转换传递的对象为客户端连接的套接字
clientSocket = (Socket)ar.AsyncState;
// 结束消息接收,获取消息
int count = clientSocket.EndReceive(ar);
string msgRecv = System.Text.Encoding.UTF8.GetString(readBuf, 0, count);
Console.WriteLine("接收客户端消息:" + msgRecv);
// 重新开启和客户端的消息接收
clientSocket.BeginReceive(readBuf, 0, bufferSize, SocketFlags.None, ReceiveCallback, clientSocket);
}
catch(Exception e)
{
Console.WriteLine(e);
if (clientSocket != null)
{
clientSocket.Close();
}
}
}
但是,如果我给客户端加一个标识,比如说按下c键,就从客户端侧关闭连接,此时服务器端会一直接受到长度为0的空消息。
那是否我在客户端控制台输入回车也会表示发送空数据呢,并不是的,因为即使回车也是一个‘\n’,看上去是空但并不是空。
所以我们可以在服务器端接收消息的时候判断接收到的数据是否长度为0,如果是就可以直接断开和客户端的连接了。
try
{
// 转换传递的对象为客户端连接的套接字
clientSocket = (Socket)ar.AsyncState;
// 结束消息接收,获取消息
int count = clientSocket.EndReceive(ar);
if (count == 0)
{
clientSocket.Close();
}
string msgRecv = System.Text.Encoding.UTF8.GetString(readBuf, 0, count);
Console.WriteLine("接收客户端消息:" + msgRecv);
// 重新开启和客户端的消息接收
clientSocket.BeginReceive(readBuf, 0, bufferSize, SocketFlags.None, ReceiveCallback, clientSocket);
}