前面我们分析过TCP的ACK
, RST
等, 本节来分析URG
.
TCP报文段首部格式
注意 : TCP虽然是面向字节流的, 但是TCP传送的数据单元却是报文段.
消息被设置为紧急时会把TCP状态标记为URG
, 同时还会设置紧急指针.
紧急模式
在套接字选项中找不到关于URG
的描述, 那么该消息又怎样被标记为紧急呢? 别急, 消息标记为紧急数据这个在最开始实现套接字通信中有说到过. 紧急标志主要就是send
和recv
最后一个参数的选项.
flags值 | 描述 |
---|---|
MSG_OOB | 发送或接收外来数据(紧急数据) |
MSG_DONTROUTE | 绕过路由表查找 |
MSG_DONTWAIT | 仅操作非阻塞 |
MSG_PEEK | 窥看外来数据 |
MSG_WAITALL | 等待达到nbytes字节数后才返回 |
MSG_NOSIGNAL | 往读端关闭的管道或者socket中写数据不产生SIGPIPE信号 |
如果要发送和接收紧急数据只要设置recv
和send
的flags
参数值为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标记只能有一个
- 带外缓冲区是单子节, 并且是一个单独的通道.