[关闭]
@SovietPower 2022-05-18T17:26:00.000000Z 字数 11690 阅读 1361

计算机网络 实验7 socket编程

CN



https://zybuluo.com/SovietPower/note/2013258


运行

服务器

  1. gcc server.c -o server
  2. ./server

客户端

  1. gcc client.c -o client
  2. ./client server_host client_host client_port

参数分别表示:服务器地址、客户端使用的地址、端口。后两项用于建立服务器客户端的全双工通信。
服务器端口默认为7700。

运行后,服务器即可接收客户端消息,并在连接时提示当前连接的客户端的socket列表。
在服务器端任意时刻,输入client_socket info,可发送消息info给指定的、socket为client_socket的已连接客户端。

客户端连续输入两个空行,则与该客户端连接终止。


客户端以两个换行符作为结束标志

就双方都判断一下,是否连续收到两个空行。
不确定这里服务器是否需要输出这两个空行,如果不需要,再判断一下即可。


同时接受多个客户端的信息,并逐个打印

这个用多线程是错的,不能保证线程是依次完成输出或单个线程的输出是原子操作。

容易想到,只需要:当前需要发送的客户端就发,不发的加跳过,依次枚举一遍,然后检查一下是否有新的客户端连接,这样死循环。
但有个问题是,接受请求要用acceptaccept是阻塞的。

accept变为非阻塞
man accept,可以看到:

  1. ERRORS
  2. EAGAIN or EWOULDBLOCK
  3. The socket is marked nonblocking and no connections are present to be ac
  4. cepted. POSIX.1-2001 and POSIX.1-2008 allow either error to be returned for
  5. this case, and do not require these constants to have the same value, so a
  6. portable application should check for both possibilities.

所以accept是可以变为非阻塞的,只需要将socket设为nonblocking
man socket,可以看到:

  1. The socket has the indicated type, which specifies the communication semantics. Currently defined types are:
  2. SOCK_STREAM Provides sequenced, reliable, two-way, connection-based
  3. byte streams. An out-of-band data transmission mecha
  4. nism may be supported.
  5. ...
  6. Some socket types may not be implemented by all protocol families.
  7. Since Linux 2.6.27, the type argument serves a second purpose: in addi
  8. tion to specifying a socket type, it may include the bitwise OR of any
  9. of the following values, to modify the behavior of socket():
  10. SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the open file
  11. description (see open(2)) referred to by the new file
  12. descriptor. Using this flag saves extra calls to fc
  13. ntl(2) to achieve the same result.

所以我们将SOCK_NONBLOCKSOCK_STREAM一样传入socket()就可以了,也就是
socket(PF_INET, SOCK_STREAM, 0)
改为socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0),即将accept变为了非阻塞。

然后要区分真正的错误和非阻塞的错误。
errno-base.herrno.h(用errno.h一层层找,在比较深的位置),有:

  1. // errno-base.h
  2. #define EAGAIN 11 /* Try again */
  3. // errno.h
  4. #define EWOULDBLOCK EAGAIN /* Operation would block */

perror函数就会根据errno,输出这里面的注释)

当然改完后自己输出一下errno也可得到:

  1. errno: 11
  2. simplex-talk: accept: Resource temporarily unavailable

只需要在accept出错时,判断errno类型是不是EAGAIN(或EWOULDBLOCK)即可。

  1. if ((new_s = accept(s, (struct sockaddr *)&sin, &len)) < 0)
  2. {
  3. if (errno != EAGAIN && errno != EWOULDBLOCK)
  4. {
  5. perror("server: accept");
  6. exit(1);
  7. }
  8. }
  9. else
  10. 保存新的socket

recv变为非阻塞
写完之后可以发现,当有多个进程同时发消息时,是轮流发的,在发送之前,不能成功发送任何消息,即使很久都不发送。发送后,这期间发送的所有消息中的最后一条才会展示出来(以前的没有缓存所以就没了?)。
因为recv也是阻塞的。

man recv可以发现,同样可设置socket为SOCK_NONBLOCK,不过是发送方也就是要设置客户端的socket。
改完后发现,客户端有错误:Operation now in progress,即:

  1. #define EINPROGRESS 115 /* Operation now in progress */

man connect,可以发现:

  1. EINPROGRESS
  2. The socket is nonblocking and the connection cannot be completed
  3. immediately. (UNIX domain sockets failed with EAGAIN instead.)
  4. It is possible to select(2) or poll(2) for completion by select
  5. ing the socket for writing. After select(2) indicates writabil
  6. ity, use getsockopt(2) to read the SO_ERROR option at level
  7. SOL_SOCKET to determine whether connect() completed successfully
  8. (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the
  9. usual error codes listed here, explaining the reason for the
  10. failure).

大概是变为非阻塞后,connection需一段时间才能完成,要use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET检查连接是否完成。
然后再man getsockopt,发现太多了,就先忽略这个错误,假设可以很快完成。
然后再run,发现recv还是阻塞。
所以在socket这设置,对recv似乎是没有影响的。

man recv还有,The flags argument是可以设置MSG_DONTWAIT的,表示不阻塞。
然后通过判断recv的错误码是不是EAGAIN(或EWOULDBLOCK),就可以非阻塞接受多个客户端的信息了。

  1. int len = recv(s_now, b, MAX_LINE, MSG_DONTWAIT);
  2. if (len > 0)
  3. {
  4. printf("from client:%d len:%d\n", s_now, len);
  5. fputs(b, stdout);
  6. }
  7. else if (len == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
  8. {
  9. perror("server recv");
  10. exit(1);
  11. }

支持在本地以及不同机器上运行

就是指定对应的参数?


连接错误时提示错误信息

client建立连接时,用个while(1),直到成功连接时才退出,否则一直读入。
当时没这么做,代码没写。


支持不少于20KB的长文本消息

设置客户端和服务器的buf足够大即可?
以及要注意,服务器对每个连接的客户端,都需有一个独立的buf。

用文件作为客户端输入的话,服务器读不到两个换行,不能主动终止,应该不是什么问题。(其实用户这样也没直接输入两个换行?)


全双工通信

fgets变为非阻塞
首先要实现非阻塞的读入。
man fgets中只有,unlocking的信息在unlocked_stdio中。

https://www.cnblogs.com/khler/archive/2010/12/20/1911701.html

open时,是可以直接设置NONBLOCK flag的。
对于stdin,可以通过fcntl改变其flag。
然后就可以正常用read从stdin读了。同样是判断errno是不是EAGAIN

  1. void InitStdin()
  2. {
  3. int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
  4. if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) < 0)
  5. {
  6. perror("fcntl");
  7. exit(1);
  8. }
  9. }
  10. int len = read(STDIN_FILENO, callback, MAX_LINE);
  11. if (len > 0)
  12. printf("callback: %s len:%d\n", callback, len);
  13. else if (len < 0)
  14. {
  15. if (errno != EAGAIN)
  16. {
  17. perror("read stdin");
  18. exit(1);
  19. }
  20. }

这样服务器的非阻塞读入和输出,就做完了。再复制给客户端一个。

然后要实现双工,需要混合一下服务器和客户端代码,也就是双方都需要listen, connect, send, recv

流程:
连接建立:server、client执行listen,client执行connect,server执行accept,client发送自己的地址和端口用于server构造地址,然后执行accept,server执行connect。
然后双方正常自由发送信息。
断开:当client发送两个换行符时,双方都执行close。结束。

listen、accept的socket,和connect、send的socket,不能使用同一个。
connect的一次只能connect一个。注意connect用完一定要close

具体内容太麻烦了,不写文字了。


代码

客户端

  1. // by SovietPower
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <strings.h>
  5. #include <stdio.h>
  6. #include <sys/types.h>
  7. #include <sys/socket.h>
  8. #include <netinet/in.h>
  9. #include <netdb.h>
  10. #include <errno.h>
  11. #include <fcntl.h>
  12. #include <unistd.h> // 定义了STDIN_FILENO
  13. #define SERVER_PORT 7700
  14. #define MAX_LINE 30000 // 30KB
  15. extern int errno;
  16. char buf[MAX_LINE];
  17. char callback[MAX_LINE];
  18. void InitStdin()
  19. {
  20. int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
  21. if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) < 0)
  22. {
  23. perror("fcntl");
  24. exit(1);
  25. }
  26. }
  27. int main(int argc, char * argv[])
  28. {
  29. InitStdin();
  30. char *host, *chost, *cport;
  31. int client, server, connection;
  32. if (argc==4)
  33. host = argv[1], chost = argv[2], cport = argv[3];
  34. else
  35. {
  36. fprintf(stderr, "usage: client server_host client_host client_port\n");
  37. exit(1);
  38. }
  39. // todo (没做)输入错误的host反馈,但就是整个while直到读到正确的结果
  40. // GetAddr();
  41. /* translate host name into peer’s IP address */
  42. struct hostent *hp = gethostbyname(host);
  43. if (!hp)
  44. {
  45. fprintf(stderr, "client: unknown host: %s.\n", host);
  46. exit(1);
  47. }
  48. // 主动连接到server
  49. if ((connection = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  50. {
  51. perror("client: socket");
  52. exit(1);
  53. }
  54. // 监听server的请求
  55. struct sockaddr_in cin;
  56. bzero((char *)&cin, sizeof(cin));
  57. cin.sin_family = AF_INET;
  58. cin.sin_addr.s_addr = INADDR_ANY;
  59. cin.sin_port = htons(atoi(cport));
  60. if ((client = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  61. {
  62. perror("client: socket");
  63. exit(1);
  64. }
  65. // 监听socket必须绑定sockaddr
  66. if ((bind(client, (struct sockaddr *)&cin, sizeof(cin))) < 0)
  67. {
  68. perror("client: bind");
  69. exit(1);
  70. }
  71. // server sockaddr
  72. struct sockaddr_in sin;
  73. bzero((char *)&sin, sizeof(sin));
  74. sin.sin_family = AF_INET;
  75. bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
  76. sin.sin_port = htons(SERVER_PORT);
  77. // 连接到server
  78. if (connect(connection, (struct sockaddr *)&sin, sizeof(sin)) < 0)
  79. {
  80. // if (errno != EINPROGRESS)
  81. {
  82. close(connection);
  83. perror("client: connect");
  84. exit(1);
  85. }
  86. }
  87. listen(client, 2);
  88. fprintf(stderr, "client: %d listening.\n",client);
  89. // client发送自己的地址和端口用于server构造地址
  90. int lenH = strlen(chost), lenP = strlen(cport);
  91. for (int i=0; i<lenH; ++i) callback[i] = chost[i];
  92. for (int i=0; i<lenP; ++i) callback[i+lenH+1] = cport[i];
  93. callback[lenH]=' ', callback[lenH+lenP+1]='\0';
  94. fprintf(stderr, "Sending connection information: %s\n", callback);
  95. if (send(connection, callback, lenH+lenP+2, 0) < 0) // 注意发送长度要多1!发送'\0'
  96. {
  97. perror("client: first send");
  98. exit(1);
  99. }
  100. // 接收server请求
  101. int addr_len;
  102. fprintf(stderr, "Wating to be connected.");
  103. if ((server = accept(client, (struct sockaddr *)&cin, &addr_len)) < 0)
  104. {
  105. // 这里就不阻塞了
  106. // if (errno != EAGAIN && errno != EWOULDBLOCK)
  107. {
  108. perror("client: accept");
  109. exit(1);
  110. }
  111. }
  112. fprintf(stderr, "Build with server %d\n", server);
  113. int enter = 0, len;
  114. memset(callback, 0, sizeof callback); // clear!
  115. while(1)
  116. {
  117. // 服务器发送的信息
  118. len = recv(server, buf, MAX_LINE, MSG_DONTWAIT);
  119. if (len > 0)
  120. {
  121. fprintf(stderr, "from server:%d len:%d\n", server, len);
  122. fputs(buf, stdout);
  123. }
  124. else if (len == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
  125. {
  126. perror("client: recv");
  127. exit(1);
  128. }
  129. // 发送给服务器
  130. len = read(STDIN_FILENO, callback, MAX_LINE);
  131. if (len > 0)
  132. {
  133. callback[len] = '\0'; // !
  134. // fprintf(stderr, "callback: %s len:%d\n", callback, len);
  135. len = send(connection, callback, len+1, 0);
  136. if (len==2 && (callback[0]=='\n'||callback[0]=='\r'))
  137. {
  138. if (!enter) enter = 1;
  139. else break;
  140. }
  141. else enter = 0;
  142. }
  143. else if (len < 0)
  144. {
  145. if (errno != EAGAIN)
  146. {
  147. perror("client: read stdin");
  148. exit(1);
  149. }
  150. }
  151. }
  152. close(connection);
  153. printf("Close!\n");
  154. return 0;
  155. }

服务器

  1. // by SovietPower
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <strings.h>
  5. #include <stdio.h>
  6. #include <sys/types.h>
  7. #include <sys/socket.h>
  8. #include <netinet/in.h>
  9. #include <netdb.h>
  10. #include <errno.h>
  11. #include <fcntl.h>
  12. #include <unistd.h> // 定义了STDIN_FILENO
  13. #define SERVER_PORT 7700
  14. #define MAX_PENDING 5
  15. #define MAX_LINE 30000 // 30KB
  16. extern int errno;
  17. char buf[MAX_PENDING][MAX_LINE];
  18. char callback[MAX_LINE];
  19. int server, clients[MAX_PENDING], enter[MAX_PENDING];
  20. int connections[MAX_PENDING];
  21. int FindFree(int *clients)
  22. {
  23. for(int i=0; i<MAX_PENDING; ++i)
  24. if (clients[i]==-1)
  25. return i;
  26. return -2;
  27. }
  28. void InitStdin()
  29. {
  30. int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
  31. if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) < 0)
  32. {
  33. perror("fcntl");
  34. exit(1);
  35. }
  36. }
  37. int InitConnection(int i)
  38. {
  39. if ((connections[i] = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  40. {
  41. perror("server: connection socket");
  42. return -1;
  43. }
  44. return 0;
  45. }
  46. int main()
  47. {
  48. InitStdin();
  49. // 主动连接到client
  50. for (int i=0; i<MAX_PENDING; ++i)
  51. clients[i] = -1, enter[i] = 0;
  52. // 监听client的请求
  53. struct sockaddr_in sin;
  54. bzero((char *)&sin, sizeof(sin));
  55. sin.sin_family = AF_INET;
  56. sin.sin_addr.s_addr = INADDR_ANY;
  57. sin.sin_port = htons(SERVER_PORT);
  58. if ((server = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
  59. {
  60. perror("server: socket");
  61. exit(1);
  62. }
  63. // 监听socket必须绑定sockaddr
  64. if ((bind(server, (struct sockaddr *)&sin, sizeof(sin))) < 0)
  65. {
  66. perror("server: bind");
  67. exit(1);
  68. }
  69. listen(server, MAX_PENDING);
  70. fprintf(stderr, "server: %d listening.\n",server);
  71. int client, addr_len;
  72. while(1)
  73. {
  74. if ((client = accept(server, (struct sockaddr *)&sin, &addr_len)) < 0)
  75. {
  76. if (errno != EAGAIN && errno != EWOULDBLOCK)
  77. {
  78. perror("server: accept");
  79. exit(1);
  80. }
  81. }
  82. else
  83. {
  84. int pos = FindFree(clients);
  85. if (pos == -2)
  86. {
  87. fprintf(stderr, "No enough space for the coming request.\n");
  88. exit(1);
  89. }
  90. else
  91. {
  92. // 与用户建立连接
  93. // 接受用户的host和port(不阻塞)
  94. char tmp[20];
  95. int len = recv(client, tmp, 20, 0), sep=-1;
  96. for (int i=0; i<20; ++i)
  97. if (tmp[i] == ' ')
  98. {
  99. tmp[sep=i] = '\0';
  100. break;
  101. }
  102. if (sep == -1)
  103. {
  104. fprintf(stderr, "Request from client %d has an invalid argument. Connection failed.\n", client);
  105. goto LISTENING;
  106. // exit(1);
  107. }
  108. char *host = tmp, *port = tmp+sep+1;
  109. fprintf(stderr, "Connecting with client %d: %s %s.\n", client, host, port);
  110. /* translate host name into peer’s IP address */
  111. struct hostent *hp = gethostbyname(host);
  112. if (!hp)
  113. {
  114. fprintf(stderr, "server: Request from client %d has an unknown host: %s.\n", client, host);
  115. goto LISTENING;
  116. // exit(1);
  117. }
  118. // client sockaddr
  119. struct sockaddr_in cin;
  120. bzero((char *)&cin, sizeof(cin));
  121. cin.sin_family = AF_INET;
  122. bcopy(hp->h_addr, (char *)&cin.sin_addr, hp->h_length);
  123. cin.sin_port = htons(atoi(port));
  124. sleep(1);
  125. // 连接到client
  126. if (InitConnection(pos) < 0)
  127. {
  128. perror("server: init connection");
  129. goto LISTENING;
  130. }
  131. if (connect(connections[pos], (struct sockaddr *)&cin, sizeof(cin)) < 0)
  132. {
  133. close(connections[pos]);
  134. perror("server: connect");
  135. goto LISTENING;
  136. }
  137. fprintf(stderr, "Connecting with %d on %d.\n", client, connections[pos]);
  138. // 分配用户
  139. enter[pos] = 0;
  140. clients[pos] = client;
  141. fprintf(stderr, "Build with %d\n", client);
  142. fprintf(stderr, "当前socket列表:");
  143. for(int i=0; i<MAX_PENDING; ++i)
  144. if (~clients[i])
  145. fprintf(stderr, "%d ",clients[i]);
  146. fprintf(stderr, "\n");
  147. }
  148. }
  149. LISTENING:
  150. // 多用户发送
  151. for (int i=0; i<MAX_PENDING; ++i)
  152. {
  153. int c_now = clients[i];
  154. if (c_now == -1) continue;
  155. char *b = buf[i];
  156. int len = recv(c_now, b, MAX_LINE, MSG_DONTWAIT);
  157. if (len > 0)
  158. {
  159. fprintf(stderr, "from client:%d len:%d actually:%ld\n", c_now, len, strlen(b));
  160. fputs(b, stdout);
  161. fflush(stdout);
  162. if (len==2 && (b[0]=='\n'||b[0]=='\r'))
  163. {
  164. if (!enter[i]) enter[i] = 1;
  165. else
  166. {
  167. fprintf(stderr, "Close with %d\n", c_now);
  168. close(connections[i]);
  169. close(c_now);
  170. clients[i] = -1;
  171. // connections[i] = -1; // 描述符不需要清空,好像必须要重新用socket创建?
  172. }
  173. }
  174. else if (len>=2 && (b[0]=='\n'||b[0]=='\r') && (b[1]=='\n'||b[1]=='\r'))
  175. {
  176. fprintf(stderr, "Close with %d\n", c_now);
  177. close(connections[i]);
  178. close(c_now);
  179. clients[i] = -1;
  180. }
  181. else enter[i] = 0;
  182. }
  183. else if (len == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
  184. {
  185. perror("server recv");
  186. exit(1);
  187. }
  188. }
  189. // 发送给指定用户信息
  190. int len = read(STDIN_FILENO, callback, MAX_LINE);
  191. if (len > 0)
  192. {
  193. callback[len] = '\0';
  194. // fprintf(stderr, "callback: %s len:%d\n", callback, len);
  195. int sep = -1;
  196. for (int i=0; i<len; ++i)
  197. if (callback[i] == ' ')
  198. {
  199. sep = i;
  200. break;
  201. }
  202. if (sep == -1)
  203. {
  204. fprintf(stderr, "Invalid argument. Usage: client_socket info\n");
  205. goto DONE;
  206. }
  207. int pos = -1, val = atoi(callback);
  208. for (int i=0; i<MAX_PENDING; ++i)
  209. if (clients[i] == val) pos = i;
  210. if (pos == -1)
  211. {
  212. fprintf(stderr, "Invalid argument. Sending failed with %d.\n", val);
  213. goto DONE;
  214. }
  215. fprintf(stderr, "Sent info to client %d\n", val);
  216. send(connections[pos], callback+sep+1, len+1-sep-1, 0);
  217. }
  218. else if (len < 0)
  219. {
  220. if (errno != EAGAIN)
  221. {
  222. perror("server: read stdin");
  223. exit(1);
  224. }
  225. }
  226. DONE:
  227. ;
  228. }
  229. return 0;
  230. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注