美文网首页
多线程自定义协议通信

多线程自定义协议通信

作者: arkliu | 来源:发表于2022-12-03 08:17 被阅读0次

    接上篇标题

    服务器每接收到一个客户端请求,就开启一个线程,循环读取客户端发来的数据,并向客户端发送数据,当客户端close或者关闭练连接后,打印此次关闭的客户端的ip地址和端口号。

    服务器端

    #include <arpa/inet.h>
    #include <netdb.h>
    #include<sys/socket.h>
    #include<unistd.h>
    #include<string.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<memory.h>
    #include<signal.h>
    #include<errno.h>
    #include<time.h>
    #include <sys/types.h>  
    #include <sys/wait.h>
    #include<pthread.h>
    #include "msg.h"
    
    int sockfd;
    
    void sig_handler(int signo) {
        if (signo == SIGINT){
            printf("server close\n");
            /*步骤6:关闭服务器端的socket*/
            close(sockfd);
            exit(1);
        }
    }
    
    void do_service(int fd) {
        /*和客户端进行读写操作(双向通信)*/
        char buf[512];
        while (1){
            memset(buf, 0, sizeof(buf));
            size_t size;  
            if((size = read_msg(fd, buf, sizeof(buf))) < 0) {
                perror("protocal error");
                break;
            } else if(size == 0) { //客户端已经断开连接,读取到的size大小为0
                break;
            } else {
                printf("%s\n", buf);
                if(write_msg(fd, buf, sizeof(buf)) < 0) {
                    if(errno == EPIPE) { //客户端读端关闭
                        break;
                    }
                    perror("protocal error");
                }
            }
        }
    }
    
    void out_fd(int fd) {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        if(getpeername(fd, (struct sockaddr*)&addr, &len) < 0) {
            perror("getpeername error");
            return;
        }
        char ip[16];
        memset(ip, 0, sizeof(ip));
        int port = ntohs(addr.sin_port);
        inet_ntop(AF_INET,
                    &addr.sin_addr.s_addr, ip, sizeof(ip));
        printf("%16s(%5d) closed\n", ip, port);
    }
    
    void* th_fn(void *arg) {
        int fd = *((int *)arg);
        do_service(fd);
        out_fd(fd);
        close(fd);
        return (void*)0;
    }
    
    int main(int argc, char *argv[]) {
        if (argc < 2){
            printf("usage:%s #port\n",argv[0]);
            exit(1);
        }
        
        if (signal(SIGINT, sig_handler) == SIG_ERR){
            perror("signal sigint error");
            exit(1);
        }
    
        /*步骤1:创建socket套接字*/
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        /*步骤2:调用bind将socket和地址绑定(包括ip,port)*/
        struct sockaddr_in serveraddr;
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET; //IPV4
        serveraddr.sin_port = htons(atoi(argv[1]));
        serveraddr.sin_addr.s_addr = INADDR_ANY;
        if(bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
            perror("bind error");
            exit(1);
        }
        /*步骤3:调用listen函数启动监听(指定port监听)*/
        if(listen(sockfd,10) < 0) {
            perror("listen error");
            exit(1);
        }
    
        /*步骤4:调用accept函数从队列中获得一个客户端的请求连接
           注意:若没有客户端连接,调用accept函数将阻塞,直到货到衣蛾客户端连接
        */
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
    
        // 设置线程属性分离
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
        while (1){
            // 主控线程负责调用accept去获取客户端的连接
            int fd = accept(sockfd, NULL, NULL);
            if(fd < 0) {
                perror("accept error");
                continue;
            }
    
            /**
             * 步骤5:启动子线程调用IO函数(read/write)和连接的客户端进行双向的通信
            */
            pthread_t th;
            int err;
            if((err = pthread_create(&th, &attr, th_fn, (void *)&fd)) != 0) {
                perror("pthread create error");
            }    
            pthread_attr_destroy(&attr);
        }
        return 0;
    }
    

    编译服务器端:

    gcc -o bin/echo_server_th -Iinclude obj/msg.o src/echo_server_mutil_th.c -lpthread
    

    *测试

    1. 运行服务器
      ./bin/echo_server_th 8888
    2. 运行多个客户端
      ./bin/echo_client 127.0.0.1 8888
    3. 多个客户端同时向服务器发送数据,关闭其中一个客户端,不影响其他客户端,同时服务器端会打印此次关闭的客户端的ip和port。

    相关文章

      网友评论

          本文标题:多线程自定义协议通信

          本文链接:https://www.haomeiwen.com/subject/tnfsfdtx.html