C++Socket通讯样例(服务端)
1. 创建Socket实例并开启。
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实现。
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实现。
(仅仅将接收的数据放到队列中,不加处理,否则可能耗时而影响通讯接收)
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
(不断从接收队列中取数据,根据通讯协议进行解析,并处理)
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
(不断从发送队列取数据,执行发送)
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
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();