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

相关推荐
车载测试工程师4 小时前
CAPL学习-IP API函数-2
网络·学习·tcp/ip·capl·canoe
●VON4 小时前
在鸿蒙 PC 上使用 Electron 获取本机 IP 地址
tcp/ip·electron·harmonyos
我是唐青枫5 小时前
C#.NET 范围与索引(Range、Index)完全解析:语法、用法与最佳实践
c#·.net
车载测试工程师6 小时前
CAPL学习-IP API函数-1
网络·学习·tcp/ip·capl·canoe·doip
CoderYanger6 小时前
优选算法-优先级队列(堆):75.数据流中的第K大元素
java·开发语言·算法·leetcode·职场和发展·1024程序员节
烛阴7 小时前
从`new()`到`.DoSomething()`:一篇讲透C#方法与构造函数的终极指南
前端·c#
深海潜水员7 小时前
【MonoGame游戏开发】| 牧场物语实现 第一卷 : 农场基础实现 (下)
vscode·游戏·c#·.net·monogame
CoderYanger8 小时前
C.滑动窗口——1423. 可获得的最大点数
java·开发语言·算法·leetcode·1024程序员节
合作小小程序员小小店8 小时前
图书管理系统,基于winform+sql sever,开发语言c#,数据库mysql
开发语言·数据库·sql·microsoft·c#
4***v21418 小时前
详解 为什么 tcp 会出现 粘包 拆包 问题
网络·tcp/ip·php