From 9899af3446b8bbf75573e82c4c77358acf30783b Mon Sep 17 00:00:00 2001 From: riba2534 Date: Wed, 16 Jan 2019 11:33:30 +0800 Subject: [PATCH] =?UTF-8?q?P92=205.2=20TCP=20=E5=8E=9F=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 +++ README.md | 53 +++++++++++++++++++++++++++ ch05/op_client.c | 63 ++++++++++++++++++++++++++++++++ ch05/op_server.c | 83 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 ch05/op_client.c create mode 100644 ch05/op_server.c diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..30097c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "socket.h": "c" + } +} \ No newline at end of file diff --git a/README.md b/README.md index ccf8e74..9505586 100644 --- a/README.md +++ b/README.md @@ -1265,7 +1265,60 @@ gcc My_op_server.c -o myserver 书上的实现: +- [op_client.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch05/op_client.c) +- [op_server.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch05/op_server.c) +阅读代码要注意一下,`int*`与`char`之间的转换。TCP 中不存在数据边界。 + +编译: + +```shell +gcc op_client.c -o opclient +gcc op_server.c -o opserver +``` + +运行: + +```shell +./opserver 9190 +./opclient 127.0.0.1 9190 +``` + +结果: + +![](https://i.loli.net/2019/01/16/5c3ea297c7649.png) + +### 5.2 TCP 原理 + +#### 5.2.1 TCP 套接字中的 I/O 缓冲 + +TCP 套接字的数据收发无边界。服务器即使调用 1 次 write 函数传输 40 字节的数据,客户端也有可能通过 4 次 read 函数调用每次读取 10 字节。但此处也有一些一问,服务器一次性传输了 40 字节,而客户端竟然可以缓慢的分批接受。客户端接受 10 字节后,剩下的 30 字节在何处等候呢? + +实际上,write 函数调用后并非立即传输数据, read 函数调用后也并非马上接收数据。如图所示,write 函数滴啊用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。 + +![](https://i.loli.net/2019/01/16/5c3ea41cd93c6.png) + +I/O 缓冲特性可以整理如下: + +- I/O 缓冲在每个 TCP 套接字中单独存在 +- I/O 缓冲在创建套接字时自动生成 +- 即使关闭套接字也会继续传递输出缓冲中遗留的数据 +- 关闭套接字将丢失输入缓冲中的数据 + +假设发生以下情况,会发生什么事呢? + +> 客户端输入缓冲为 50 字节,而服务器端传输了 100 字节。 + +因为 TCP 不会发生超过输入缓冲大小的数据传输。也就是说,根本不会发生这类问题,因为 TCP 会控制数据流。TCP 中有滑动窗口(Sliding Window)协议,用对话方式如下: + +> - A:你好,最多可以向我传递 50 字节 +> - B:好的 +> - A:我腾出了 20 字节的空间,最多可以接受 70 字节 +> - B:好的 + +数据收发也是如此,因此 TCP 中不会因为缓冲溢出而丢失数据。 + +write 函数在数据传输完成时返回。 diff --git a/ch05/op_client.c b/ch05/op_client.c new file mode 100644 index 0000000..ae782bd --- /dev/null +++ b/ch05/op_client.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#define BUF_SIZE 1024 +#define RLT_SIZE 4 +#define OPSZ 4 +void error_handling(char *message); + +int main(int argc, char *argv[]) +{ + int sock; + char opmsg[BUF_SIZE]; + int result, opnd_cnt, i; + 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..........."); + + fputs("Operand count: ", stdout); + scanf("%d", &opnd_cnt); + opmsg[0] = (char)opnd_cnt; + + for (i = 0; i < opnd_cnt; i++) + { + printf("Operand %d: ", i + 1); + scanf("%d", (int *)&opmsg[i * OPSZ + 1]); + } + fgetc(stdin); + fputs("Operator: ", stdout); + scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]); + write(sock, opmsg, opnd_cnt * OPSZ + 2); + read(sock, &result, RLT_SIZE); + + printf("Operation result: %d \n", result); + close(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/op_server.c b/ch05/op_server.c new file mode 100644 index 0000000..b0cd0c6 --- /dev/null +++ b/ch05/op_server.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#define BUF_SIZE 1024 +#define OPSZ 4 +void error_handling(char *message); +int calculate(int opnum, int opnds[], char oprator); +int main(int argc, char *argv[]) +{ + int serv_sock, clnt_sock; + char opinfo[BUF_SIZE]; + int result, opnd_cnt, i; + int recv_cnt, recv_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); + for (int i = 0; i < 5; i++) + { + opnd_cnt = 0; + clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); + read(clnt_sock, &opnd_cnt, 1); + + recv_len = 0; + while ((opnd_cnt * OPSZ + 1) > recv_len) + { + recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE - 1); + recv_len += recv_cnt; + } + result = calculate(opnd_cnt, (int *)opinfo, opinfo[recv_len - 1]); + write(clnt_sock, (char *)&result, sizeof(result)); + close(clnt_sock); + } + close(serv_sock); + return 0; +} +int calculate(int opnum, int opnds[], char op) +{ + int result = opnds[0], i; + switch (op) + { + case '+': + for (i = 1; i < opnum; i++) + result += opnds[i]; + break; + case '-': + for (i = 1; i < opnum; i++) + result -= opnds[i]; + break; + case '*': + for (i = 1; i < opnum; i++) + result *= opnds[i]; + break; + } + return result; +} +void error_handling(char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + exit(1); +} \ No newline at end of file