当前位置: 首页 > article >正文

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


http://www.kler.cn/a/370545.html

相关文章:

  • ORB-SLAM2源码学习:ORBmatcher.cc⑥: int ORBmatcher::Fuse将地图点投影到关键帧中进行匹配和融合
  • ARP Check
  • 消息队列实战指南:三大MQ 与 Kafka 适用场景全解析
  • 【Azure 架构师学习笔记】- Azure Function (2) --实操1
  • 【Pytorch实用教程】TCN(Temporal Convolutional Network,时序卷积网络)简介
  • SQL ON与WHERE区别
  • Vue 权限管理
  • STM32 第3章 如何用串口下载程序
  • 快消品行业数字化转型:定制开发 S2B2C 商城小程序的主战场选择与突破
  • windows环境下,使用docker搭建redis集群
  • .NET Core WebApi第5讲:接口传参实现、数据获取流程、204状态码问题
  • Flutter之build 方法详解
  • 第15课 算法(上)
  • 海外学子如何玩转反向代购,解锁财富密码!
  • 数据库的开发---实训报告
  • React Intl 的工作原理
  • Knife4j配置 ▎使用 ▎教程 ▎实例
  • Peach-9B-8k-Roleplay模型部署指南
  • 利用Kubernetes原生特性实现简单的灰度发布和蓝绿发布
  • 为什么架构设计禁止IP直连?
  • 网管平台(进阶篇):网管软件的配置方式
  • 数据库产品中SQL注入防护功能应该包含哪些功能
  • Golang | Leetcode Golang题解之第515题在每个树行中找最大值
  • Android 相机CameraX框架
  • 【面试】rabbitmq的主要组件有哪些?
  • 什么是时间戳?怎么获取?有什么用?