Linux高性能服务器编程中的TCP带外数据梳理总结

Linux高性能服务器编程中的TCP带外数据梳理总结

文章目录

1.TCP 带外数据总结

至此,我们讨论完了 TCP 带外数据相关的所有知识。总结梳理一下:

cobol 复制代码
// 异常事件,采用带MSG_OOB标志的recv函数读取带外数据
else if (FD_ISSET(connfd, &exception_fds)) {
    ret = recv(connfd, buf, sizeof(buf) - 1, MSG_OOB);
    if (ret <= 0) {
        break;
    }
    printf("get %d bytes of oob data: %s\n", ret, buf);
}
  • 《Linux 高性能服务器编程》第 10 章 10.5.3 小节 (P190)Linux高性能服务器编程 | 读书笔记 | 8. 信号-CSDN博客在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:

  • I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件。

  • 使用SIGURG信号。

在应用程序检测到带外数据到达以后,还需要进一步判断带外数据在数据流中的具体位置,才能够读取带外数据。**《Linux 高性能服务器编程》第 5 章 5.9 小节 (P87)**带外标记介绍的 sockatmark调用可以判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark返回1,此时我们就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0。

2.第五章带外数据

send.c

c 复制代码
#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[])
{
	if(argc<2)
	{
		printf("usage:%s if_address port_number\n",argv[0]);
		return 1;
	}
	const char *ip=argv[1];
	int port=atoi(argv[2]);
	struct sockaddr_in server_address;

	bzero(&server_address,sizeof(server_address));

	server_address.sin_family=AF_INET;
	inet_pton(AF_INET,ip,&server_address.sin_addr);
	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;
}

recv.c

c 复制代码
#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>

#define BUFSIZE 1024

int main(int argc,char * argv[])
{
	if(argc<2)
	{
		printf("usage:%s if_address port_number\n",argv[0]);
		return 1;
	}
	const char *ip=argv[1];
	int port=atoi(argv[2]);
	struct sockaddr_in address;

	bzero(&address,sizeof(address));

	address.sin_family=AF_INET;
	inet_pton(AF_INET,ip,&address.sin_addr);
	address.sin_port=htons(port);

	int sockfd=socket(PF_INET,SOCK_STREAM,0);
	assert(sockfd>=0);

	int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address));
	assert(ret!=-1);
	
	ret=listen(sockfd,5);
	assert(ret!=-1);

	struct sockaddr_in client;
    socklen_t client_len=sizeof(client);
	int connfd=accept(sockfd,(struct sockaddr*)&client,&client_len);
	if(connfd<0)
	{
		printf("connection failed\n");
	}
	else
	{
		char buffer[BUFSIZE];

		memset(buffer,'\0',BUFSIZE);
		ret=recv(connfd,buffer,BUFSIZE-1,0);
		printf("got %d bytes of normal data '%s'\n",ret,buffer);

		memset(buffer,'\0',BUFSIZE);
		ret=recv(connfd,buffer,BUFSIZE-1,MSG_OOB);
		printf("got %d bytes of normal data '%s'\n",ret,buffer);

		memset(buffer,'\0',BUFSIZE);
		ret=recv(connfd,buffer,BUFSIZE-1,0);
		printf("got %d bytes of normal data '%s'\n",ret,buffer);
		close(connfd);
	}
	close(sockfd);
	return 0;
}

运行结果:

由此可见,客户端发送给服务器的3字节的带外数据"abc"中,仅有最后一个字符'c'被服务器当成真正的带外数据接收。并且服务器对正常数据的接收将被带外数据截断,即123ab123不能一次性读出,第二个123得在调用一次recv。

flags参数只对send和recv的当前调用生效。

3.第九章带外数据

send.c

c 复制代码
#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[])
{
	if(argc<2)
	{
		printf("usage:%s if_address port_number\n",argv[0]);
		return 1;
	}
	const char *ip=argv[1];
	int port=atoi(argv[2]);
	struct sockaddr_in server_address;

	bzero(&server_address,sizeof(server_address));

	server_address.sin_family=AF_INET;
	inet_pton(AF_INET,ip,&server_address.sin_addr);
	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);
        sleep(1);
		send(sockfd,oob_data,strlen(oob_data),MSG_OOB);
        sleep(1);
		send(sockfd,normal_data,strlen(normal_data),0);
	}
	close(sockfd);
	return 0;
}

select.c

c 复制代码
#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<string.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/select.h>

int main(int argc,char * argv[])
{
	if(argc<=2)
	{
		printf("usage:%s if_address port_number\n",argv[0]);
		return 1;
	}
	const char *ip=argv[1];
	int port=atoi(argv[2]);

	int ret=0;
	struct sockaddr_in address;

	bzero(&address,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;
	socklen_t client_len=sizeof(client);
	int connfd=accept(listenfd,(struct sockaddr*)&client,&client_len);
	if(connfd<0)
	{
		printf("connection failed\n");
		close(listenfd);
	}
	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("select failure\n");
			break;
		}
		if(FD_ISSET(connfd,&read_fds))
		{
			ret=recv(connfd,buf,sizeof(buf)-1,0);
			if(ret<=0)
			{
				break;
			}
			printf("get %d bytes of normal data:%s\n",ret,buf);
		}
		else if(FD_ISSET(connfd,&exception_fds))
		{
			ret=recv(connfd,buf,sizeof(buf)-1,MSG_OOB);
			if(ret<=0)
			{
				break;
			}
			printf("get %d bytes of oob data:%s\n",ret,buf);
		}
	}
	close(listenfd);
	close(connfd);
	return 0;
}

运行结果图:

可以看到一开始只有123ab和123没有带外数据c

是什么原因导致的呢?我们在第五节测试的时候,明明 TCP 服务器终端是打印出了带外数据的(使用带 MSG_OOB 标志的 recv 函数),说明问题肯定没有出在数据的发送上(因为这两次数据发送使用的是同一个客户端程序)。

那么数据接收出了什么问题呢?猜测可能是由于数据发送间隔太短,导致服务器没有时间处理带外数据。

于是我们在 sned.c 函数中加入延时:

编译后重新运行就有带外数据abc了,可以看到ab会被当做普通数据处理,带外数据只有字符c。

4.第十章带外数据

send.c

c 复制代码
#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[])
{
	if(argc<2)
	{
		printf("usage:%s if_address port_number\n",argv[0]);
		return 1;
	}
	const char *ip=argv[1];
	int port=atoi(argv[2]);
	struct sockaddr_in server_address;

	bzero(&server_address,sizeof(server_address));

	server_address.sin_family=AF_INET;
	inet_pton(AF_INET,ip,&server_address.sin_addr);
	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);
        sleep(1);
		send(sockfd,oob_data,strlen(oob_data),MSG_OOB);
        sleep(1);
		send(sockfd,normal_data,strlen(normal_data),0);
	}
	close(sockfd);
	return 0;
}

sig_msg.c

c 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <libgen.h>
 
#define BUF_SIZE 1024
 
static int connfd;
 
// SIGURG信号的处理函数
void sig_urg(int sig) {
    int save_errno = errno;
    char buffer[BUF_SIZE];
    memset(buffer, '\0', BUF_SIZE);
    
    int ret;
    while ((ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB)) < 0) {
        if (errno == EWOULDBLOCK) {
            continue; // 如果接收缓冲区满了,继续读取,直到接收到带外数据
        } else {
            break; // 处理其他错误情况
        }
    }
    if (ret > 0) {
        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)); // 使用 memset 函数将结构体 sa 的所有字节初始化为零。
    sa.sa_handler = sig_handler; // 将信号处理函数指针 sig_handler 赋值给 sa_handler 字段。这个字段指定了当信号 sig 发生时,操作系统应该调用哪个函数来处理这个信号。
    sa.sa_flags |= SA_RESTART; // 设置 sa_flags 字段,并启用 SA_RESTART 标志。SA_RESTART 标志表示,如果在处理信号时一个被阻塞的系统调用被中断,内核会自动重新启动这个系统调用,而不会返回 EINTR 错误。这在网络编程中很有用,因为它可以避免信号处理过程中某些系统调用(如 recv 或 accept)被意外中断。
    sigfillset(&sa.sa_mask); // 将 sa_mask 字段设置为阻塞所有信号。在信号处理函数运行期间,其他信号将被阻塞,防止信号处理函数被其他信号中断。
    assert(sigaction(sig, &sa, NULL) != -1); // 调用 sigaction 函数来注册信号处理程序,将信号 sig 与 sig_handler 函数绑定。如果 sigaction 调用失败,它将返回 -1,在这种情况下,assert 宏将终止程序并报告错误。sigaction 的第三个参数为 NULL,表示不需要保存之前的信号处理程序信息。
}
 
int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);
 
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
 
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);
 
    int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);
 
    ret = listen(sock, 5);
    assert(ret != -1);
 
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        // 将 SIGURG 信号与 sig_urg 处理函数关联起来。当 SIGURG 信号发生时,操作系统将调用 sig_urg 函数来处理这个信号。
        addsig(SIGURG, sig_urg);
        // 设置指定文件描述符 connfd 的所有者为当前进程,以便该文件描述符在收到 SIGURG 信号时,将信号发送给这个进程。
        fcntl(connfd, F_SETOWN, getpid());
 
        char buffer[BUF_SIZE];
        // 循环接收普通数据
        while (1) {
            memset(buffer, '\0', BUF_SIZE);
            ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
            if (ret < 0) {
                // 如果 recv 或其他系统调用因信号中断而返回 -1,那么 errno 就会被设置为 EINTR,表示系统调用被信号中断。
                if(errno == EINTR) {
                    continue; // 如果recv因信号中断,则继续读取
                }
                break; // 处理其他错误
            } else if (ret == 0) {
                printf("Client disconnected.\n");
                break;
            }
            printf("get %d bytes of normal data '%s'\n", ret, buffer);
        }
        close(connfd);
    }
    close(sock);
    return 0;
}

运行结果图:

第一次是加了sleep的,可以打出来c这个带外数据

第二没加,就打不出来了,原因还是同上

相关推荐
下雪了 ~5 分钟前
HTTP和HTTPS的区别有哪些?
服务器·前端·笔记·网络协议·计算机网络
记得多喝水o15 分钟前
docker环境部署zabbix
运维·网络·网络协议·tcp/ip·信息与通信
运维&陈同学19 分钟前
【Elasticsearch01】企业级日志分析系统ELK之Elasticsearch单机部署
大数据·linux·elk·elasticsearch·微服务·云原生·jenkins
百川Cs38 分钟前
【Linux】文件挂载系统-Filesystem mounting
linux·运维·服务器
EutoCool39 分钟前
Linux:进程通信、管道通信
linux·运维·服务器
码猩1 小时前
VBA 连续打印多个内容成PDF
java·服务器·pdf
爱宇阳1 小时前
查看服务器或系统架构(amd64、arm64...)
运维·服务器
Yan-英杰2 小时前
利用代理IP爬取Zillow房产数据用于数据分析
网络·python·网络协议·tcp/ip·数据分析
乌云大帝2 小时前
IIS服务器部署C# WebApi程序,客户端PUT,DELETE请求无法执行
运维·服务器·c#·webapi
水彩橘子3 小时前
构建centos docker基础镜像
linux·docker·centos