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

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

文章目录

  • Linux高性能服务器编程中的TCP带外数据梳理总结
    • 1.TCP 带外数据总结
    • 2.第五章带外数据
      • send.c
      • recv.c
    • 3.第九章带外数据
      • send.c
      • select.c
    • 4.第十章带外数据
      • send.c
      • sig_msg.c

1.TCP 带外数据总结

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

  • 《Linux 高性能服务器编程》第 3 章 3.8 节 (P50):Linux高性能服务器编程 | 读书笔记 | 2. TCP协议-CSDN博客
  • 《Linux 高性能服务器编程》第 5 章 5.8.1 小节 (P81):Linux高性能服务器编程 | 读书笔记 | 3. Linux网络编程基础API-CSDN博客提到的 recvsend 调用,函数传入的参数中的 flags 参数为数据收发提供了额外的控制,其中 MSG_OOB标志的含义即为发送或接收紧急数据。使用 MSG_OOB 选项发送带外数据的代码描述了这一过程。
  • 《Linux 高性能服务器编程》第 9 章 9.1.3 小节 (P148):Linux高性能服务器编程 | 读书笔记 | 7. I/O 复用-CSDN博客,socket上接收到普通数据和带外数据都将使select函数返回,但 socket 处于不同的就绪状态:前者处于可读状态,后者处于异常状态。对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据。
// 异常事件,采用带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

#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

#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 1024int 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;
}

运行结果:

image-20241214191915653

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

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

3.第九章带外数据

send.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

#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;
}

运行结果图:

image-20241214200803724

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

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

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

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

image-20241214201105732

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

4.第十章带外数据

send.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

#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 1024static 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;
}

运行结果图:

image-20241214205436518

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyTorch3D 可视化

PyTorch3D是非常好用的3D工具库。但是PyTorch3D对于可用于debug&#xff08;例如调整cameras参数&#xff09;的可视化工具并没有进行系统的介绍。这篇文章主要是想介绍我觉得非常使用的PyTorch3D可视化工具。 1. 新建一个Mesh 从hugging face上下载一个glb文件&#xff0c;例…

C# 网络编程--关于UDP 通信(二)

UDP (User Datagram Protocol) 是一种无连接的传输层协议&#xff0c;主要用于支持数据报文的传输。它的主要特点包括简单、高效、不保证可靠性和顺序。 1.UDP协议基本概念 1.udp基于IP的简单的协议&#xff0c;不可靠的协议 2.优点&#xff1a;简单、 轻量化、 传输速度高、…

Axure高保真数据可视化大屏图表组件库

推出了一款高保真数据可视化大屏图表组件库&#xff0c;旨在为用户提供丰富的图表类型&#xff0c;使数据呈现更加直观、生动。本文将详细介绍该组件库中的各类图表元件&#xff0c;包括面积图、折线图、柱状图、条形图、圆环图、雷达图、仪表图以及综合类图表&#xff0c;以满…

基于视觉的3D占用网络汇总

综述文章:https://arxiv.org/pdf/2405.02595 基于视觉的3D占用预测方法的时间线概述: 自动驾驶中基于视觉的3D占用预测的分层结构分类 2023年的方法: TPVFormer, OccDepth, SimpleOccupancy, StereoScene, OccupancyM3D, VoxFormer, OccFormer, OVO, UniOcc, MiLO, Multi-…

一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测

一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测 目录 一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现INFO-CNN-SVM向量加权算法优化卷积神经网络结…

渗透测试-前端验签绕过之SHA256

本文是高级前端加解密与验签实战的第1篇文章&#xff0c;本系列文章实验靶场为Yakit里自带的Vulinbox靶场&#xff0c;本文讲述的是绕过SHA256签名来爆破登录。 绕过 通过查看源代码可以看到key为 1234123412341234通过查看源代码可以看到是通过SHA256来进行签名的&#xff0…

深入了解IPv6——光猫相关设定:DNS来源、DHCPv6服务、前缀来源等

光猫IPv6设置后的效果对比图&#xff1a; 修改前&#xff1a; 修改后&#xff1a; 一、DNS来源 1. 网络连接 来源&#xff1a; 从上游网络&#xff08;如运营商&#xff09;获取 IPv6 DNS 信息&#xff0c;通过 PPPoE 或 DHCPv6 下发。 特点&#xff1a; DNS 服务器地址直…

CentOS7下,hive4.0.0安装部署

hive安装部署 为了简单起见&#xff0c;都安装到node1服务器上。&#xff08;集群&#xff1a;node1&#xff0c;node2&#xff0c;node3&#xff09; 环境&#xff08;已安装&#xff09;&#xff1a;Hadoop3.4.0&#xff0c;jdk-8u171 需要安装&#xff1a;MySQL8.4.3&…

burp(2)利用java安装burpsuite

BurpSuite安装 burpsuite 2024.10专业版&#xff0c;已经内置java环境&#xff0c;可以直接使用&#xff0c; 支持Windows linux macOS&#xff01;&#xff01;&#xff01; 内置jre环境&#xff0c;无需安装java即可使用&#xff01;&#xff01;&#xff01; bp2024.10下载…

攻防世界逆向刷题笔记(新手模式6-?)

6.1000clicks 看题目名字似乎是让咱们点击1000次之后才会出flag。本来打算用CE看能不能搜索出来数值&#xff0c;技术不到家&#xff0c;最后没有搜索到&#xff0c;还导致永劫无间打不了了。所以还是拿出IDA老实分析。 直接搜索flag字符&#xff0c;出来一大堆。张紫涵大佬说…

CentOS 上如何查看 SSH 服务使用的端口号?

我们知道&#xff0c;linux操作系统中的SSH默认情况下&#xff0c;端口是使用22&#xff0c;但是有些线上服务器并不是使用的默认端口&#xff0c;那么这个时候&#xff0c;我们应该如何快速知道SSH使用的哪个端口呢&#xff1f; 1、通过配置文件查看 cat /etc/ssh/sshd_confi…

定时/延时任务-Kafka时间轮源码分析

文章目录 1. 概要2. TimingWheel2.1 核心参数2.2 添加任务2.3 推进时间 3. TimerTaskList3.1 添加节点3.2 删除节点3.3 刷新链表3.4 队列相关 4. 时间轮链表节点-TimerTaskEntry5. TimerTask6. Timer 和 SystemTimer - 设计降级逻辑7. 上层调用8. 小结 1. 概要 时间轮的文章&a…

厦门凯酷全科技有限公司深耕抖音电商运营

在数字经济飞速发展的今天&#xff0c;抖音电商平台以其独特的社交属性和庞大的用户基础&#xff0c;迅速成为众多品牌和商家的新战场。在这个充满机遇与挑战的市场中&#xff0c;厦门凯酷全科技有限公司凭借其专业的服务、创新的理念和卓越的执行力&#xff0c;成为了抖音电商…

探秘多AI Agent模式:机遇、应用与未来展望(5/30)

摘要&#xff1a;多 AI Agent 模式是一种强大的人工智能架构&#xff0c;它利用多个智能体&#xff08;Agent&#xff09;之间的协作与交互来解决复杂问题、执行多样化任务并模拟复杂系统行为。在这种模式中&#xff0c;每个 Agent 都具备独立的感知、决策和行动能力&#xff0…

java之集合(详细-Map,Set,List)

1集合体系概述 1.1集合的概念 集合是一种容器&#xff0c;用来装数据的&#xff0c;类似于数组&#xff0c;但集合的大小可变&#xff0c;开发中也非常常用。 1.2集合分类 集合分为单列集合和多列集合 Collection代表单列集合&#xff0c;每个元素&#xff08;数据&#xff…

uni-app多环境配置动态修改

前言 这篇文章主要介绍uniapp在Hbuilderx 中&#xff0c;通过工程化&#xff0c;区分不同环境、动态修改小程序appid以及自定义条件编译&#xff0c;解决代码发布和运行时手动切换问题。 背景 当我们使用uniapp开发同一个项目发布不同的环境二级路径不同时&#xff0c;这时候…

继电器控制与C++编程:实现安全开关控制的技术分享

在现代生活中,继电器作为一种重要的电气控制元件,在电气设备的安全控制中起到了至关重要的作用。通过低电流控制高电流,继电器能够有效地隔离控制电路与被控设备,从而保障使用者的安全。本项目将介绍如何通过树莓派Pico与继电器模块结合,使用C++编程实现继电器的控制。 一…

软考中级-软件设计师通过心路经验分享

执念&#xff0c;第四次终于通过了 没买书&#xff0c;下班后每天2小时&#xff0c;四个2个月终于过了 学习经验&#xff1a; 1.下班后学习真的靠毅力&#xff0c;和上学的时候考证不是一个状态&#xff0c;大家要及时调整&#xff0c;否则过程很痛苦 2.失败三次的经验&#xf…

分布式 Raft算法 总结

前言 相关系列 《分布式 & 目录》《分布式 & Raft算法 & 总结》《分布式 & Raft算法 & 问题》 参考文献 《Raft一致性算法论文译文》《深入剖析共识性算法 Raft》 简介 Raft 木筏是一种基于日志复制实现的分布式容错&一致性算法。在Raft算法…

仿iOS日历、飞书日历、Google日历的日模式

仿iOS日历、飞书日历、Google日历的日模式&#xff0c;24H内事件可自由上下拖动、自由拉伸。 以下是效果图&#xff1a; 具体实现比较简单&#xff0c;代码如下&#xff1a; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color;…