【高并发网络通信架构】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 信号,并在该函数中执行退出逻辑。

问题思考

  • 这种实现方式对于几十几百个客户端并发连接可能没问题,但对于成千上万的客户端进行高并发连接,仍采用这种方式来处理显然是不现实的?下一章介绍。。。

猜你喜欢

转载自blog.csdn.net/weixin_43729127/article/details/131591649