26. TCP协议之紧急模式

本文详细介绍了TCP协议中的紧急模式,分析了TCP报文段首部格式和紧急模式的实现方式。通过实验展示了如何在客户端和服务端发送和接收紧急数据,并探讨了紧急数据在传输过程中的特点,即紧急消息只有一个单子节缓冲,且只能有一个OOB标记。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前面我们分析过TCP的ACK, RST等, 本节来分析URG.


TCP报文段首部格式

注意 : TCP虽然是面向字节流的, 但是TCP传送的数据单元却是报文段.

在这里插入图片描述

消息被设置为紧急时会把TCP状态标记为URG, 同时还会设置紧急指针.


紧急模式

在套接字选项中找不到关于URG的描述, 那么该消息又怎样被标记为紧急呢? 别急, 消息标记为紧急数据这个在最开始实现套接字通信中有说到过. 紧急标志主要就是sendrecv最后一个参数的选项.

flags值描述
MSG_OOB发送或接收外来数据(紧急数据)
MSG_DONTROUTE绕过路由表查找
MSG_DONTWAIT仅操作非阻塞
MSG_PEEK窥看外来数据
MSG_WAITALL等待达到nbytes字节数后才返回
MSG_NOSIGNAL往读端关闭的管道或者socket中写数据不产生SIGPIPE信号

如果要发送和接收紧急数据只要设置recvsendflags参数值为MSG_OOB即可.

recv(sockfd, buf, sizeof(buf), MSG_OOB);
send(sockfd, buf, len, MSG_OOB);

为了发送和接收紧急消息, 那么对端又怎么区分普通消息和紧急消息呢 ? 紧急消息内核会向进程发送SIGURG信号. 既然会触发信号, 那么这样就容易区分了, 直接捕捉信号即可.


紧急模式实验

但是这里我并不打算使用捕捉信号来完成实验. 后面在socket就绪条件 [1]中有一点关于“socket上有未处理的错误 : select能处理的异常情况只有一种: socket上接收到带外数据 ” .

使用IO复用, 当接收到紧急消息时, 会触发错误(POLLERR, EPOLLERR). 这里就实验代码就采用select来接收紧急数据.


服务端 : 完整代码 URG_service.c

主要部分代码如下 :

void doservice(int service){
	int client;
	char buf[1024];
	// errset 和 terrset 主要用于接收紧急消息
	fd_set rset, trset, errset, terrset;

	client = Accept(service, NULL, NULL);
	FD_ZERO(&rset);
	FD_ZERO(&errset);

	int num;
	int stat = 0;
	while(1){
		FD_SET(client, &rset);
		if(stat == 0)
			FD_SET(client, &errset);
		trset = rset; terrset = errset;

		select(client + 1, &trset, NULL, &terrset, NULL);

		// 接收紧急消息, 当发生错误的时候套接字可读也可写
		if(FD_ISSET(client, &terrset)){
			num = recv(client, buf, sizeof(buf), MSG_OOB);
			buf[num] = 0;
			fprintf(stderr, "OOB message : %s\n", buf);
			FD_CLR(client, &errset);
		}
		if(FD_ISSET(client, &trset)){
			num = recv(client, buf, sizeof(buf), 0);
			if(num == 0)
				break;
			buf[num] = 0;
			write(STDOUT_FILENO, buf, num);
		}
	}
	close(client);
	close(service);
}

主要注意的需要设置select的错误返回以及发生错误时套接字即可读也可写.


客服端 : 完整代码 URG_client.c

主要部分代码如下 :

void doClient(int service, const char *ip, int port){
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	inet_aton(ip, &addr.sin_addr);

	Connect(service, (struct sockaddr *)&addr, sizeof(addr));
	
	send(service, "123", 3, 0);	// 普通数据
	send(service, "123", 3, MSG_OOB);	// 紧急数据
	
	close(service);
}

客服端直接发送紧急数据.

运行结果 :

在这里插入图片描述

咦? 怎么回事, 怎么紧急数据只有最后一个字节3, 其余部分居然都不是紧急数据. 别急, 接下来就来分析这个问题.


紧急模式分析

我们先以单子节分析 :

send(sockfd, "a", 1, MSG_OOB);

发送一个紧急消息, 当前的发送缓冲区由以下缓冲区变为了带OOB数据的缓冲区, 并且设置了TCP紧急指针. 其实发送的该数据a被加入到缓冲区中并标志为OOB同时将紧急指针设置为指向下一个可用位置, 发送端还会将为待发送的下一分节在TCP首部设置URG标志.

在这里插入图片描述

在这里插入图片描述

所以在抓包的时候也能够看到该标志被设置.

在这里插入图片描述

分析单子节之后, 我们在回来看看我们例子, 是将整个123当作紧急消息发送了, 怎么只有最后一个3成了紧急消息了, 其他的都成了普通消息了呢? 其实问题很简单, 是设计的问题. 即使数据流动因为TCP流动而停止, 紧急通知也能发送到对端的TCP, 因为该数据并不放入套接字接收缓冲区, 而是被放入一个独立的单子节带外缓冲区.

所以紧急消息只会有一个单子节缓冲, 而被标记的也是最后一个字节.

注意 : 只能有一个OOB标记. 如果旧的OOB数据没有被读取而新的OOB到来了, 那么新的OOB标记就会清除掉旧的标记. 有兴趣的也可以做一下这个实验.


小结

信号捕捉的方法也在目录中能够找到. 关于recv的其他参数选项有兴趣的也可以尝试实验一下.

  • OOB标记只能有一个
  • 带外缓冲区是单子节, 并且是一个单独的通道.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值