1. 创建Socket实例并开启。
cs
private int OpenTcp(int port, string ip = "")
{
//1. 开启服务端
try
{
_tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddr = IPAddress.Any;
if (ip != "" && ip != string.Empty)
{
ipAddr = IPAddress.Parse(ip);
}
_tcpServer.Bind(new IPEndPoint(ipAddr, port));
_tcpServer.Listen();
_isListening = true;
//开启线程
Thread listenThread = new Thread(new ThreadStart(AcceptClients));
listenThread.Start();
}
catch (Exception ex)
{
Log.Instance.Error("TcpServer setup fail!\n" + CommonMsg.Space + ex.Message);
}
return 0;
}
2. 客户端连接------AcceptClients实现。
cs
private void AcceptClients()
{
while (_isListening)
{
//单客户端
_tcpSocket = _tcpServer.Accept();
if (_tcpSocket != null)
{
Thread clientThread = new Thread(SocketReceive);
clientThread.Start(_tcpSocket);
Log.Instance.Info($"Socket connected! ( {_tcpSocket.LocalEndPoint} )");
}
Thread.Sleep(300);
}
}
3. 接收客户端信息------SocketReceive实现。
(仅仅将接收的数据放到队列中,不加处理,否则可能耗时而影响通讯接收)
cs
private void SocketReceive(object? obj)
{
Socket? tcpClient = (Socket?)obj;
if (tcpClient == null) return;
//以下代码不放入线程中
while (true)
{
if (tcpClient.Connected)
{
int available = tcpClient.Available;
if (available > 0)
{
byte[] buffer = new byte[available];
int dataSize = tcpClient.Receive(buffer);
if (dataSize != available) { MessageBox.Show("Socket received data not equal to available data"); break; }
_queueMsgRecv.Enqueue(buffer);
}
}
Thread.Sleep(5);
}
tcpClient.Close();
}
4. 开启数据处理线程------OpenMsgParseThread
(不断从接收队列中取数据,根据通讯协议进行解析,并处理)
cs
private void OpenMsgParseThread()
{
Task.Run(() =>
{
while (true)
{
while (_queueMsgRecv.TryDequeue(out byte[]? buffer))
{
if (buffer != null && buffer.Length > 0)
{
//查询list 中是否存在结束符
while (true)
{
List<byte> bufList = buffer.ToList();
int flagIdx = bufList.IndexOf(0x04);
if (flagIdx == -1)
{
//不存在结束符
_msgValid += Encoding.UTF8.GetString(buffer);
break;
}
else
{
//存在结束符
_msgValid += Encoding.UTF8.GetString(buffer.Take(flagIdx).ToArray());
buffer = buffer.Skip(flagIdx + 1).Take(buffer.Length - flagIdx - 1).ToArray();
try
{
JObject jObj = JObject.Parse(_msgValid); //这个地方 如果发来的数据格式有问题,或者不完整,解析会出现异常
ProcessRecvMsg(jObj);
}
catch (Exception ex)
{
Log.Instance.Error("Msg: " + _msgValid);
_msgValid = "";
byte[] data = LeadlapTool.CreateComnData(MsgToken.TcpDataParseException, null, false, ex.Message);
SendMsg(data);
continue;
}
_msgValid = "";
continue;
}
}
}
}
Thread.Sleep(5);
}
});
}
5. 开启数据发送线程------OpenMsgSendThread
(不断从发送队列取数据,执行发送)
cs
private void OpenMsgSendThread()
{
Task.Run(() =>
{
while (true)
{
if (_tcpSocket.Connected)
{
lock (_locker)
{
bool ret = _queueMsgSend.TryDequeue(out byte[]? data);
if (ret && data != null)
{
_tcpSocket.Send(data);
}
}
}
Thread.Sleep(10);
}
});
}
6. 在需要发送数据处执行发送操作------SendMsg
cs
lock (_locker)
{
_queueMsgSend.Enqueue(data);
}
7. 关于数据解析
通常需要约定好帧头、帧尾,防止数据丢包、粘包情况。
(1)描述:数据为 byte[],以 0x04 为结束符。
(2)分析:如果数据发送由于阻塞,导致粘包;或者如果发送数据量过大,分好几次才接收完整,导致拆包;
(3)解决:粘包------对数据包解析识别结束符 0x04 并分割,分别解析。
拆包------对数据包叠加,直至识别到结束符 0x04,整合后解析。
(4)数据类型转换。
0x04 作为 byte比较。用 List 的 IndexOf 函数判断,若是-1,则不存在;否则返回 序号。
byte[] --> List: buffer.ToList();
List --> byte[]: bufList.ToArray();
byte[] --> string: Encoding.UTF8.GetString(buffer);
string --> byte[]: Encoding.UTF8.GetBytes(msg);
buffer.Take(flagIdx).ToArray();