zoukankan      html  css  js  c++  java
  • 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 小潘
    
  • 相关阅读:
    UVA 11925 Generating Permutations 生成排列 (序列)
    UVA 1611 Crane 起重机 (子问题)
    UVA 11572 Unique snowflakes (滑窗)
    UVA 177 PaperFolding 折纸痕 (分形,递归)
    UVA 11491 Erasing and Winning 奖品的价值 (贪心)
    UVA1610 PartyGame 聚会游戏(细节题)
    UVA 1149 Bin Packing 装箱(贪心)
    topcpder SRM 664 div2 A,B,C BearCheats , BearPlays equalPiles , BearSorts (映射)
    UVA 1442 Cave 洞穴 (贪心+扫描)
    UVA 1609 Foul Play 不公平竞赛 (构(luan)造(gao)+递归)
  • 原文地址:https://www.cnblogs.com/wsl540/p/14745945.html
Copyright © 2011-2022 走看看