1. 本文主要内容
初次接触网络通信部分内容时踩了不少坑,历经磕绊总算摸索出了靠谱有效的实现异步通信的解决方法,在此做简单记录,之后若有需要可以节约这段时间。
首先进行一下简要说明:
- 异步网络通信指的是客户端在发送请求等待接收数据时,不必一直阻塞直到收到服务端回复才能进行下一步处理,在发送完数据之后就可以执行其他操作了,等到接收到相应回复,再通过回调函数来处理消息内容。与之相对的就是同步消息收发策略。
- 不管是何种语言,谁是服务端,谁是客户端,其收发消息时的流程步骤都是类似的,大体包括:根据ip和端口号创建socket,发送方对将数据打包写入socket,接收方解析数据并处理。
- TCP之类的可靠传输协议,只能保证数据完整,有序的从传输方到接收方,并不管数据本身是什么,该交给那个方法处理,想要解析收到的数据,只有靠自己定义消息格式并建立相应的打包及解析方法才能识别具体是什么内容,对应哪条回复等等之类。
在我之前想用它来进行通信时,曾天真地以为我这儿把消息写到网络流里去了,那儿就应该收到这条消息的内容,可以直接处理了。
但实际上,虽然数据可以有序、完整的被接收,但是并不会每次就正好接收到一条另一端发送的完整消息,而是存在分包、粘包的情况,因此,无论是服务端,还是客户端,都要对该问题进行处理。
在本文中,以TCP为通信协议,c#端为客户端,向python服务端发起连接请求,然后服务器进行回复,客户端对回复内容再进行处理,那么该简单处理流程如下文所示。
2. 通信策略
- 为了让接收方知道这条消息的起止位置,在消息的头部加入一个4字节整型,用来标识这段消息的长度,假如我们需要发送字符串
Hello world!
消息给服务器,那么我们写入socket
中的数据应如下表所示(实际传输的就最后一行的字节数组):
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
消息长度 | 内容 | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
16 | H | e | l | l | o | w | o | r | l | d | ! | ||||
16 | 0 | 0 | 0 | 72 | 101 | 108 | 108 | 111 | 32 | 119 | 111 | 114 | 108 | 100 | 33 |
- 接收方就需要根据首部的长度,判断当前消息的完整内容,如果当前接收内容小于消息长度,就继续接收直到这个消息完整,若大于消息长度,那么将这条消息的内容提取出来,将后面的消息进行合并再重新解析。
3. C#端收发数据
以下是c#端建立连接以及收发数据的示意代码:
// NetworkDemo.cs
using System.Collections;
using System;
using System.IO;
using System.Net.Sockets;
public class NetworkDemo
{
public String host = "127.0.0.1";
public Int32 port = 2000;
public Queue events;
internal Boolean socketReady = false;
byte[] receivedBuff;
int receivedBuffSize = 2048;
TcpClient tcpSocket;
NetworkStream netstream;
BinaryWriter writer;
// 处理粘包,分包相关变量
const int msgHeadLen = sizeof(int);
int currDataLen = 0; // 报文长度
int currReceLen = 0; // 报文内容接受不完整 currReceLen < currDataLen
public NetworkDemo()
{
SetupSocket();
receivedBuff = new byte[receivedBuffSize];
Receive(receivedBuff, 0, receivedBuffSize, UnPackRawData);
events = new Queue();
}
public void SetupSocket()
{
try
{
tcpSocket = new TcpClient(host, port);
netstream = tcpSocket.GetStream();
writer = new BinaryWriter(netstream);
socketReady = true;
}
catch (Exception e)
{
Console.WriteLine("Socket error:" + e);
}
}
public void CloseSocket()
{
if (!socketReady)
return;
writer.Close();
netstream.Close();
tcpSocket.Close();
socketReady = false;
}
private bool IsSocketReady()
{
// 如果连接尚未创立,尝试建立新连接
i