用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;
}

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

有几个注意点:

  • 全局变量在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();
}

很简单,订阅了手柄发布的话题/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;
}

这里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!

笔记本上的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!

虚拟机的普通客户端

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. 未考虑信号的退出处理
  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
你好!关于C++多线程Socket通信的问题,我可以帮你解答。首先,多线程是一种并发编程的方式,可以同时执行多个任务,提高程序的性能和响应能力。而Socket通信是一种基于网络的通信方式,可以在不同的计算机之间进行数据传输。 在C++中,你可以使用标准库中的thread类来创建和管理多线程。下面是一个简单的例子: ```cpp #include <iostream> #include <thread> // 线程函数 void threadFunc(int n) { std::cout << "Hello from thread! n = " << n << std::endl; } int main() { // 创建线程并启动 std::thread t(threadFunc, 42); // 等待线程结束 t.join(); return 0; } ``` 上面的代码中,我们使用thread类创建了一个新线程,并指定了要执行的线程函数threadFunc。线程函数可以带有参数,这里我们传递了一个整数n。通过调用t.join()来等待线程执行完毕。 关于Socket通信C++提供了一些库来方便地进行网络编程,如Winsock和POSIX Socket等。你可以使用这些库来创建服务器和客户端程序,实现数据的发送和接收。 下面是一个简单的例子,展示了如何使用Winsock库创建一个简单的TCP服务器: ```cpp #include <iostream> #include <winsock2.h> int main() { // 初始化Winsock库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cerr << "Failed to initialize Winsock" << std::endl; return 1; } // 创建套接字 SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0); if (listenSocket == INVALID_SOCKET) { std::cerr << "Failed to create socket" << std::endl; WSACleanup(); return 1; } // 绑定地址和端口 sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = INADDR_ANY; serverAddress.sin_port = htons(8888); // 使用端口8888 if (bind(listenSocket, (sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) { std::cerr << "Failed to bind socket" << std::endl; closesocket(listenSocket); WSACleanup(); return 1; } // 监听连接请求 if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) { std::cerr << "Failed to listen on socket" << std::endl; closesocket(listenSocket); WSACleanup(); return 1; } // 接受连接请求 SOCKET clientSocket = accept(listenSocket, NULL, NULL); if (clientSocket == INVALID_SOCKET) { std::cerr << "Failed to accept client connection" << std::endl; closesocket(listenSocket); WSACleanup(); return 1; } // 接收和发送数据 char buffer[1024]; int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0); if (bytesRead > 0) { std::cout << "Received data: " << buffer << std::endl; send(clientSocket, buffer, bytesRead, 0); } // 关闭套接字 closesocket(clientSocket); closesocket(listenSocket); WSACleanup(); return 0; } ``` 上面的例子中,我们使用了Winsock库来创建一个TCP服务器。首先,我们使用WSAStartup函数来初始化Winsock库,然后创建一个套接字listenSocket,并绑定地址和端口。接下来,我们使用listen函数开始监听连接请求,并使用accept函数接受客户端的连接。 一旦连接建立,我们可以使用recv函数接收客户端发送的数据,并使用send函数将数据发送回客户端。最后,我们关闭套接字并调用WSACleanup函数来清理Winsock库的资源。 这只是一个简单的示例,你可以根据自己的需求进行更复杂的多线程Socket通信实现。希望对你有所帮助!如果有任何疑问,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prejudices

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值