TCP带外数据学习总结(概念,发送接收过程,数据到达检测,代码实现)

最近在学习《Linux高性能服务器编程》 这本书,书中零零散散的讲了TCP带外数据的一些知识,在这里把这些知识总结以下,方便自己,也方便他人。

本文主要分为以下四个方面总结,分别为 TCP带外数据的概念,如何发送和接收带外数据,怎么检测带外数据的到达,最后介绍相关函数以及代码实现。

第一部分: TCP带外数据的概念

   有很多传输层此协议都具有带外数据(OUT Of Band) 的概念,其作用是迅速通告通信的另一方本段发生的重要事件。带外数据具有比普通数据更高的优先级,理论上应该被立即发送和立即接收。带外数据可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中。

   UDP并没有实现带外数据,TCP也没有真正的带外数据。

   TCP利用其头部中的紧急指针标志以及紧急指针字段,给应用程序提供里一种紧急方式。所以TCP是利用传输普通数据的连接来传输带外数据。

第二部分:TCP带外数据的发送和接收

1. TCP带外数据的发送过程:

假设一个进程已经往某个TCP连接的发送缓冲区写入里N字节的普通数据,并等待其发送。在数据发送前,该进程又向这个连接写入里3个字节的带外数据“abc”。 此时,待发送的TCP报文段头部将被蛇者URG标志,并且紧急指针指向带外数据的下一个字节

发送端一次发送的多字节带外数据只有最后一个字节被当作是带外数据, 其他数据还是被当作普通数据。

2. TCP带外数据的接收过程

TCP接收端只有在接收到具有紧急标志的TCP报文端时才检查紧急指针,然后根据紧急指针所指的位置确定带外数据的位置,并将其读入一个特殊的缓存中这个缓存只有一个字节,称为带外缓存

第三部分:内核如何通知应用程序带外数据的到来

主要有两种方法:

1. IO复用(select方式复用)系统的调用报告一个异常事件;

select系统调用的用途是:在指定的一段时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。同样,select系统调用可以监听socket上的可读、可写和异常时间。在网络程序中,select能处理的异常情况只有一种:socket上接收到带外数据

其具体步骤是:首先设置select监听我们关心的socket描述符,当有事件发生时,判断时间类型,如果时间类型是socket异常时间,则表明有紧急数据到来,可以读取紧急数据里。

2. SIGURG信号

其实现方法是:在进程中注册SIGURG信号以及其信号处理函数,当进程接收到SIGURG信号时,在SIGURG的信号处理函数内接收紧急数据。此时信号的模式要为SA_RESTART,表示被信号中断的系统调用在信号返回时继续进行。

第四部分:相关函数及代码实现

1.相关函数

(1) 发送接收带外数据和发送接收普通数据一样,都可以使用send()和recv()函数。其区别在于函数的最后一个参数 int flags不同。

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd,void* buf,size_t len,int flags);
ssize_t send(int sockfd,const void* buf,size_t len,int flags);
//将fags 设置为 MSG_OOB 表示结束和发送带外数据。

(2) select 系统调用相关函数

#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
//nfds参数表示监听的文件描述符总数,一般取监听的最大文件描述符 + 1。
//readfds指向需要监听可读事件的描述符集合,writefds指向需要监听可写事件的描述符集合,exceptfds指向需要监听异常//的描述符集合
//
FD_ZERO(fd_set* fdset);//清除描述符集fdset中的所有描述符
FD_SET(int fd,fd_set* fdset);//向描述符集fdset添加描述符fd
FD_CLR(int fd,fd_set* fdset);//在描述符集fdset中清除描述符fd
int FD_ISSET(int fd,fd_set* fdset);//测试描述符集fdset中描述符fd是否被置位

(3) SIGUSR 信号处理相关函数

#include <signal.h>
#include <fcntl.h>
int sigaction (int sig, const struct sigaction* act, struct sigaction* oact);
//sig参数指定要绑定的信号,在这里就是SIGUSR
//act 参数绑定了信号处理函数指针,指定里信号的处理方式以及设置里信号掩码
//其中act.sa_handler 赋值为信号处理函数的指针

//使用SIGUSR之前 ,必须设置socket的宿主进程或者进程组,使用下面的函数
fcntl(sockfd,F_SETOWN,getpid());

2.代码实现

代码实现一个客户端和一个服务器端,客户端既发送带外数据,又发送普通数据,服务器端分别用两种方式实现既接收带外数据又接收普通数据并显示,可通过启动时设置不同的参数来使用不同方式接收。

客户端依次发送普通数据 123,带外数据abc,普通数据123,客户端输入两个参数,参数1服务器地址,参数2服务器监听端口。服务器端接收数据,服务器程序有三个输入参数,参数1 服务器的绑定地址,参数2服务器的绑定端口,参数3为1时用select复用接收,参数3为2时用SIGUSR信号方式处理。

客户端代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main ( int argc, char* argv[])//参数1为服务器IP地址,参数2为服务器的监听端口
{
        if(argc<=2)
        {
                printf("Usage: %s ip_address port_number\n",basename(argv[0]));
                return 1;
        }

        const char* ip = argv[1];//获取IP参数
        int port = atoi (argv[2]);//获取端口参数

        struct sockaddr_in server_address;
        memset(&server_address,0,sizeof(server_address));
        server_address.sin_family = AF_INET;
        inet_pton(AF_INET,ip,&server_address.sin_addr);//设置地址结构体IP
        server_address.sin_port = htons (port);//设置地址结构体端口

        int sockfd = socket(PF_INET,SOCK_STREAM,0);//生成套接字
        assert(sockfd>=0);

        if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0)
        {
                printf("connection failed!\n");

        }
        else
        {
                const char* oob_data = "abc";
                const char* normal_data = "123";
                send(sockfd,normal_data,strlen(normal_data),0);//发送普通数据
                send(sockfd,oob_data,strlen(oob_data),MSG_OOB);//发送带外数据
                send(sockfd,normal_data,strlen(normal_data),0);//发送普通数据
        }
        close(sockfd);//关闭套接字
        return 0;
}

服务器端代码

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>

static int connfd;

void sig_urg(int sig)//信号处理函数,在信号处理函数里接收带外数据
{
    int save_errno = errno;
    char buffer[1024];
    memset(buffer,0,1024);
    int ret = recv (connfd,buffer,1023,MSG_OOB);//接收带外数据
    printf("got %d bytes of oob data ‘%s‘\n",ret,buffer);
    errno = save_errno;
}

void addsig(int sig,void(*sig_handler)(int))//注册信号处理函数,设置信号处理方式以及掩码
{
    struct sigaction sa;
    memset(&sa,0,sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig,&sa,NULL)!=-1);
}

int main(int argc, char* argv[])
{
    if(argc <= 3)
    {
        printf("Usage: %s ip_address port_number method\n",basename(argv[0]));
        return 1;
    }

    const char* ip = argv[1];
    int port = atoi(argv[2]);
    int method = atoi(argv[3]);

    int ret = 0;
    struct sockaddr_in address;
    memset (&address,0,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip ,&address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET,SOCK_STREAM,0);//生成套接字
    assert(listenfd>=0);
    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));//绑定套接字
    assert(ret!=-1);
    ret = listen(listenfd,5);//设置监听
    assert(ret!=-1);

    struct sockaddr_in client_address;//客户端地址
    socklen_t client_addrlength = sizeof(client_address);
    connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);//接收客户端连接

    if(connfd<0)
    {
        printf("errno is: %d\n",errno);
        close(listenfd);
    }

    if(method == 1)//select复用模式接收带外数据
    {
        char buf[1024];
        fd_set read_fds;//需要监听可读信息的描述符集
        fd_set exception_fds;//需要监听异常信息的描述符集
        FD_ZERO(&read_fds);//清空描述符集
        FD_ZERO(&exception_fds);//清空异常描述符集

        while(1)
        {
        memset(buf,‘\0‘,sizeof(buf));
        FD_SET(connfd,&read_fds);//将套接字加入监听可读描述符集
        FD_SET(connfd,&exception_fds);//将套接字加入监听异常描述符集

        ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);//开始复用监听

        if(ret < 0)
        {
            printf("selection failure\n");
            break;
        }

        if(FD_ISSET(connfd,&read_fds))//有正常数据到来可读
        {
            memset(buf,‘\0‘,sizeof(buf));
            ret = recv(connfd,buf,sizeof(buf)-1,0);
            if(ret <= 0)
            {
                break;
            }
            printf("get %d bytes of normal data: %s\n",ret,buf);
        }

        if(FD_ISSET(connfd,&exception_fds))//有异常发生,即有带外数据到来
        {
            FD_CLR(connfd,&exception_fds);//清除描述符集中的相应位,否则,程序工作不正常
            memset(buf,‘\0‘,sizeof(buf));
            ret = recv(connfd,buf,sizeof(buf)-1,MSG_OOB);
            if(ret <= 0)
            {
                break;
            }
            printf("get %d bytes of oob data: %s\n",ret,buf);
            FD_SET(connfd,&exception_fds);//重新加入描述符集
        }
        }
        close(connfd);//关机套接字
        close(listenfd);//关闭监听套接字
        return 0;

    }

    else if(method == 2)//利用SIGURG检测带外数据是否到达
    {
        addsig(SIGURG,sig_urg);//注册信号集信号处理函数
        fcntl(connfd,F_SETOWN,getpid());//设置套接字的主进程
        char buffer[1024];

        while(1)
        {
            memset(buffer,0,1024);
            ret = recv(connfd,buffer,1023,0);
            if(ret <= 0)
            {
                break;
            }
            printf("got %d bytes of normal data ‘%s‘\n",ret,buffer);
        }
        close(connfd);
        close(listenfd);
        return 0;
    }

}

以上就是我对TCP带外数据相关知识的总结,由于我是个学生,没有实际的经验,纸上得来终觉浅,所以借总结写一写来加深印象。欢迎批评指正!

时间: 2024-12-24 21:22:02

TCP带外数据学习总结(概念,发送接收过程,数据到达检测,代码实现)的相关文章

TCP带外数据

传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看

TCP带外数据测试

带外数据的应用情况 如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求. 使用带外数据的实际程序例子就是telnet,rlogin,ftp命令. 前两个程序(telnet和rlogin)会将中止字符作为紧急数据发送到远程端.这会允许远程端冲洗所有未处理的输入,并且丢弃所有未发送的终端输出.这会快速中断一个向我们屏幕发送大量数据 的运行进程. ftp命令使用带外数据来中断一个文件的传输. TCP的带外数据(TCP紧急数据) TCP协议没有真正

TCP带外数据(URG,MSG_OOB)

前言: blog原文地址:http://blog.csdn.net/ordeder/article/details/43243425 本文系读书笔记,主要参考材料:http://wenku.baidu.com/view/f04a4dff9e31433239689341.html TCP的带外数据: 头部标志: URG位,紧急指针. 数据包中:一个紧急指针只指向一个字节的带外数据的后已字节位置.紧急数据时插在正常数据流中进行传输.紧急指针用于指出带外数据字节在正常字节流中的位置. 问题:为何不直接

大数据学习之小白如何学大数据?(详细篇)

大数据这个话题热度一直高居不下,不仅是国家政策的扶持,也是科技顺应时代的发展.想要学习大数据,我们该怎么做呢?大数据学习路线是什么?先带大家了解一下大数据的特征以及发展方向. 大数据的三个发展方向,平台搭建/优化/运维/监控.大数据开发/设计/架构.数据分析/挖掘. 先说一下大数据的4V特征: 数据量大,TB->PB 数据类型繁多,结构化.非结构化文本.日志.视频.图片.地理位置等; 商业价值高,但是这种价值需要在海量数据之上,通过数据分析与机器学习更快速的挖掘出来; 处理时效性高,海量数据的处

大数据学习总结(8)大数据场景

大数据场景一.各种标签查询 查询要素:人.事.物.单位 查询范围:A范围.B范围.... 查询结果:pic.name.data from 1.痛点:对所有文本皆有实时查询需求2.难点:传统SQL使用WHERE子句匹配LIKE关键词,在庞大的数据字段中搜索某些想要的字,需遍历所有数据页或者索引页,查询效率底,当出现千万级以上数据时,耗时较高,无法满足实时要求3.方案:使用全文检索方案,分布式架构,即使PB级量级也可做到毫秒级查询 大数据场景二.客户事件查询 查询条件:城市.区域.时间跨度(2017

TCP带外数据读写

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main( int argc, char* argv[] ) {

android发送/接收json数据(转)

客户端向服务器端发送数据,这里用到了两种,一种是在url中带参数,一种是json数据发送方式: url带参数的写法: url+/?r=m/calendar/contact_list&uid=3&&subscriptionslist[pageindex]=10&subscriptionslist[recordlimit]=10 从“&”符号之后一连串都是参数. 发送方式代码编写" DefaultHttpClient httpClient = new Defa

Vue导出模板、使用前端js办法导出表格数据、导入表格前端读取表格数据、导入表格发送后端读取数据

以下是几种用的较多的函数方法,可以参考使用. // 導出1 myExport() { // post請求文件寫法1 const url = 'http://XXXX/XXXX/XXXX/XXXX' const formData = new FormData() formData.append('file', '123') this.axios({ method: 'post', url: url, data: formData, responseType: 'blob' // 表明返回服務器返回

android 不能接收组播数据,但能够发送组播数据

http://blog.csdn.net/hknock/article/details/44244031 Android的Wifi,默认情况下是不接受组播的,见:http://developer.android.com/reference/android/net/wifi/WifiManager.MulticastLock.html 默认情况下,应用是不接收组播信息的,这样要接收处理的报文太多,很快就会把电池用尽.要知道移动设备(特指电话一类的,平板要好得多)目前最重要的因素是电量. 要想打开组