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

Posted lnlin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回射程序改进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 }

运行示例:

服务端:

[[email protected] net]# ./serv 
continue!
continue!
Lin:Hello, Ming
Ming:Hi, Lin

客户端:

[[email protected] net]$ ./cli 120.24.55.49 Lin
Lin
Success init, the connected socket is 3
Hello, Ming
Ming:Hi, Lin

[[email protected] net]$ ./cli 120.24.55.49 Lin
Lin
Success init, the connected socket is 3
Hello, Ming
Ming:Hi, Lin

 

以上是关于回射程序改进3——消息的群发的主要内容,如果未能解决你的问题,请参考以下文章

10.2 消息队列实现的回射服务器

微信推送信息功能

夺命雷公狗---微信开发30----微信群发消息3

.net微信公众号开发——群发消息

UNIX网络编程笔记—TCP客户/服务器程序示例

微信小程序 实现模板消息群发、发送给指定用户