TCP协议之紧急模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/89915016

前面我们分析过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标记只能有一个
  • 带外缓冲区是单子节, 并且是一个单独的通道.

猜你喜欢

转载自blog.csdn.net/Function_Dou/article/details/89915016