【高并发网络通信架构】2.引入多线程实现多客户端连接的tcp服务端

目录

一,往期文章

二,代码实现

关键代码

完整代码

运行效果


一,往期文章

【高并发网络通信架构】1.Linux下实现单客户连接的tcp服务端

二,代码实现

关键代码

  • 因为accept是阻塞等待客户端连接,当客户端连接成功后才会执行accept后面的代码,所以为实现多个客户端连接,第一步是将accept放在master循环里。
  • recv是阻塞型函数,如果不将recv的相关代码放到线程里去,会导致客户端无法与服务端通信。​​

完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <pthread.h>

#define BUFFER_LENGTH   1024

void* handleClient(void *arg)
{
    int cfd = *((int*)arg);
    while(1)    //一直循环是确保一直和服务端通信,并且recv是阻塞函数
    {
        char buffer[BUFFER_LENGTH] = {0};
        int recvLen = recv(cfd,buffer,BUFFER_LENGTH,0);
        if(recvLen < 0){
            printf("recv error code: %d codeInfo: %s\n",errno,strerror(errno));
            break;
        }else if(recvLen == 0){
            //客户端断开连接
            printf("client fd %d disconnect\n",cfd);
            close(cfd);     //关闭客户端文件描述符,释放资源
            break;
        }else{
            printf("recv fd %d data: %s\n",cfd,buffer);
            send(cfd,buffer,recvLen,0);    //将接收到的数据重新发给客户端
        }
    }

    //退出线程
    pthread_exit(NULL);
    return NULL;
}

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in serverAddr;
    memset(&serverAddr,0,sizeof(struct sockaddr_in));
    serverAddr.sin_family = AF_INET;  //ipv4
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    serverAddr.sin_port = htons(port);
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,SOMAXCONN))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);
    if(sfd == -1)exit(0);

    pthread_t thread_id;
    struct sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof(struct sockaddr_in);

    while (1)//每个服务器至少都有一个主循环
    {
        //接受新的连接请求
        int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&clientAddrLen);

        //创建一个新的线程来处理客户端连接
        int flag = pthread_create(&thread_id,NULL,handleClient,(void*)&cfd);
        if(0 == flag){
            printf("client fd: %d ,thread ID: %ld start\n",cfd,thread_id);
        }else{
            perror("pthread_create");
            break;
        }

        //分离线程,让线程自行处理结束
        pthread_detach(thread_id);
    }

    close(sfd);     //关闭服务端文件描述符,释放资源
    printf("server fd %d close\n",sfd);
    
    return 0;
}

运行效果

  • 编译时记得加 -lpthread 连接到库,运行如下。

存在问题

  1. 在程序运行中,不进行客户端连接操作直接按下 Ctrl+Z 会发送一个信号(SIGTSTP)给前台运行的进程,会导致该服务端程序被挂起 (Suspended),导致我下一次在运行时会打印如下错误,说明上次运行并没有释放服务端套接字。(没有客户端连接的情况下
  2. 因为资源没有释放,导致每次要手动去杀死该挂起的进程,就很麻烦,如下。
  3. 最后执行 kill -9 394407 杀死挂起的进程。

解决方法

  • 最终的原因是程序如何退出的问题,要使程序直接退出,你可以注册一个信号处理函数来捕获 SIGTSTP 信号,并在该函数中执行退出逻辑。

问题思考

  • 这种实现方式对于几十几百个客户端并发连接可能没问题,但对于成千上万的客户端进行高并发连接,仍采用这种方式来处理显然是不现实的?下一章介绍。。。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的基于C++11标准的多线程TCP服务端客户端代码示例: 服务端代码: ```c++ #include <iostream> #include <thread> #include <mutex> #include <vector> #include <cstring> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> const int MAX_CONNECTIONS = 10; // 最大连接数 std::mutex g_mutex; // 互斥量 std::vector<int> g_connections; // 客户端连接列表 void client_handler(int client_socket) { char buffer[1024]; while (true) { memset(buffer, 0, sizeof(buffer)); int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); if (bytes_received <= 0) { // 客户端断开连接 break; } // 处理客户端请求 // ... // 发送响应 send(client_socket, buffer, bytes_received, 0); } // 关闭客户端连接 close(client_socket); // 删除客户端连接 std::lock_guard<std::mutex> lock(g_mutex); auto it = std::find(g_connections.begin(), g_connections.end(), client_socket); if (it != g_connections.end()) { g_connections.erase(it); } } int main() { int server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { std::cerr << "Failed to create server socket" << std::endl; return 1; } // 绑定监听地址和端口 sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = INADDR_ANY; server_address.sin_port = htons(12345); if (bind(server_socket, (sockaddr*)&server_address, sizeof(server_address)) == -1) { std::cerr << "Failed to bind server socket" << std::endl; return 1; } // 开始监听 if (listen(server_socket, MAX_CONNECTIONS) == -1) { std::cerr << "Failed to listen on server socket" << std::endl; return 1; } std::cout << "Server started" << std::endl; while (true) { // 等待客户端连接 sockaddr_in client_address; socklen_t client_address_size = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_size); if (client_socket == -1) { std::cerr << "Failed to accept client connection" << std::endl; continue; } // 添加客户端连接 std::lock_guard<std::mutex> lock(g_mutex); if (g_connections.size() >= MAX_CONNECTIONS) { std::cerr << "Max connections reached" << std::endl; close(client_socket); continue; } g_connections.push_back(client_socket); // 处理客户端请求 std::thread t(client_handler, client_socket); t.detach(); } // 关闭服务器 close(server_socket); return 0; } ``` 客户端代码: ```c++ #include <iostream> #include <cstring> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> int main() { int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket == -1) { std::cerr << "Failed to create client socket" << std::endl; return 1; } // 连接服务器 sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); server_address.sin_port = htons(12345); if (connect(client_socket, (sockaddr*)&server_address, sizeof(server_address)) == -1) { std::cerr << "Failed to connect to server" << std::endl; return 1; } std::cout << "Connected to server" << std::endl; char buffer[1024]; while (true) { // 发送请求 std::string request; std::cout << "Enter request: "; std::getline(std::cin, request); if (request.empty()) { break; } send(client_socket, request.c_str(), request.length(), 0); // 接收响应 memset(buffer, 0, sizeof(buffer)); int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); if (bytes_received <= 0) { break; } // 处理响应 std::cout << "Response: " << buffer << std::endl; } // 关闭客户端 close(client_socket); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

拉伊卜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值