diff --git a/README.md b/README.md index 85f18ae..ccf8e74 100644 --- a/README.md +++ b/README.md @@ -1180,8 +1180,91 @@ client: 本章代码,在[TCP-IP-NetworkNote](https://github.com/riba2534/TCP-IP-NetworkNote)中可以找到。 +上一章仅仅是从编程角度学习实现方法,并未详细讨论 TCP 的工作原理。因此,本章将想次讲解 TCP 中必要的理论知识,还将给出第 4 章客户端问题的解决方案。 + ### 5.1 回声客户端的完美实现 +#### 5.1.1 回声服务器没有问题,只有回声客户端有问题? + +问题不在服务器端,而在客户端,只看代码可能不好理解,因为 I/O 中使用了相同的函数。先回顾一下服务器端的 I/O 相关代码: + +```c +while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0) + write(clnt_sock, message, str_len); +``` + +接着是客户端代码: + +```c +write(sock, message, strlen(message)); +str_len = read(sock, message, BUF_SIZE - 1); +``` + +二者都在村换调用 read 和 write 函数。实际上之前的回声客户端将 100% 接受字节传输的数据,只不过接受数据时的单位有些问题。扩展客户端代码回顾范围,下面是,客户端的代码: + +```c +while (1) +{ + fputs("Input message(Q to quit): ", stdout); + fgets(message, BUF_SIZE, stdin); + + if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) + break; + + write(sock, message, strlen(message)); + str_len = read(sock, message, BUF_SIZE - 1); + message[str_len] = 0; + printf("Message from server: %s", message); +} +``` + +现在应该理解了问题,回声客户端传输的是字符串,而且是通过调用 write 函数一次性发送的。之后还调用一次 read 函数,期待着接受自己传输的字符串,这就是问题所在。 + +#### 5.1.2 回声客户端问题的解决办法 + +这个问题其实很容易解决,因为可以提前接受数据的大小。若之前传输了20字节长的字符串,则再接收时循环调用 read 函数读取 20 个字节即可。既然有了解决办法,那么代码如下: + +- [echo_client2.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch05/echo_client2.c) + +这样修改为了接收所有传输数据而循环调用 read 函数。测试及运行结果可参考第四章。 + +#### 5.1.3 如果问题不在于回声客户端:定义应用层协议 + +回声客户端可以提前知道接收数据的长度,这在大多数情况下是不可能的。那么此时无法预知接收数据长度时应该如何手法数据?这是需要的是**应用层协议**的定义。在收发过程中定好规则(协议)以表示数据边界,或者提前告知需要发送的数据的大小。服务端/客户端实现过程中逐步定义的规则集合就是应用层协议。 + +现在写一个小程序来体验应用层协议的定义过程。要求: + +1. 服务器从客户端获得多个数组和运算符信息。 +2. 服务器接收到数字候对齐进行加减乘运算,然后把结果传回客户端。 + +例: + +1. 向服务器传递3,5,9的同事请求加法运算,服务器返回3+5+9的结果 +2. 请求做乘法运算,客户端会收到`3*5*9`的结果 +3. 如果向服务器传递4,3,2的同时要求做减法,则返回4-3-2的运算结果。 + +请自己实现一个程序来实现功能。 + +我自己的实现: + +- [My_op_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch05/My_op_server.c) +- [My_op_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch05/My_op_client.c) + +编译: + +```shell +gcc My_op_client.c -o myclient +gcc My_op_server.c -o myserver +``` + +结果: + +![](https://i.loli.net/2019/01/15/5c3d966b81c03.png) + +其实主要是对程序的一点点小改动,只需要再客户端固定好发送的格式,服务端按照固定格式解析,然后返回结果即可。 + +书上的实现: + diff --git a/ch05/My_op_client.c b/ch05/My_op_client.c new file mode 100644 index 0000000..1c7c6f7 --- /dev/null +++ b/ch05/My_op_client.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#define BUF_SIZE 10240 +void error_handling(char *message); + +int main(int argc, char *argv[]) +{ + int sock; + char message[BUF_SIZE]; + int str_len; + struct sockaddr_in serv_adr; + if (argc != 3) + { + printf("Usage : %s \n", argv[0]); + exit(1); + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock == -1) + error_handling("socket() error"); + memset(&serv_adr, 0, sizeof(serv_adr)); + serv_adr.sin_family = AF_INET; + serv_adr.sin_addr.s_addr = inet_addr(argv[1]); + serv_adr.sin_port = htons(atoi(argv[2])); + + if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) + error_handling("connect() error"); + else + printf("连接成功!\n"); + int n, i; + char temp[20]; + puts("请输入你要计算的数字个数:"); + scanf("%d", &n); + sprintf(temp, "%d", n); + strcat(temp, " "); + strcat(message, temp); + for (i = 0; i < n; i++) + { + printf("请输入第 %d 个数字:", i + 1); + scanf("%s", temp); + strcat(temp, " "); + strcat(message, temp); + } + puts("请输入你要进行的运算符(+,-,*):"); + scanf("%s", temp); + strcat(message, temp); + write(sock, message, strlen(message)); + str_len = read(sock, message, BUF_SIZE - 1); + message[str_len] = 0; + printf("运算的结果是: %s\n", message); + return 0; +} +void error_handling(char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + exit(1); +} \ No newline at end of file diff --git a/ch05/My_op_server.c b/ch05/My_op_server.c new file mode 100644 index 0000000..d271f6e --- /dev/null +++ b/ch05/My_op_server.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 10240 +void error_handling(char *message); + +char res[10]; +char *calc(char *s) +{ + int len = strlen(s), i; + int n = 0; + for (i = 0; i < len; i++) + if (s[i] == ' ') + { + i++; + break; + } + else + n = n * 10 + (s[i] - '0'); + int *num = malloc(sizeof(int) * n); + int tot = 0, x = 0; + + for (; i < len; i++) + { + if (s[i] == '+' || s[i] == '*' || s[i] == '-') + break; + if (s[i] == ' ') + { + num[tot++] = x; + x = 0; + } + else + x = x * 10 + (s[i] - '0'); + } + int ans = 0; + if (s[i] == '+') + { + for (int i = 0; i < tot; i++) + ans += num[i]; + } + else if (s[i] == '*') + { + ans = 1; + for (int i = 0; i < tot; i++) + ans *= num[i]; + } + else if (s[i] == '-') + { + ans = num[0]; + for (int i = 1; i < tot; i++) + ans -= num[i]; + } + free(num); + sprintf(res, "%d", ans); + return res; +} +int main(int argc, char *argv[]) +{ + int serv_sock, clnt_sock; + char message[BUF_SIZE]; + int str_len; + + struct sockaddr_in serv_adr, clnt_adr; + socklen_t clnt_adr_sz; + + if (argc != 2) + { + printf("Usage : %s \n", argv[0]); + exit(1); + } + + serv_sock = socket(PF_INET, SOCK_STREAM, 0); + if (serv_sock == -1) + error_handling("socket() error"); + + memset(&serv_adr, 0, sizeof(serv_adr)); + serv_adr.sin_family = AF_INET; + serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_adr.sin_port = htons(atoi(argv[1])); + + if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) + error_handling("bind() error"); + + if (listen(serv_sock, 5) == -1) + error_handling("listen() error"); + + clnt_adr_sz = sizeof(clnt_adr); + clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); + if (clnt_sock == -1) + error_handling("accept() error"); + str_len = read(clnt_sock, message, BUF_SIZE); + write(clnt_sock, calc(message), str_len); + close(clnt_sock); + close(serv_sock); + return 0; +} + +void error_handling(char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + exit(1); +} \ No newline at end of file diff --git a/ch05/README.md b/ch05/README.md new file mode 100644 index 0000000..ec3b937 --- /dev/null +++ b/ch05/README.md @@ -0,0 +1 @@ +## 第 5 章 基于 TCP 的服务端/客户端(2) \ No newline at end of file diff --git a/ch05/echo_client2.c b/ch05/echo_client2.c new file mode 100644 index 0000000..99af0a1 --- /dev/null +++ b/ch05/echo_client2.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 1024 +void error_handling(char *message); + +int main(int argc, char *argv[]) +{ + int sock; + char message[BUF_SIZE]; + int str_len, recv_len, recv_cnt; + struct sockaddr_in serv_adr; + + if (argc != 3) + { + printf("Usage : %s \n", argv[0]); + exit(1); + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock == -1) + error_handling("socket() error"); + + memset(&serv_adr, 0, sizeof(serv_adr)); + serv_adr.sin_family = AF_INET; + serv_adr.sin_addr.s_addr = inet_addr(argv[1]); + serv_adr.sin_port = htons(atoi(argv[2])); + + if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) + error_handling("connect() error!"); + else + puts("Connected..........."); + + while (1) + { + fputs("Input message(Q to quit): ", stdout); + fgets(message, BUF_SIZE, stdin); + + if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) + break; + str_len = write(sock, message, strlen(message)); + + recv_len = 0; + while (recv_len < str_len) + { + recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1); + if (recv_cnt == -1) + error_handling("read() error"); + recv_len += recv_cnt; + } + message[recv_len] = 0; + printf("Message from server: %s", message); + } + close(sock); + return 0; +} + +void error_handling(char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + exit(1); +} \ No newline at end of file