UN2L9——服务端和客户端异步通信

服务端Socket的TCP异步通信

暂时不考虑区分消息类型,分包、黏包以及心跳消息等功能,实现一个基础的服务端

ServerClient.cs

  • clientDic​:用于管理各个连接客户端的Socket,可以通过ID来获取单个Socket
  • Start​:提供给外部开启并初始化服务端Socket的方法,使用异步方法开启监听客户端连入
  • AcceptCallBack​:监听客户端连入异步方法的回调方法,处理连接客户端的Socket,并开启下一次监听客户端连入
  • BroadCast​:让所有的ClientSocket​发送传入的广播消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Net;
using System.Net.Sockets;

namespace TeachTcpServerAsync
{
internal class ServerSocket
{
private Socket? socket;
private Dictionary<int, ClientSocket> clientDic = new Dictionary<int, ClientSocket>();

public void Start(string ip, int port, int num)
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
try
{
socket.Bind(ipPoint);
socket.Listen(num);
//通过异步监听客户端连入
socket.BeginAccept(AcceptCallBack, null);
Console.WriteLine($"服务端启动成功");
}
catch (Exception e)
{
Console.WriteLine($"服务端启动失败:{e.Message}");
}
}

private void AcceptCallBack(IAsyncResult result)
{
if (socket == null)
return;
try
{
//获取连入的客户端,并记录
Socket clientSocket = socket.EndAccept(result);
ClientSocket client = new ClientSocket(clientSocket);
clientDic.Add(client.clientID, client);
//继续监听客户端的连入
socket.BeginAccept(AcceptCallBack, null);
}
catch (Exception e)
{
Console.WriteLine($"客户端连入失败:{e.Message}");
}
}

public void Broadcast(string str)
{
foreach (var client in clientDic.Values)
{
client.Send(str);
}
}
}
}

ClientSocket.cs

  • CLIENT_BEGIN_ID​:连接客户端的Socket起始ID,每多一个ClientSocket​就累加1
  • clientID​:ClientSocket​的ID,便于从字典内获取
  • cacheBytes​:缓存数组
  • cacheNum​:缓存数组内有多少字节数据
  • ReceiveCallBack​:接收消息异步方法的回调方法,消息在这里处理,并开启下一次的接收消息的异步方法
  • Send​:对外开放的发送消息的方法,使用异步方法发送消息
  • SendCallBack​:异步方法发送消息的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System.Net.Sockets;
using System.Text;

namespace TeachTcpServerAsync
{
internal class ClientSocket
{
public Socket socket;
public int clientID;
private static int CLIENT_BEGIN_ID = 1;

private byte[] cacheBytes = new byte[1024];
private int cacheNum = 0;

public ClientSocket(Socket socket)
{
this.socket = socket;
this.clientID = CLIENT_BEGIN_ID++;

//开始收消息
this.socket.BeginReceive(cacheBytes, cacheNum, cacheBytes.Length, SocketFlags.None, ReceiveCallBack, this.socket);
}

private void ReceiveCallBack(IAsyncResult result)
{
try
{
cacheNum = this.socket.EndReceive(result);
//通过字符串去解析
Console.WriteLine(Encoding.UTF8.GetString(cacheBytes, 0, cacheNum));
//如果是连接状态再继续收消息,目前还是以字符串形式解析,所以解析完就直接从0开始接收
cacheNum = 0;
if (this.socket.Connected)
this.socket.BeginReceive(cacheBytes, cacheNum, cacheBytes.Length, SocketFlags.None, ReceiveCallBack, this.socket);
else
Console.WriteLine($"客户端{clientID}已断开连接");
}
catch (SocketException e)
{
Console.WriteLine($"接收消息错误[{e.ErrorCode}]:{e.Message}");
}
}

public void Send(string str)
{
if (this.socket.Connected)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
this.socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallBack, null);
}
}

private void SendCallBack(IAsyncResult result)
{
try
{
this.socket.EndSend(result);
}
catch (SocketException e)
{
Console.WriteLine($"发送失败[{e.ErrorCode}]:{e.Message}");
}
}
}
}

Program.cs

  • Main​:创建并初始化服务器SocketServerSocket​,然后循环监听终端输入,输入B:​就调用服务端广播消息方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace TeachTcpServerAsync
{
internal class Program
{
static void Main(string[] args)
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.Start("127.0.0.1", 8080, 1024);

while (true)
{
string? input = Console.ReadLine();
if (string.IsNullOrEmpty(input))
return;
if (input.Substring(0, 2) == "B:")
{
serverSocket.Broadcast(input.Substring(2));
}
}
}
}
}

客户端Socket的TCP异步通信

暂时不考虑区分消息类型,分包、黏包以及心跳消息等功能,实现一个基础的客户端​

  • cacheBytes​:缓存数组
  • cacheNum​:缓存数组内有多少字节数据
  • Connent​:传入服务器的IP地址与端口号,使用异步方法连接对应的服务器,连接后就使用异步方法开始接收服务端消息
  • ReceiveCallBack​:异步接收消息的回调函数,处理消息,并开始下一次异步接收服务端消息
  • Close​:关闭并释放客户端Socket
  • Send​:将外部要发送的消息处理后,使用异步方法将字节数组发送到服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class NetAsyncMgr : MonoBehaviour
{
private static NetAsyncMgr instance;
public static NetAsyncMgr Instance => instance;
//和服务器进行连接的Socket
private Socket socket;
//接收消息用的缓存容器
private byte[] cacheBytes = new byte[1024 * 1024];
private int cacheNum = 0;

private void Awake()
{
instance = this;
DontDestroyOnLoad(gameObject);
}

//连接服务器的代码
public void Connent(string ip, int port)
{
if (socket != null && socket.Connected)
return;
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.RemoteEndPoint = ipPoint;
args.Completed += (socket, args) =>
{
if (args.SocketError == SocketError.Success)
{
print("连接成功");
//开始收消息
SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
receiveArgs.SetBuffer(cacheBytes, 0, cacheBytes.Length);
receiveArgs.Completed += ReceiveCallBack;
this.socket.ReceiveAsync(receiveArgs);
}
else
{
print($"连接失败:{args.SocketError}");
}
};
socket.ConnectAsync(args);
}

//接收消息完成的回调函数
private void ReceiveCallBack(object obj, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
//解析消息,目前用的字符串规则
print(Encoding.UTF8.GetString(args.Buffer, 0, args.BytesTransferred));
//继续去收消息
args.SetBuffer(0, args.Buffer.Length);
//继续异步接收消息
if (this.socket != null && this.socket.Connected)
socket.ReceiveAsync(args);
else
Close();
}
else
{
print($"接收消息时出错:{args.SocketError}");
//关闭客户端连接
Close();
}
}

public void Close()
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(false);
socket.Close();
socket = null;
}
}

public void Send(string str)
{
if (this.socket != null && this.socket.Connected)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.SetBuffer(bytes, 0, bytes.Length);
args.Completed += (socket, args) =>
{
if (args.SocketError != SocketError.Success)
{
print($"发送消息失败:{args.SocketError}");
Close();
}
};
this.socket.SendAsync(args);
}
else
{
Close();
}
}
}