• 回射程序改进3——消息的群发


    前文列表:

    简单的回射程序

    回射程序改进1

    回射程序改进2——群发消息(fork)错误的尝试

    目的:

    设计一个C/S程序,客户端发送/接收消息,服务端将从客户端接收到的消息群发给其它已连接套接字,产生

    类似群聊的效果

    相对于之前的改进:

    1.客户端可以在服务端终止后得到通知

    2.客户端使用shutdown()函数处理批量输入产生的问题

    3.服务端使用select()函数管理套接字(单进程),而非使用fork()让每个子进程管理一个套接字(多进程)

    select函数介绍

    程序代码:

    客户端:

      1 #include "net.h"
      2 
      3 int main(int argc, char **argv)
      4 {
      5     int sockfd;
      6 
      7     if (argc != 3)
      8     {
      9         printf("Error arg!
    ");
     10         exit(1);
     11     }
     12 
     13     printf("%s
    ", argv[2]);
     14 
     15     sockfd = tcp_connect(argv[1], SERV_PORT);
     16     printf("Success init, the connected socket is %d
    ", sockfd);
     17     cli_io_select(sockfd, argv[2], stdin);
     18     printf("End...
    ");
     19 
     20     return 0;
     21 }
      4 // 建立一个TCP套接字(IPv4),并与给定主机端口连接,并返回连接后的套接字
      5 int tcp_connect(char *ser_ip, int port)
      6 {
      7     int sockfd;
      8     struct sockaddr_in servaddr;
      9 
     10     if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
     11     {
     12         printf("Error socket!
    ");
     13         exit(1);
     14     }
     15 
     16     bzero(&servaddr, sizeof(servaddr));
     17     servaddr.sin_family = AF_INET;
     18     servaddr.sin_port = htons(port);
     19 
     20     if (inet_pton(AF_INET, ser_ip, &servaddr.sin_addr) <= 0)
     21     {
     22         printf("Error inet_pton!
    ");
     23         exit(1);
     24     }
     25 
     26     if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
     27     {
     28         printf("Error connect!
    ");
     29         exit(1);
     30     }
     31 
     32     return sockfd;
     33 }
     35 // 将一个字符串放到另一个字符串的头部,构造将用户名加到客户发送的消息中
     36 // 不提供对字符串空间大小的检查
     37 char *addStrHead(char *head, char *row)
     38 {
     39     int headLen, rowLen, i;
     40     headLen = strlen(head);
     41     rowLen = strlen(row);
     42 
     43     for (i = headLen + rowLen; i >= 0; i--)
     44     {
     45         if (i > headLen)
     46         {
     47             row[i] = row[i - headLen - 1];
     48         }
     49         else if (i == headLen)
     50         {
     51             row[i] = ':';
     52         }
     53         else
     54         {
     55             row[i] = head[i];
     56         }
     57     }
     58 
     59     row[headLen + rowLen + 1] = '';
     60 
     61     return row;
     62 }
     91 // 使用select的cli_io函数,使得在服务器进程终止后客户可以马上获取通知
     92 void cli_io_select(int sockfd, char *mark, FILE *fp)
     93 {
     94     int maxfdp1, n, stdineof;
     95     fd_set rset;
     96     char sendline[MAXLINE], recvline[MAXLINE];
     97 
     98     FD_ZERO(&rset);
     99 
    100     for ( ; ; )
    101     {
    102         FD_SET(fileno(fp), &rset);
    103         FD_SET(sockfd, &rset);
    104 
    105         // fileno() 函数,将文件流指针转换为文件描述符·
    106         maxfdp1 = max(fileno(fp), sockfd) + 1;
    107 
    108         if (select(maxfdp1, &rset, NULL, NULL, NULL) < 0)
    109         {
    110             printf("Error select!
    ");
    111             exit(1);
    112         }
    113 
    114         if (FD_ISSET(sockfd, &rset))
    115         {
    116             if ( (n = read(sockfd, recvline, MAXLINE)) == 0 )
    117             {
    118                 if (stdineof == 1)
    119                 {
    120                     return;
    121                 }
    122                 else
    123                 {
    124                     printf("Error server terminated prematurely!
    ");
    125                     exit(1);
    126                 }
    127             }
    128 
    129             if (write(fileno(stdout), recvline, n) < 0)
    130             {
    131                 printf("Error write!
    ");
    132                 exit(1);
    133             }
    134         }
    135 
    136         if (FD_ISSET(fileno(fp), &rset))
    137         {
    138             if ( (n = read(fileno(fp), sendline, MAXLINE)) == 0 )
    139             {
    140                 stdineof = 1;
    141 
    142                 if (shutdown(sockfd, SHUT_WR) < 0)
    143                 {
    144                     printf("Error shutdown!
    ");
    145                     exit(1);
    146                 }
    147 
    148                 FD_CLR(fileno(fp), &rset);
    149                 continue;
    150 
    151                 return;
    152             }
    153 
    154             addStrHead(mark, sendline);
    155 
    156             if (write(sockfd, sendline, (n + strlen(mark) + 1)) < 0)
    157             {
    158                 printf("Error write!
    ");
    159    
    160             }
    161         }
    162     }
    163 }
                 exit(1);

    服务端:

      1 #include "net.h"
      2 
      3 int main(int argc, char **argv)
      4 {
      5     int listenfd;
      6     listenfd = tcp_listen(SERV_PORT);
      7     serv_io_select(listenfd);
      8 }
      3 // 创建一个tcp套接字,并在指定端口上监听
      4 int tcp_listen(int port)
      5 {
      6     int listenfd;
      7     struct sockaddr_in servaddr;
      8 
      9     if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
     10     {
     11         printf("Error socket!
    ");
     12         exit(1);
     13     }
     14 
     15     bzero(&servaddr, sizeof(servaddr));
     16     servaddr.sin_family = AF_INET;
     17     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
     18     servaddr.sin_port = htons(port);
     19 
     20     if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
     21     {
     22         printf("Error bind!
    ");
     23         exit(1);
     24     }
     25 
     26     if (listen(listenfd, LISTENQ) < 0)
     27     {
     28         printf("Error listen!
    ");
     29         exit(1);
     30     }
     31 
     32     return listenfd;
     33 }
     88 void sendToOtherSocket(int *client, char *buf, int maxi, int index, int len)
     89 {
     90     int i;
     91 
     92     for (i = 0; i <= maxi; i++)
     93     {
     94         if (i != index)
     95         {
     96             if (write(client[i], buf, len) < 0)
     97             {
     98                 printf("Error write!
    ");
     99                 exit(1);
    100             }
    101         }
    102     }
    103 
    104     return;
    105 }
    106 
    107 // 使用select的serv_io
    108 void serv_io_select(int listenfd)
    109 {
    110     int sockfd, connfd, maxfd, maxi, i, n, nready, client[FD_SETSIZE];
    111     struct sockaddr_in cliaddr;
    112     socklen_t clilen;
    113     fd_set rset, allset;
    114     char buf[MAXLINE];
    115 
    116     maxfd = listenfd;
    117     maxi = -1;
    118 
    119     // 初始化 client 数组,将其所有元素设为 -1,表示这一位未使用
    120     for (i = 0; i < FD_SETSIZE; i++)
    121     {
    122         client[i] = -1;
    123     }
    124 
    125     FD_ZERO(&allset);
    126     FD_SET(listenfd, &allset);
    127 
    128     for ( ; ; )
    129     {
    130         rset = allset; // 使用allset是由于我们使用FD_ISSET来测试fd_set数据类型中的描述符,描述符集内任何与未就绪描述符对应的位返回时均置为1
    131         if( (nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0 )
    132         {
    133             printf("Error select!
    ");
    134             exit(1);
    135         }
    136 
    137         // 有新的连接
    138         if (FD_ISSET(listenfd, &rset))
    139         {
    140             clilen = sizeof(cliaddr);
    141 
    142             if( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0 )
    143             {
    144                 printf("Error accept!
    ");
    145                 exit(1);
    146             }
    147 
    148             // 在client数组中顺序寻找第一个未被使用的元素,用于保存新的connfd
    149             for (i = 0; i < FD_SETSIZE; i++)
    150             {
    151                 if (client[i] < 0)
    152                 {
    153                     client[i] = connfd;
    154                     break;
    155                 }
    156             }
    157 
    158             // client数组中没有可用元素来保存新的connfd
    159             if (i == FD_SETSIZE)
    160             {
    161                 printf("Error too many clients!
    ");
    162                 exit(1);
    163             }
    164 
    165             // 将新的connfd加入allset
    166             FD_SET(connfd, &allset);
    167 
    168             // 重新设置maxfd, 用于select函数的第一个参数
    169             if (connfd > maxfd)
    170             {
    171                 maxfd = connfd;
    172             }
    173 
    174             if (i > maxi)
    175             {
    176                 maxi = i;    // maxi指向client数组的可用client的最大index
    177             }
    178 
    179             // 除了有一个新连接外,没有其他描述符可读
    180             if (--nready <= 0)
    181             {
    182                 printf("continue!
    ");
    183                 continue;
    184             }
    185         }
    186 
    187         for (i = 0; i <= maxi; i++)
    188         {
    189             if ( (sockfd = client[i]) < 0 )
    190             {
    191                 continue;
    192             }
    193 
    194             if (FD_ISSET(sockfd, &rset))
    195             {
    196                 if ( (n = read(sockfd, buf, MAXLINE)) == 0 )
    197                 {
    198                     // 客户端关闭了此套接字
    199                     if (close(sockfd) < 0)
    200                     {
    201                         printf("Error close!
    ");
    202                         exit(1);
    203                     }
    204 
    205                     FD_CLR(sockfd, &allset);
    206                     client[i] = -1;
    207                 }
    208                 else
    209                 {
    210                     if (write(fileno(stdout), buf, n) < 0)
    211                     {
    212                         printf("Error write!
    ");
    213                         exit(1);
    214                     }
    215                     sendToOtherSocket(client, buf, maxi, i, n);
    216                 }
    217 
    218                 if (--nready <= 0)
    219                 {
    220                     break;
    221                 }
    222             }
    223         }
    224     }
    225 }

    运行示例:

    服务端:

    [root@iZwz976helaylvgqok97prZ net]# ./serv 
    continue!
    continue!
    Lin:Hello, Ming
    Ming:Hi, Lin

    客户端:

    [wangml@iZwz976helaylvgqok97prZ net]$ ./cli 120.24.55.49 Lin
    Lin
    Success init, the connected socket is 3
    Hello, Ming
    Ming:Hi, Lin
    
    [wangml@iZwz976helaylvgqok97prZ net]$ ./cli 120.24.55.49 Lin
    Lin
    Success init, the connected socket is 3
    Hello, Ming
    Ming:Hi, Lin
  • 相关阅读:
    杂项收集,包括-发邮件、二维码生成、文件下载、压缩、导出excel
    SQL2008删除大量数据
    优秀程序设计的18大原则
    多线程基础
    SQL金典
    [读书笔记]高效程序员的45个习惯:敏捷开发修炼之道
    Unity 查找资源引用工具
    Unity自动生成各种机型分辨率效果工具
    Unity Editor模式 Invoke()函数 失效
    Unity 特效 粒子 自动播放
  • 原文地址:https://www.cnblogs.com/lnlin/p/9609023.html
Copyright © 2020-2023  润新知