linux回声服务器系列(5)_多线程并发

在网络编程中,回声服务器(echo server)是一个典型的例子。
实现非并发服务器很容易,即迭代服务器;
实现并发服务器需要采用IO复用或并发编程技术。
在这个系列中将分别采用简易迭代、select函数、epoll函数(水平触发和边沿触发)、多进程、多线程五种方式来实现回声服务器。

传送门:
linux回声服务器系列(1)_简单迭代版本
linux回声服务器系列(2)_select实现
linux回声服务器系列(3)_epoll实现
linux回声服务器系列(4)_多进程版本
linux回声服务器系列(5)_多线程并发

多线程优势

上一篇讲到的,采用多进程并发的服务器,会存在以下几个问题:

  • fork的代价较为昂贵;
  • fork之后,父子进程之间需要借助IPC(进程间通信)通信,比较麻烦。

采用线程可以解决上述问题,线程更轻量,创建速度也更快。

多线程并发可以同时处理多个客户端的请求,对每一个连接请求均开启一个线程来处理。

服务端代码

主函数建立监听套接字


int main(int argc, char * argv[])
{
    int sockfd, connfd;
    char sendBuf[BUFMAXLEN] = {0};
    char recvBuf[BUFMAXLEN] = {0};
    struct sockaddr_in server_addr, client_addr;

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    
    if(listen(sockfd, SOMAXCONN) == -1)
    {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }
    
    std::cout<<"server start... "<<std::endl;

    echo_thread(sockfd);

    close(sockfd);

    return 0;
}

echo_thread做具体的多线程处理:

void echo_thread(int listenfd)
{
    pid_t * pargs;
    pthread_t tid;
    struct sockaddr_in server_addr, client_addr;
    socklen_t sock_len = sizeof(sockaddr_in);
    
    while(1)
    {
        pargs = (pid_t *)malloc(sizeof(pid_t));
        if((*pargs = accept(listenfd,  (struct sockaddr *)(&client_addr), &sock_len)) < 0)
        {
            perror("accept failed");
            exit(EXIT_FAILURE);
        }
        std::cout<<"accept client ip: "<<inet_ntoa(client_addr.sin_addr)<<" on socket: "<<*pargs<<std::endl;
        pthread_create(&tid, NULL, thread_echo_process, (void *)pargs);
        pthread_detach(tid);
    }
}

echo_thread中循环监听listenfd套接字的请求状态,一旦有连接请求则新建一个回声处理线程。因为并不需要等待线程结束,因此将线程分离。

这里要注意向线程传参数的方法:

  1. 传指针,不可行,会出现多线程访问共享变量的冲突问题;(具体见UNP第三版540页)
  2. 直接传值,可行,但可能造成数据精度丢失;
  3. 每次malloc一个指针传入,最优;

我们采用第三种方式,并在线程处理函数中将参数指针free掉:

void * thread_echo_process(void * args)
{
    pid_t connfd;
    int recv_bytes;

    connfd = *((pid_t *)args);
    free(args);

    while(recv_bytes = read(connfd, recv_buf, BUFMAXLEN))
    {
        std::cout<<"recbuf: "<<recv_buf<<std::endl;
        write(connfd, recv_buf, recv_bytes);
        memset(recv_buf, 0, BUFMAXLEN);
    }
    std::cout<<"socket: "<<connfd<<" closed"<<std::endl;
    close(connfd);

    pthread_exit(NULL);    
}

在线程处理函数中做回声处理,最后用pthread_exit退出。

你可能感兴趣的:(linux网络编程)