《TCP/IP 网络编程》第五章——基于 TCP 的服务器端/客户端(2)(学习笔记)

代码链接

第五章 基于 TCP 的服务器端/客户端(2)

本章将详细讲解 TCP 中必要的理论知识,还将给出第 4 章客户端问题的解决方案。

5.1 回声客户端的完美实现

5.1.1 回声服务端没有问题,只有客户端有问题?

问题不在服务器端,而在客户端,但只看代码也许不太好理解,因为 I/O 中使用了相同的函数。先回顾一下回声服务器端的 I/O 相关代码,下面是第四章 echo_server.c 的部分代码。

while((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
    write(clnt_sock, message, str_len);

接着是第四章 echo_client.c 代码

write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE - 1);

二者都在循环调用 read 或 write 函数。实际上之前的回声客户端将 100% 接收自己传输的数据,只不过接受数据时的单位有些问题。扩展客户端代码回顾范围,下面还是 echo_client.c 的代码:

while (1) {
    fputs("Input message(Q to quit): ", stdout);
    fgets(message, BUF_SIZE, stdin);
    ....
    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 回声客户端问题解决方法

因为可以提前确定接收数据的大小,客户端可以循环调用 read 函数直到大小满足。

书上参考代码一些我的见解

如 read 函数第三个参数设置问题,应该会造成数组越界

逻辑上收到的数据大小应该不会超过发出的大小,用不等式判断没有问题,但可能死循环。书上使用大小判断解决,但是我认为出现这种读超了应该看看超出部分的数据是什么,因为自己代码逻辑并不会超。

代码略,思路并不复杂。

5.1.3 如果问题不在于回声客户端:定义应用层协议

回声客户端可以提前知道接收的数据长度,但这在更多情况下是不可能的。若无法预知接收数据长度时应如何收发数据?这时需要的是应用层协议的定义。

在收发过程中需要定好规则(协议)以表示数据的边界,或提前告知收发数据的大小。服务端/客户端实现过程中逐步定义的这些规则集合就是应用层协议。

请自己实现一个程序来实现计算器功能。

客户端发送需要计算内容,服务端返回计算结果,客户端呈现计算结果。

我自己的实现参考文件 op_client.cop_server.c

和书上方法不同。

我的方法是

mywrite 函数,每次先写两位,表示接下来发送数据长度,在发送数据。

myread 函数,每次先读取两位,表示接下来要接收数据长度,在接收数据。

纯 c 语言写表达式求值太麻烦,就随便实现了一下加减法。

运行结果

# 服务端
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-05$ gcc op_server.c -o opserver.exe
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-05$ ./opserver.exe 9190
Connect client 1 
接收到的表达式: 5+2+6-1
返回答案: 12


# 客户端
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-05$ gcc op_client.c -o opclient.exe
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-05$ ./opclient.exe 127.0.0.1 9190
Connected...
输入表达式: 
5+2+6-1
result: 12

5.1.4 计算器服务器端/客户端示例

5.2 TCP 原理

5.2.1 TCP 套接字中的 I/O 缓冲

TCP 套接字的数据收发无边界。服务器端即使调用 1 次 write 函数传输 40 字节的数据,客户端也有可能通过 4 次 read 函数调用每次读取 10 字节。但此处也有一些疑问,服务器端一次性传输了 40 字节,而客户端居然可以缓慢地分批接收。客户端接受 10 字节后,剩下的 30 字节在何处等候呢?

实际上,write 函数调用后并非立即传输数据,read 函数调用后也并非马上接收数据。write 函数调用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。

这些 I/O 缓冲特性可整理如下:

  • I/O 缓冲在每个 TCP 套接字中单独存在
  • I/O 缓冲在创建套接字时自动生成
  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
  • 关闭套接字将丢失输入缓冲中的数据

那么,下面这种情况会会引发什么事情?

客户端输入缓冲为 50 字节,而服务器端传输了 100 字节。

根本不会发生这类问题,因为 TCP 会控制数据流。TCP 中有滑动窗口(Sliding Window)协议,用对话方式呈现如下:

A:你好,最多可以向我传递 50 字节
B:好的
A:我腾出了 20 字节的空间,最多可以收 70 字节
B:好的

数据收发也是如此,因此 TCP 中不会因为缓冲溢出而丢失数据。

write 函数返回在数据移到输出缓冲时,因为 TCP 保证对输出缓冲区数据的传输,所以说 write 函数在数据传输完成时返回。

5.2.2 TCP 内部工作原理 1:与对方套接字的连接

TCP 套接字从创建到消失所经过程分为如下 3 步:

  • 与对方套接字建立连接
  • 与对方套接字进行数据交换
  • 断开与对方套接字的连接

首先讲解与对方套接字建立连接的过程。该过程又被称为 Three-way handshaking(三次握手)。

三次握手过程自行查阅《TCP/IP 详解卷一》。

5.2.2 TCP 内部工作原理 2:与对方主机的数据交换

详见《TCP/IP 详解卷一》

5.2.3 TCP 的内部工作原理 3:断开与套接字的连接

四次挥手

详见《TCP/IP 详解卷一》

5.3 基于 Windows 的实现

5.4 习题

以下是我的理解

  1. 请说明 TCP 套接字连接设置的三次握手过程。尤其是 3 次数据交换过程每次收发的数据内容。
  • 第一次握手:建立连接时,客户端发送 SYN 包(seq=j)到服务器,并进入 SYN_SENT 状态,等待服务器确认。SYN:同步序列编号
  • 第二次握手:服务器收到 SYN 包,回复 SYN+ACK 包,(ack=j+1)(seq=k)此时服务器进入 SYN_RECV 状态
  • 第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),完成三次握手。
  1. TCP 是可靠的数据传输协议,但在通过网络通信的过程中可能丢失数据。请通过 ACK 和 SEQ 说明 TCP 通过何种机制保证丢失数据的可靠传输。

ACK 表示下个需要接收的序列号起始,SEQ 是当前发送数据的起始序号。收到某个 ACK,说明这个 ACK 之前的数据全部正确发送。等待 ACK 超时则说明数据丢失需要重传。

  1. TCP 套接字中调用 write 和 read 函数时数据如何移动?结合 I/O 缓冲进行说明。

TCP 套接字调用 write 函数时,数据将移至输出缓冲区。由 TCP 协议栈完成传输到对方输入缓冲区。

调用 read 函数从输入缓冲区中读取数据。

  1. 对方主机的输入缓冲剩余 50 字节空间时,若本主机通过 write 函数请求传输 70 字节,请问 TCP 如何处理这种情况?

因为滑动窗口协议,能发送的只有前 50 字节,多余的 20 字节存储在发送缓冲区直到对方通告窗口空间覆盖到相应字节序号。

  1. 我的计算器服务器端和客户端收发数据是类似功能,一个固定长度的前缀表示接下来需要收发消息的长度。

  2. 创建收发文件的服务器端/客户端

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值