• 用C++ Thread实现简单的socket多线程通信


    用C++ Thread实现简单的socket多线程通信

    起因

    为什么要用C++的Thread,很简单,因为我菜

    一打五用pthread实现了socket多线程通信,我之前学并发的时候没看pthread,因此代码只能看个大概,后面还是要系统学一下pthread

    服务端

    多线程功能放在腾讯云服务器上,代码如下:

    #include "tcpserver.h"
    #include <thread>
    #include <mutex>
    
    TcpServer server;
    mutex tcp_mutex;
    
    void tcpFunc(int clientfd);
    
    int main(int argc, char *argv[])
    {
    
        if (server.initServer(6666) == false)
        {
            cout << "服务端初始化失败!!!" << endl;
            return -1;
        }
    
        vector<thread> tcp_vec;
        while (true)
        {
            if (!server.tcpAccept())
            {
                continue;
            }
            tcp_vec.emplace_back(tcpFunc, server.m_connectfd);
            // thread tcpThread(tcpFunc, server.m_connectfd);
            // if (tcpThread.joinable())
            if(tcp_vec.back().joinable())
            {
                // cout << "Tcp thread " << tcpThread.get_id() << "is joinable!" << endl;
                cout << "Tcp thread " << tcp_vec.back().get_id() << " is joinable!" << endl;
                tcp_vec.back().detach();
            }
        }
    
        return 0;
    }
    
    void tcpFunc(int clientfd)
    {
        int buf_len = 0;
        char buffer[1024];
        while (true)
        {
            unique_lock<mutex> tcplck(tcp_mutex);
            memset(buffer, 0, sizeof(buffer));
            if (!server.tcpRecv(clientfd, buffer, &buf_len, 5))
            {
                cout << "接收客户端数据失败!" << endl;
                tcplck.unlock();
                break;
            }
            cout << "服务端接收数据:" << buffer << endl;
    
            strcpy(buffer, "I am your father!");
            if (!server.tcpSend(clientfd, buffer, sizeof(buffer)))
            {
                cout << "向客户端发送数据失败!" << endl;
                tcplck.unlock();
                break;
            }
            tcplck.unlock();
    				usleep(100);
        }
        cout << "通信异常!" << endl;
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    实在是很简单,贻笑大方了

    有几个注意点:

    • 全局变量在main函数执行完后会销毁,线程中用到了全局变量server,线程detach后要保证数据的收发,就要保持server的生存期,这里体现为在main中循环等待客户端的连接
    • 要用锁锁住线程中server的操作,避免不同线程同时操作server造成混乱
    • usleep(100);是为了避免不同线程争抢同一把锁而造成死锁的发生

    ROS客户端

    #include "tcpclient.h"
    #include <ros/ros.h>
    #include <geometry_msgs/Twist.h>
    
    TcpClient client;
    string send_str = "I am king of the world!";
    char recv_buff[1024];
    
    void client_callback(const geometry_msgs::Twist::ConstPtr &msg)
    {
        cout << "vel X:" << msg->linear.x << ";vel Y:" << msg->linear.y << ";angular Z:" << msg->angular.z << endl;
        if (!client.tcpSend(client.m_sockfd, send_str.data(), send_str.size()))
        {
            cout << "向服务端发送报文失败!" << endl;
        }
        if (!client.tcpRecv(client.m_sockfd, recv_buff, NULL, 10))
        {
            cout << "从服务端接收报文失败!" << endl;
        }
        cout << "接收服务端报文:" << recv_buff << endl << endl;
    }
    
    int main(int argc, char **argv)
    {
        ros::init(argc, argv, "joystick_client");
        ros::NodeHandle nh;
    
        string server_ip = "1.116.137.21";
        string loop_ip = "127.0.0.1";
    
        if (client.connectToServer(server_ip.data(), 6666) == false)
        {
            cout << "连接失败!!!" << endl;
            return -1;
        }
    
        ros::Subscriber sub = nh.subscribe("/cmd_vel", 1, client_callback);
    
        ros::spin();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    很简单,订阅了手柄发布的话题/cmd_vel,在回调函数中和服务端通讯

    话题的发布频率是10Hz,意味着和服务端通讯的频率也是10Hz

    普通客户端

    #include "tcp/tcpclient.h"
    
    int main(int argc, char **argv)
    {
        TcpClient client;
    
        string server_ip = "1.116.137.21";
        string loop_ip = "127.0.0.1";
    
        if (client.connectToServer(server_ip.data(), 6666) == false)
        {
            cout << "连接失败!!!" << endl;
            return -1;
        }
        cout << "成功连接服务器!" << endl;
    
        char buff[1024];
        while (true)
        {
            memset(buff, 0, sizeof(buff));
            sprintf(buff, "Ouch!");
            if (!client.tcpSend(client.m_sockfd, buff, sizeof(buff)))
            {
                cout << "向服务端发送报文失败!" << endl;
                return -1;
            }
    
            memset(buff, 0, sizeof(buff));
            if (!client.tcpRecv(client.m_sockfd, buff, NULL, 5))
            {
                cout << "从服务端接收报文失败!" << endl;
                return -1;
            }
            cout << "接收服务端报文:" << buff << endl << endl;
            sleep(0.1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    这里sleep(0.1);是为了模拟ROS中话题的频率

    sleep过长会导致服务端阻塞等待该客户端的消息,从而导致其余客户端与服务端的通信失败(如果客户端中允许的通信延时很短的话)

    运行效果

    云服务器上的服务端

    [root@VM-4-11-centos bin]# ./server_thread 
    Tcp thread 140662362572544 is joinable!
    服务端接收数据:I am king of the world!
    服务端接收数据:I am king of the world!
    服务端接收数据:I am king of the world!
    服务端接收数据:I am king of the world!
    Tcp thread 140662354179840 is joinable!
    服务端接收数据:I am king of the world!
    服务端接收数据:Ouch!
    服务端接收数据:I am king of the world!
    服务端接收数据:Ouch!
    服务端接收数据:I am king of the world!
    服务端接收数据:Ouch!
    服务端接收数据:I am king of the world!
    服务端接收数据:Ouch!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    笔记本上的ROS客户端

    redwall@redwall-G3-3500:~$ rosrun joystick_client joystick_client 
    [ERROR] [1656939307.244367879]: [registerPublisher] Failed to contact master at [localhost:11311].  Retrying...
    [ INFO] [1656939314.923909682]: Connected to master at [localhost:11311]
    vel X:0;vel Y:0;angular Z:0
    接收服务端报文:I am your father!
    
    vel X:0;vel Y:0;angular Z:0
    接收服务端报文:I am your father!
    
    vel X:0;vel Y:0;angular Z:0
    接收服务端报文:I am your father!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    虚拟机的普通客户端

    prejudice@prejudice-VirtualBox:~/socket_test/socket_for_linux/bin$ ./tcp_client 成功连接服务器!
    接收服务端报文:I am your father!
    
    接收服务端报文:I am your father!
    
    接收服务端报文:I am your father!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    不足

    1. 未考虑线程的清理
    2. 未考虑信号的退出处理
  • 相关阅读:
    法大大举办2023年伙伴大会:AI赋能,建设生态2.0
    【开题报告】基于SpringBoot的美术馆预约平台的设计与实现
    Instant Neural Graphics Primitives with a Multiresolution Hash Encoding以及源码浅析
    企业直播怎么从公域引流
    vue项目分环境打包的具体步骤 --- 区分测试环境与线上环境的打包引用路径
    ES7升级、jar包升级、工具类封装,代码改造
    华为云云服务器评测|华为云云耀云服务器L实例使用教学
    联想Thinkbook Ubuntu18.04 安装nvidia显卡驱动
    FileOutputStream中和FileWriter的换行不同
    数据结构预算法--链表(单链表,双向链表)
  • 原文地址:https://blog.csdn.net/qq_34935373/article/details/125608828