C++Socket通讯样例(服务端)

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();

相关推荐
DC妙妙屋27 分钟前
10.24.2024刷华为OD C题型(四) -- 对象list按照多个属性排序
1024程序员节
无敌最俊朗@38 分钟前
unity3d————三角函数练习题
开发语言·学习·unity·c#·游戏引擎
bitenum1 小时前
qsort函数的学习与使用
c语言·开发语言·学习·算法·visualstudio·1024程序员节
EmotionFlying2 小时前
(11)(2.1.6) Hobbywing DroneCAN ESC(一)
copter·ardupilot·1024程序员节·电调和电机
伏飞而行2 小时前
六、元素应用CSS的习题
1024程序员节
月巴月巴白勺合鸟月半2 小时前
C#版的有道智云对话接口
开发语言·c#
浪里个浪的10242 小时前
【C#】编写计算机选课程序
开发语言·c#
idealzouhu2 小时前
Spring Boot 实现文件上传下载功能
java·spring boot·1024程序员节
sudo_Ene3 小时前
Isaac Sim Docker 部署并使用过程记录
笔记·学习·docker·1024程序员节·isaacsim
GGBondlctrl3 小时前
【JavaEE初阶】网络原理—关于TCP协议值滑动窗口与流量控制,进来看看吧!!!
网络·网络协议·tcp/ip·滑动窗口·流量控制·拥塞控制·tcp协议特性