上篇文章中我们使用Connect,Receive和Send来进行接收数据,会阻塞,它是在单一线程完成的,不具备灵活性
因此可以使用BeginConnect和EndConnect等API完成相同功能完成异步连接和异步的发送接收
他们的函数原型如下:
BeginConnect:
cs
public IAsyncResult BeginConnect( string host, int port, AsyncCallback requestCallback, object state )
对应IP,端口,回调函数,state参数用于传递用户自定义的对象或数据给回调函数
EndConnect:
cs
public void EndConnect( IAsyncResult asyncResult )
BeginReceive:
cs
public IAsyncResult BeginReceive ( byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state )
EndReceive:
cs
public int EndReceive( IAsyncResult asyncResult )
BeginSend:
cs
public IAsyncResult BeginSend( byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state )
EndSend:
cs
public int EndSend ( IAsyncResult asyncResult )
BeginAccept:
cs
public IAsyncResult BeginAccept( AsyncCallback callback, object state )
EndAccept:
cs
public Socket EndAccept( IAsyncResult asyncResult )
一、客户端
1:异步Socket创建和连接(BeginConnect/EndConnect)
cs
byte[] readBuff = new byte[1024];
string recvStr = "";
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket); //异步连接,socket作为参数传递给回调函数
public void ConnectCallback(IAsyncResult ar) //回调函数
{
try
{
Socket socket = (Socket)ar.AsyncState; //ar.AsyncState 属性中获取了传递给 BeginConnect 方法的状态对象,即原始的Socket
socket.EndConnect(ar); //用于等待连接操作完成,并返回连接的结果。如果连接成功,则该方法会返回并且不会抛出异常;如果连接失败,则会抛出相应的异常。
Debug.Log("Socket Connect Succ");
}
catch (SocketException ex)
{
Debug.Log("Socket Connect fail" + ex.ToString());
}
}
注:每次调用BeginConnect方法启动异步连接操作时,都应该在适当的地方调用EndConnect方法来结束该操作
2:异步接收消息(基于异步连接)(BeginReceive/EndReceive)
cs
byte[] readBuff = new byte[1024];
string recvStr = "";
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket);
public void ConnectCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
Debug.Log("Socket Connect Succ");
socket.BeginReceive( readBuff, 0, 1024, 0, ReceiveCallback, socket); //继续开启接收数据的回调
}
catch (SocketException ex)
{
Debug.Log("Socket Connect fail" + ex.ToString());
}
}
private void ReveiveCallBack(IAsyncResult ar) //异步回调
{
try
{
Socket socket = (Socket)ar.AsyncState; //从BeginReceive中获取到的socket,就是原始Socket
int count = socket.EndReceive(ar); //同理,EndReceive和BeginReceive同时出现,结束异步操作,返回实际字节数
recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
socket.BeginReceive(readBuff, 0, 1024, 0, ReveiveCallBack, socket); //再次调用回调,继续接收下一批数据
}
catch(SocketException e)
{
Debug.Log("Socket Receive fail" + e.ToString());
}
}
private void Update()
{
text.text = recvStr;
}
注:两个地方调用了BeginReceive,一个在Connect的回调函数,一个在结束一次接收后,解析后再次调用
3:异步发送消息(BeginSend/EndSend)
cs
public void Send() {
//Send
string sendStr = InputFeld.text;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
}
public void SendCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket) ar.AsyncState;
int count = socket.EndSend(ar);
Debug.Log("Socket Send succ" + count);
}
catch (SocketException ex)
{
Debug.Log("Socket Send fail" + ex.ToString());
}
}
注:一般情况下,EndSend的返回值count与要发送数据的长度相同, 代表数据全部发出
二、服务端
1:多客户端准备
服务端需要处理多个连接,所以需要创建一个存储多个连接的列表,先定义一个ClientState类,保存一个客户端的全部信息,包括Socket,读缓冲区readBuff
然后创建一个能存储多个用户信息的字典
cs
class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
static Socket listenfd;
public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
}
2:异步接受连接(BeginAccept/EndAccept)
cs
class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
static Socket listenfd;
public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static void Main(string[] args)
{
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
listenfd.BeginAccept (AcceptCallback, listenfd);
}
public static void AcceptCallback(IAsyncResult ar)
{
try{
Console.WriteLine("[服务器]Accept");
Socket listenfd = (Socket) ar.AsyncState; //获取原始的Socket连接
Socket clientfd = listenfd.EndAccept(ar); //从原始Socket中读取到客户端Socket
//把读取到的客户端添加到字典
ClientState state = new ClientState();
state.socket = clientfd;
clients.Add(clientfd, state);
//接收数据BeginReceive
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
//继续Accept
listenfd.BeginAccept (AcceptCallback, listenfd);
}
}
catch(SocketException e)
{
Console.WriteLine("Socket Accept fail" + e.ToStrin());
}
}
三、聊天室
1:客户端
cs
using System;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;
public class ChatClient : MonoBehaviour
{
public InputField inputField;
public Text text;
byte[] readBuff = new byte[1024];
string recvStr = "";
Socket socket;
private void Start()
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("127.0.0.1", 8888, ConnectCallback, socket);
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
Debug.Log("Socket Connect Succ");
socket.BeginReceive(readBuff, 0, 1024, 0,ReceiveCallback, socket);
}
catch(Exception e)
{
Debug.Log("Socket connect fail" + e.ToString());
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndReceive(ar);
string s = System.Text.Encoding.Default.GetString(readBuff, 0, count);
recvStr = s + "\n" + recvStr;//差异在于会显示历史数据
socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallback, socket);
}
catch(Exception e)
{
Debug.Log("Socket Receive fail" + e.ToString());
}
}
public void Send()
{
string sendStr = inputField.text;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndSend(ar);
Debug.Log("Socket Send succ" + count);
}
catch (SocketException ex)
{
Debug.Log("Socket Send fail" + ex.ToString());
}
}
private void Update()
{
text.text = recvStr;
}
}
2:服务端
聊天室与Echo程序的不同之处在于服务端对消息的处理
同样需要把BeginAccept放到死循环里,让它一直接收消息,避免退出
cs
using System.Net;
using System.Net.Sockets;
class ClientState{
public Socket? socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
static Socket listenfd;
public static Dictionary<Socket,ClientState> clients = new Dictionary<Socket, ClientState> ();
static void Main(string[] args)
{
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iP = IPAddress.Parse ("127.0.0.1");
IPEndPoint iPEp = new IPEndPoint(iP, 8888);
listenfd.Bind (iPEp);
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
while (true)
{
listenfd.BeginAccept(AcceptCallback, listenfd);
}
}
private static void AcceptCallback(IAsyncResult ar)
{
try
{
Console.WriteLine("[服务器]Accept");
Socket listenfd = (Socket) ar.AsyncState;
Socket clientfd = listenfd.EndAccept(ar);
ClientState state = new ClientState();
state.socket = clientfd;
clients.Add(clientfd, state);
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
listenfd.BeginAccept(AcceptCallback, listenfd);
}
catch (Exception ex)
{
Console.WriteLine("Socket Accept fail" + ex.ToString());
}
}
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
ClientState state = (ClientState)ar.AsyncState;
Socket clientfd = state.socket;
int count = clientfd.EndReceive(ar);
if(count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return;
}
string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr);
foreach(ClientState s in clients.Values)
{
s.socket.Send(sendBytes);
}
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (Exception ex)
{
Console.WriteLine("Socket Receive fail" + ex.ToString());
}
}
}