上文中的同步服务端程序会一直阻塞等待某个客户端的数据,导致它同一时间只能处理一个客户端的请求,无法响应其他客户端。使用异步方法,可以让服务端同时处理多个客户端的数据,及时响应。
服务端需要将客户端的消息发送给所有的客户端,譬如聊天室,某个用户说了一句话,服务端需要把这句话发送给聊天室内的每一个人。因此服务端需要有个列表,保存所有连接上来的客户端信息。
创建ClientState类保存一个客户端的信息:
public class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
ClientState包含TCP连接所需Socket, 以及用于填充BeginReceive参数的读缓冲区readBuff。
为了方便的获取客户端的信息,定义Dictionary
static Dictionary<Socket,ClientState> clients = new Dictionary<Socket, ClientState>();
public IAsyncResult BeginAccept{
AsyncCallback callback;
object state;
}
public Socket EndAccept{
IAsyncResult asyncResult;
}
执行BeginAccept方法,当客户端连接上来时,回调函数AsyncCallback将被执行。在回调函数中,开发者可以使用EndAccept获取新客户端的套接字(Socket),还可以获取state参数传入的数据。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
public class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
//监听Socket listenfd
private static Socket listenfd;
//连接服务端的客户端列表(使用字典是为了方便查找)
private static Dictionary<Socket, ClientState> clients =
new Dictionary<Socket, ClientState>();
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//创建监听Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//注册Bind
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
//设置并开始监听
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
//传入回调函数的状态信息是:监听Socket
listenfd.BeginAccept(AcceptCallback, listenfd);
Console.ReadLine();
}
public static void AcceptCallback(IAsyncResult ar)
{
try
{
//根据传入的监听Socket调用End方法,返回:连接客户端的Socket
Console.WriteLine("[服务端]Accept");
Socket listenfd = (Socket)ar.AsyncState;
Socket clientfd = listenfd.EndAccept(ar);
//根据连接客户端的Socket创建对应的客户端信息对象
ClientState state = new ClientState();
state.socket = clientfd;
//将客户端信息对象加入服务端客户列表(Socket,state)
clients.Add(clientfd,state);
//对连接的客户端进行监听(接收数组使用对应客户端的读缓冲区readBuff)
clientfd.BeginReceive(state.readBuff, 0, 1024, 0,
ReceiveCallback, state);
//服务器继续监听:客户端连接
listenfd.BeginAccept(AcceptCallback, listenfd);
}
catch (SocketException e)
{
Console.WriteLine("Socket Accept fail"+e.ToString());
}
}
public static void ReceiveCallback(IAsyncResult ar)
{
try
{
//回调函数的状态参数为客户端信息
ClientState state = (ClientState)ar.AsyncState;
Socket clientfd = state.socket;
//接收客户端发送的信息
int count = clientfd.EndReceive(ar);
//客户端发送信息为空则移除客户端连接并且return Receive (关闭对其信息的接收)
if (count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return;
}
//发送原文回复客户端
string receStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + receStr);
//可以使用同步Send方法
clientfd.Send(sendBytes);
//继续接收客户端的信息
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (SocketException e)
{
Console.WriteLine("Socket Receive fail:"+e.ToString());
}
}
}
在Unity中导出exe文件,运行多个客户端开始聊天吧!
聊天服务端的区别在于,在发送信息时要将信息发送给每一个客户端。
聊天客户端的区别在于,在接收信息时将新接收到的信息拼接到原始文本的上一行即可。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
public class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
//监听Socket listenfd
private static Socket listenfd;
//连接服务端的客户端列表(使用字典是为了方便查找)
private static Dictionary<Socket, ClientState> clients =
new Dictionary<Socket, ClientState>();
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//创建监听Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//注册Bind
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
//设置并开始监听
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
//传入回调函数的状态信息是:监听Socket
listenfd.BeginAccept(AcceptCallback, listenfd);
Console.ReadLine();
}
public static void AcceptCallback(IAsyncResult ar)
{
try
{
//根据传入的监听Socket调用End方法,返回:连接客户端的Socket
Console.WriteLine("[服务端]Accept");
Socket listenfd = (Socket)ar.AsyncState;
Socket clientfd = listenfd.EndAccept(ar);
//根据连接客户端的Socket创建对应的客户端信息对象
ClientState state = new ClientState();
state.socket = clientfd;
//将客户端信息对象加入服务端客户列表(Socket,state)
clients.Add(clientfd,state);
//对连接的客户端进行监听(接收数组使用对应客户端的读缓冲区readBuff)
clientfd.BeginReceive(state.readBuff, 0, 1024, 0,
ReceiveCallback, state);
//服务器继续监听:客户端连接
listenfd.BeginAccept(AcceptCallback, listenfd);
}
catch (SocketException e)
{
Console.WriteLine("Socket Accept fail"+e.ToString());
}
}
public static void ReceiveCallback(IAsyncResult ar)
{
try
{
//回调函数的状态参数为客户端信息
ClientState state = (ClientState)ar.AsyncState;
Socket clientfd = state.socket;
//接收客户端发送的信息
int count = clientfd.EndReceive(ar);
//客户端发送信息为空则移除客户端连接并且return Receive (关闭对其信息的接收)
if (count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return;
}
//发送原文回复客户端
string receStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + receStr);
//可以使用同步Send方法
foreach (ClientState s in clients.Values)
{
s.socket.Send(sendBytes);
}
//继续接收客户端的信息
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (SocketException e)
{
Console.WriteLine("Socket Receive fail:"+e.ToString());
}
}
}