• 18.5.2 多线程并发服务器端的实现


    实现多个客户端之间可以交换信息的简单聊天程序

    先上结果:

    服务端代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <string.h>
    #include <pthread.h>
    
    /*
    服务端示例中,需要掌握临界区的构成,访问全局变量 clnt_cnt 和数组 clnt_sock_array 的代码将构
    成临界区,添加和删除客户端时,变量 clnt_cnt 和数组 clnt_sock_array 将同时发生变化。
    因此下列情形会导致数据不一致,从而引发错误:
    1. 线程 A 从数组 clnt_sock_array 中删除套接字信息,同时线程 B 读取 clnt_cnt 变量
    2. 线程 A 读取变量 clnt_cnt ,同时线程 B 将套接字信息添加到 clnt_sock_array 数组
    */
    
    #define BUF_SIZE 256
    #define MAX_CLEN 256
    
    int clnt_cnt = 0;
    int clnt_sock_array[MAX_CLEN];
    pthread_mutex_t mutex;
    
    void *handle_clnt(void *arg);
    void error_handling(char *msg);
    void send_msg(char *msg, int len);
    
    int main(int argc, char *argv[])
    {
        int clnt_sock = 0;
        int serv_sock = 0;
        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size = 0;
        pthread_t pid;
    
        if (argc != 2)
        {
            printf("Usage: %s <port>
    ", argv[0]);
            exit(1);
        }
        //创建互斥锁
        pthread_mutex_init(&mutex, NULL);
        //创建套接字
        serv_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (-1 == serv_sock)
        {
            error_handling("fail to socket!");
        }
        //初始化IP地址
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(atoi(argv[1]));
    
        if (-1 == bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
        {
            error_handling("fail to bind");
        }
    
        if (-1 == listen(serv_sock, 5))
        {
            error_handling("fail to bind");
        }
    
        while (1)
        {
            clnt_addr_size = sizeof(clnt_addr);
            clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
    
            //上锁
            pthread_mutex_lock(&mutex);
            //将新客户端存入数组
            clnt_sock_array[clnt_cnt++] = clnt_sock;
            //解锁
            pthread_mutex_unlock(&mutex);
    
            //创建线程为新客户端服务,并且把clnt_sock作为参数
            pthread_create(&pid, NULL, handle_clnt, &clnt_sock);
            //引导线程销毁,不会阻塞
            pthread_detach(pid);
            //打印客户端的ip地址
            printf("Connected client ID:%s
    ", inet_ntoa(clnt_addr.sin_addr));
        }
        close(serv_sock);
        return 0;
    }
    
    void *handle_clnt(void *arg)
    {
        int i = 0;
        int str_len = 0;
        int clnt_sock = *((int *)arg);
        char buf_msg[BUF_SIZE];
    
        while ((str_len = read(clnt_sock, buf_msg, sizeof(buf_msg))) != 0)
        {
            send_msg(buf_msg, str_len);
        }
        //接受到的信息为0,说明当前客户端已经断开连接
        pthread_mutex_lock(&mutex);
        //删除没有连接的客户端
        for (i=0; i<clnt_cnt; i++)
        {
            if (clnt_sock == clnt_sock_array[i])
            {
                while (i++ < clnt_cnt - 1)
                {
                    clnt_sock_array[i] = clnt_sock_array[i + 1];
                }
                break;
            }
        }
        clnt_cnt--;
        pthread_mutex_unlock(&mutex);
        close(clnt_sock);
        return NULL;
    }
    
    //向所有客户端全发送信息
    void send_msg(char *msg, int len)
    {
        int i;
        pthread_mutex_lock(&mutex);
        for (i=0; i<clnt_cnt; i++)
        {
            write(clnt_sock_array[i], msg, len);
        }
        pthread_mutex_unlock(&mutex);
    }
    
    void error_handling(char *msg)
    {
        fputs(msg, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    

    客户端代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <pthread.h>
    
    #define BUF_SIZE 256
    #define NAME_SIZE 20
    
    void *recv_msg(void *arg);
    void *send_msg(void *arg);
    void error_handling(char *msg);
    
    char name[NAME_SIZE] = "[default]";
    char msg[BUF_SIZE];
    
    int main(int argc, char *argv[])
    {
        int sock;
        struct sockaddr_in serv_addr;
        pthread_t send_thread;
        pthread_t recv_thread;
        void *thread_return;
    
        if (argc != 4)
        {
            printf("Usage : %s <IP> <port> <name>
    ", argv[0]);
            exit(1);
        }
    
        sprintf(name, "[%s]", argv[3]);
        sock = socket(AF_INET, SOCK_STREAM, 0);
        if (-1 == sock)
        {
            error_handling("fail to socket");
        }
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(atoi(argv[2]));
        serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    
        if (-1 == connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
        {
            error_handling("fail to connect");
        }
    
        //创建发送消息线程
        pthread_create(&send_thread, NULL, send_msg, (void *)&sock);
        //创建接受消息线程
        pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock);
    
        //pthread_join()的作用可以这样理解:主线程等待子线程的终止
        pthread_join(send_thread, &thread_return);
        pthread_join(recv_thread, &thread_return);
    
        close(sock);
        return 0;
    }
    
    void *send_msg(void *arg)
    {
        int sock = *((int *)arg);
        char name_msg[NAME_SIZE + BUF_SIZE];
    
        while (1)
        {
            fgets(msg, BUF_SIZE, stdin);
            if (!strcmp(msg, "q
    ") || !strcmp(msg, "Q
    "))
            {
                close(sock);
                exit(0);
            }
            sprintf(name_msg, "%s %s", name, msg);
            write(sock, name_msg, strlen(name_msg));
        }
        return NULL;
    }
    
    void *recv_msg(void *arg)
    {
        int sock = *((int *)arg);
        char name_msg[NAME_SIZE + BUF_SIZE];
        int str_len;
    
        while (1)
        {
            str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE -1);
            name_msg[str_len] = 0;
            fputs(name_msg, stdout);
        }
        return NULL;
    }
    
    void error_handling(char *msg)
    {
        fputs(msg, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    

    编译运行:

    gcc chat_server.c -D_REENTRANT -o cserv -lpthread
    gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread
    ./cserv 8180
    ./cclnt 127.0.0.1 8180 大郎
    ./cclnt 127.0.0.1 8180 小潘
    
  • 相关阅读:
    本站将进行有关《大道至简》的讨论~
    启动一个Rich Web Client的项目:Qomo OpenProject
    JavaScript面向对象的支持(1)
    从基础开始:Qomo OpenProject中的一些关键词(2)
    代码规范性与品质问题~
    任何想法的致命问题,并不在于没有实施条件,而在于根本不被实施
    再谈borland与MS对BUG的不同态度~
    善于使用资源的程序员才是好程序员
    伴随开发人员成长的问题:工程重要,还是算法重要?细节重要,还是架构重要?
    JavaScript面向对象的支持(2)
  • 原文地址:https://www.cnblogs.com/wsl540/p/14745945.html
Copyright © 2020-2023  润新知