更优的TCP客户端设计

TCP类型套接字的特性

在之前的文章中提到过,TCP套接字没有数据边界的。

在回声客户端中,只调用了一次read函数读取信息,读取到的信息可能存在问题,分别是数据截断和数据沾包。客户端收发数据的代码如下所示:

	send(sock, message, strlen(message), 0);
	int strLen = recv(sock, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		break;
	message[strLen] = 0;
	printf("Message from server: %s", message);

数据截断

问题表现:

  • 客户端发送的数据可能被分割成多个TCP包传输
  • 服务器可能无法在一次read调用中接收全部数据
  • 客户端可能在read时只收到部分数据

问题代码展示:

// 客户端
char msg[] = "Hello, this is a long message";
write(sock, msg, sizeof(msg)); // 发送数据
read(sock, buf, BUF_SIZE);     // 期望接收完整数据

// 服务器
while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0) {
    write(clnt_sock, buf, str_len); // 回声
}

客户端的字符串可能会被拆分为多个部分发送,服务器接收到一部分消息后就会发送给客户端,此时客户端会调用一次read函数接收这个不完整的消息。

数据沾包

当客户端发送的消息过短时,这些消息可能会合并在一起,通过调用一次write函数发送给服务器,经服务器回声之后,客户端会一次性收到一个合并后的大消息,这些消息原本应该分为多次收到的。

// 客户端连续发送
write(sock, "Hello", 5);
write(sock, "World", 5);
read(sock, buf, BUF_SIZE); // 可能收到"HelloWorld"

// 服务器可能将两次发送的数据一起读取并回传

TCP套接字中的I/O缓冲

要解决这两个问题,应该先了解write和read函数的工作机制。

write函数调用后并非立即传输数据,read函数调用后也并非马上接收数据。

在这里插入图片描述

调用write函数之后,数据移动至输出缓冲,并在适当的时候传向对方的输入缓冲。这些IO缓冲特性总结如下:

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

不会发生超出输入缓冲区大小的数据传输,TCP会控制数据流(滑动窗口)。

write函数和send函数会在数据移动到缓冲区时返回。

解决方案

在了解了write和read函数的特性之后,我们就可以设计解决数据截断和沾包问题的方案了。

事先知道数据大小

我们可以规定数据传输的长度,在客户端的接收代码中循环接收信息知道接收到的信息总长度大于等于我们规定的数据长度。

//客户端
const int message_len = 0xff;
char message[message_len];

write(serv_sock, message, message_len);

int readed_len = 0;
int read_len = 0;
char message_read[0xff];
while(readed_len < message_len)
{
    read_len = read(serv_sock, message_read + readed_len, message_len - readed_len);
    readed_len += read_len;
}

发送部分也可以这样写:

// 客户端
int msg_len = strlen(msg);
write(sock, &msg_len, sizeof(int));   // 先发送长度
write(sock, msg, msg_len);            // 再发送数据

事先不知道数据大小

在大多数情况我们不知道数据的大小,这时候需要制定协议规定好接收到什么消息的时候停止接收消息。

TCP套接字内部原理

TCP套接字从创建到消失的三个步:

  1. 与对方的套接字建立连接
  2. 数据交换
  3. 断开与对方套接字的连接

建立连接

三次握手:

  1. A:请求与B建立连接
  2. B:受理A的连接请求
  3. A:已确认B受理连接请求

套接字是全双工的,可以双向传输数据。

在这里插入图片描述

SYN表示同步的意思,表示收发数据前的同步消息,SEQ表示序列号,用于重新排列消息,ACK是确认号,用于确认已接收到的数据字节的序列号。

交换数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ACK号 = SEQ号 + 传递的字节数 + 1

套接字会启动计时器等待ACK应答,若计时器发生超时,则重传。

断开连接

为了避免在传输数据时断开连接,TCP套接字在断开连接时会进行协商。四次握手。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值