多线程自定义协议通信

2022-12-03  本文已影响0人  arkliu

接上篇标题

服务器每接收到一个客户端请求,就开启一个线程,循环读取客户端发来的数据,并向客户端发送数据,当客户端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。
上一篇下一篇

猜你喜欢

热点阅读