带外数据比普通数据具有更高的优先级,应该会立即被发送,不论发送缓冲区中是否有排队等候发送的普通数据,他的传输可以使用一条单独的链路传输也可以映射到普通数据传输的链接中。
UDP没有实现带外数据传输,TCP也没有真正的带外数据。不过TCP利用其头部中的紧急指针标志和紧急指针两个字段, 给应用程序提供了一种紧急方式。TCP的紧急方式利用传输普通数据的连接来传输紧急数据。这种紧急数据的含义和带外数据类似,因此后文也将TCP紧急数据称为带外数据。如果有多个带外数据,则每个TCP头部都设置URG,他们的紧急指针指向同一位置(数据流中带外数据的下一位置,)只有一个TCP报文段真正携带带外数据。
接收端只有在接收到紧急指针标志时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将它读人一个特殊的缓存中。这个缓存只有1字节,称为带外缓存。如果上层应用程序没有及时将带外数据从带外缓存中读出,则后续的带外数据(如果有的话)将覆盖它。
讨论的带外数据的接收过程是TCP模块接收带外数据的默认方式。如果我们给TCP连接设置了SO_OOBINLINE选项,则带外数据将和普通数据一样被TCP模块存放在TCP接收缓冲区中。此时应用程序需要像读取普通数据一样来读取带外数据。那么这种情况下如何区分带外数据和普通数据呢?显然,紧急指针可以用来指出带外数据的位置,socket编程接口也提供了系统调用来识别带外数据.
带外标记
#include <sys/socket.h> int sockatmark(int sockfd);
判断被读到的下一个数据是否有带外数据,如果是返回1,用recv的MSG_OOB接收,不是返回0,
I/O复用
//... int main() { fd_set exception; FD_ZERO(&exception); while(true) { FD_SET(connfd,&exception); int res=select(maxfd+1,&readFd,nullptr,&exception,nullptr); if(res<0) { // } //可读 if(FD_ISSET(connfd,&readFd)) { res=recv(connfd,buff,sizeof(buff)-1,0); if(res<)) { // } } else if(FD_ISSET(connfd,&exception)) { //对于异常,采用带MSG_OOB标志的recv接外带数据 res=recv(connfd,buff,sizeof(buff)-1,MSG_OOB); if(res<0) { // } } } close(connfd); close(listefd); return 0; }
用SIGURG信号
//... int setNoBlocking(int fd) { int oldOption=fcntl(fd,FD_GETFL); int newOption=oldOption|O_NONBLOCK; fcntl(fd,F_SETFL,newOption); return oldOption; } void addFd(int epollFd,int fd) { epoll_event event; event.data.fd=fd; event.events=EPOLLIN|EPOLLET; epoll_ctl(epollFd,EPOLL_CTL_ADD,fd,&event); setNoBlocking(fd); } //信号处理函数 void sig_handle(int sig) { //保留原来的errno,在函数最后恢复,以保证函数的可重入性 int sigErrno=errno; char buff[1024]; memset(buff,'\0',1024); int res=recv(connfd,buff,1023,MSG_OOB);//接收带外数据 errno=sigErrno; return ; } void addSig(int sig,void (sig_handle)(int )) { struct sigaction sa; memset(&sa,'\0',sizeof(sa)); sa.sa_handler=sig_handle; sa.sa_flags|=SA_RESTART; sigfillset(&sa.sa_mask); assert(sigaction(sig,&sa,NULL)!=NULL); return ; } int main() { //... int connfd=accpet(/*...*/); if(connfd<0) { //... } else { addSig(SIGURG,sig_handle); fcntl(connfd,F_SETOWN,getpid());//使用SIGURG前,必须设置socket的宿主进程或进程组(获取异步I/Od的所有权。SIGIO) //接收普通数据 } return 0; }