前文列表:
目的:
设计一个C/S程序,客户端发送/接收消息,服务端将从客户端接收到的消息群发给其它已连接套接字,产生
类似群聊的效果
相对于之前的改进:
1.客户端可以在服务端终止后得到通知
2.客户端使用shutdown()函数处理批量输入产生的问题
3.服务端使用select()函数管理套接字(单进程),而非使用fork()让每个子进程管理一个套接字(多进程)
程序代码:
客户端:
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!\n"); 10 exit(1); 11 } 12 13 printf("%s\n", argv[2]); 14 15 sockfd = tcp_connect(argv[1], SERV_PORT); 16 printf("Success init, the connected socket is %d\n", sockfd); 17 cli_io_select(sockfd, argv[2], stdin); 18 printf("End...\n"); 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!\n"); 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!\n"); 23 exit(1); 24 } 25 26 if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) 27 { 28 printf("Error connect!\n"); 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] = '\0'; 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!\n");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 else123 {124 printf("Error server terminated prematurely!\n");125 exit(1);126 }127 }128 129 if (write(fileno(stdout), recvline, n) < 0)130 {131 printf("Error write!\n");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!\n");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!\n");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!\n"); 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!\n"); 23 exit(1); 24 } 25 26 if (listen(listenfd, LISTENQ) < 0) 27 { 28 printf("Error listen!\n"); 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!\n"); 99 exit(1);100 }101 }102 }103 104 return;105 }106 107 // 使用select的serv_io108 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数据类型中的描述符,描述符集内任何与未就绪描述符对应的位返回时均置为1131 if( (nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0 )132 {133 printf("Error select!\n");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!\n");145 exit(1);146 }147 148 // 在client数组中顺序寻找第一个未被使用的元素,用于保存新的connfd149 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数组中没有可用元素来保存新的connfd159 if (i == FD_SETSIZE)160 {161 printf("Error too many clients!\n");162 exit(1);163 }164 165 // 将新的connfd加入allset166 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的最大index177 }178 179 // 除了有一个新连接外,没有其他描述符可读180 if (--nready <= 0)181 {182 printf("continue!\n");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!\n");202 exit(1);203 }204 205 FD_CLR(sockfd, &allset);206 client[i] = -1;207 }208 else209 {210 if (write(fileno(stdout), buf, n) < 0)211 {212 printf("Error write!\n");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, MingMing:Hi, Lin
客户端:
[wangml@iZwz976helaylvgqok97prZ net]$ ./cli 120.24.55.49 LinLinSuccess init, the connected socket is 3Hello, MingMing:Hi, Lin[wangml@iZwz976helaylvgqok97prZ net]$ ./cli 120.24.55.49 LinLinSuccess init, the connected socket is 3Hello, MingMing:Hi, Lin