博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何偷窥到socket对应的内核缓冲区中有什么数据? 有多少数据?---利用recv的MSG_PEEK和ioctlsocket的FIONREAD
阅读量:4140 次
发布时间:2019-05-25

本文共 4768 字,大约阅读时间需要 15 分钟。

       很多时候, 应用程序仅仅想知道内核缓冲区中有什么数据, 或者想知道有多少数据可读, 也就是说, 应用程序仅仅想偷窥一下里面的数据, 并不是想偷取, 那怎么办呢?

 

       事实上, 我们之前已经大致说过, 现在, 我们继续来复习一下recv的MSG_PEEK:

       服务端程序为:

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); while(1) { getchar(); char sendBuf[100] = "good"; send(sockConn, sendBuf, strlen(sendBuf), 0); // 每次发送5个字节过去 } } closesocket(sockSrv); WSACleanup(); return 0;}

      开启服务端。

 

 

      客户端程序为:

 

#include 
#include
#pragma comment(lib, "ws2_32.lib")int main(){ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); while(1) { char recvBuf[100] = {0}; recv(sockClient, recvBuf, 100 - 1, MSG_PEEK); // 注意: 最后的参数是MSG_PEEK, 而不是0 //printf("%s\n", recvBuf); // 不要用这个, 否则打印不全,要用下面的: int i = 0; for(i = 0; i < 99; i++) { putchar(recvBuf[i]); } getchar(); } closesocket(sockClient); WSACleanup(); return 0;}

     开启客户端。

 

 

     我们看到, 当服务端不停地给客户端发送数据的时候, 客户端的recv并不会把数据从内核缓冲区中取出来, 此时, 内核缓冲区中的数据不断累积, 每次累积5个字节。 在实际实验中, 我发了3次, 客户端接收3次,客户端的结果如下:

good  (第1次偷窥到的)

good good (第2次偷窥到的)

good good good (第3次偷窥到的)

      可以看到, 数据在内核缓冲区确实是不断积累的, 也从侧面证明了, recv并只是在偷窥, 而非偷取。 好, 我们关掉服务端和客户端, 不要影响后面的实验。

 

      继续讨论, 有的时候, 我们不是想看里面有什么数据, 而是想知道里面有多少数据(可能是为了便于知道随后该读取多少), 其实, 用recv的MSG_PEEK也是可以做到的, 但更好的方法是, 直接利用ioctlsocket的FIONREAD去获取。 我查阅了很多资料, 却不知道FIONREAD中这个N的对应的具体单词, 那我就斗胆猜测是not, 也就是说, FIONREAD是function, input, output, not, read的缩写, 从字面意思看, 它不会去read(此处指代偷取)出来内核缓冲区里面的数据。 该看程序了:

      服务端程序依然为:

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); while(1) { getchar(); char sendBuf[100] = "good"; send(sockConn, sendBuf, strlen(sendBuf), 0); // 发送数据到客户端,最后一个参数一般设置为0 } } closesocket(sockSrv); WSACleanup(); return 0;}

     开启服务端:

 

    

     再看客户端, 程序为:

 

#include 
#include
#pragma comment(lib, "ws2_32.lib")int main(){ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); while(1) { getchar(); unsigned long bytes = 0; ioctlsocket(sockClient, FIONREAD, &bytes); // 探测内核缓冲区中有多少数据可以接收 printf("%d\n", bytes); } closesocket(sockClient); WSACleanup(); return 0;}

      开启它。

 

 

      我们同样让服务端端依次发3次数据, 发第一次后, 客户端探测, 发现有5个字节, 发第二次, 客户端再探测, 发现有10个字节, 发第三次, 客户端再次探测, 发现有15个字节, 可见, 正确探测到了内核缓冲区待接收的数据的多少。 客户端结果为:

5

10
15

 

     以上两种方法在利用tcp keepalive进行断网检测中经常用到, 后面我会继续介绍tcp keepalive的断网检测。 

 

     ok, 本文就到此为止。

 

转载地址:http://jugvi.baihongyu.com/

你可能感兴趣的文章
oslo渐晕分析源代码(二)
查看>>
OSLO由渐晕设置孔径的源码分析
查看>>
火石玻璃、冕牌玻璃
查看>>
Masonry的使用
查看>>
iOS实例浅谈MVC (附登陆注册demo)
查看>>
iOS KVO用法(附修改账号密码demo)
查看>>
LeetCode206 反转链表
查看>>
LeetCode 141 环形链表
查看>>
LeetCode 21 合并两个有序链表
查看>>
LeetCode 19 删除链表的倒数第N个节点
查看>>
LeetCode 876 链表的中间节点
查看>>
LeetCode 1186 删除一次得到子数组最大和
查看>>
ViewController生命周期(含viewDidLoad和loadView的特殊情况)
查看>>
layoutSubview调用问题 含证明及特殊情况
查看>>
iOS present 和 push
查看>>
剪不断,理还乱的UIColor,CGColor,CIColor
查看>>
C语言--简易表达式求值(栈的初步应用)
查看>>
LeetCode 121 买卖股票的最佳时机 和 LeetCode 122 买卖股票的最佳时机2 C语言
查看>>
iOS 深复制与浅复制
查看>>
LeetCode 155 最小栈 C语言
查看>>