socket之send和recv原理剖析
2015-12-18 15:31
232 查看
谈到网络socket编程, 我们不得不提两个基本也很重要的函数:send和recv. 对socket编程理解不深的童鞋容易产生这样一个错误的认识: send函数是用来发送数据, 而recv函数是用来接收数据的, 其实, 这种观点是稍微有点偏颇的, 掩盖了本质。
下面, 我们看一幅图, 了解一下send和recv函数所处的位置(这幅图是我在网上找的, 不太清晰, 请凑合看一下):
为了简便起见, 我们仅考虑单向的数据流, 即A(客户端)向B(服务端)发送数据。 在应用程序Program A中, 我们定义一个数组char szBuf[100] = "tcp"; 那么这个szBuf就是应用程序缓冲区(对应上图的Program A中的某块内存), send函数对应上面蓝色的Socket API, 内核缓冲区对应上面的黄色部分。 我们看到, send函数的作用是把应用程序缓冲区中的数据拷贝到内核缓冲区, 仅此而已。 内核缓冲区中的数据经过网卡, 经历网络传到B端的网卡(TCP协议),
然后进入B的内核缓冲区, 然后由recv函数剪切/复制到Program B的应用程序缓冲区。前面我们用过wireshark抓包, wireshark抓的正是流经网卡的数据。
强调一下:
1. 对于客户端A, 其发送的内核缓冲区和接收的内核缓冲区是不一样的, 互不干扰. 服务端B也同理。
2. recv函数是剪切还是复制, 由最后一个参数决定, 我们在之前的博文已经讲述过了。
下面, 我们不考虑recv函数, 仅仅玩转一下send(双向send), 并用wireshark抓包实验一下, 加深理解。 注意, Wireshark抓不了环回包, 所以, 需要在两台电脑上测试。
服务端B的程序为:
[cpp] view
plaincopy
#include <stdio.h>
#include <winsock2.h> // 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("0.0.0.0"); // socket对应的IP地址
addrSrv.sin_port = htons(8888); // socket对应的端口
// 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)
bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 将socket设置为监听模式,5表示等待连接队列的最大长度
listen(sockSrv, 5);
// sockSrv为监听状态下的socket
// &addrClient是缓冲区地址,保存了客户端的IP和端口等信息
// len是包含地址信息的长度
// 如果客户端没有启动,那么程序一直停留在该函数处
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);
while(1)
{
getchar(); // 阻塞一下
send(sockConn, "tcp", strlen("tcp") + 1, 0); // send来啦
}
closesocket(sockConn);
closesocket(sockSrv);
WSACleanup();
return 0;
}
先启动服务端B.
下面我们来看客户端A:
[cpp] view
plaincopy
#include <winsock2.h>
#include <stdio.h>
#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("192.168.1.101");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
while(1)
{
getchar();
send(sockClient, "cpp", strlen("cpp") + 1, 0);
}
closesocket(sockClient);
WSACleanup();
return 0;
}
好, 再启动客户端A.
我们在客户端A上安装wireshark并启动抓包, 实验发现:
1. 当A向B发送数据时, A上的wireshark可以抓到对应的包, 因为数据经过了A的网卡。(不管B是否有去recv)
2. 当B向A发送数据时, A上的wireshark也可以抓到对应的包, 因为数据到达了A的内核缓冲区, 也经历了A的网卡。(不管A是否有去recv)
相信通过本文的讨论, 又加深了对send和recv的认识。 未来, 路漫漫, 我们慢慢来。
下面, 我们看一幅图, 了解一下send和recv函数所处的位置(这幅图是我在网上找的, 不太清晰, 请凑合看一下):
为了简便起见, 我们仅考虑单向的数据流, 即A(客户端)向B(服务端)发送数据。 在应用程序Program A中, 我们定义一个数组char szBuf[100] = "tcp"; 那么这个szBuf就是应用程序缓冲区(对应上图的Program A中的某块内存), send函数对应上面蓝色的Socket API, 内核缓冲区对应上面的黄色部分。 我们看到, send函数的作用是把应用程序缓冲区中的数据拷贝到内核缓冲区, 仅此而已。 内核缓冲区中的数据经过网卡, 经历网络传到B端的网卡(TCP协议),
然后进入B的内核缓冲区, 然后由recv函数剪切/复制到Program B的应用程序缓冲区。前面我们用过wireshark抓包, wireshark抓的正是流经网卡的数据。
强调一下:
1. 对于客户端A, 其发送的内核缓冲区和接收的内核缓冲区是不一样的, 互不干扰. 服务端B也同理。
2. recv函数是剪切还是复制, 由最后一个参数决定, 我们在之前的博文已经讲述过了。
下面, 我们不考虑recv函数, 仅仅玩转一下send(双向send), 并用wireshark抓包实验一下, 加深理解。 注意, Wireshark抓不了环回包, 所以, 需要在两台电脑上测试。
服务端B的程序为:
[cpp] view
plaincopy
#include <stdio.h>
#include <winsock2.h> // 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("0.0.0.0"); // socket对应的IP地址
addrSrv.sin_port = htons(8888); // socket对应的端口
// 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)
bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 将socket设置为监听模式,5表示等待连接队列的最大长度
listen(sockSrv, 5);
// sockSrv为监听状态下的socket
// &addrClient是缓冲区地址,保存了客户端的IP和端口等信息
// len是包含地址信息的长度
// 如果客户端没有启动,那么程序一直停留在该函数处
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);
while(1)
{
getchar(); // 阻塞一下
send(sockConn, "tcp", strlen("tcp") + 1, 0); // send来啦
}
closesocket(sockConn);
closesocket(sockSrv);
WSACleanup();
return 0;
}
先启动服务端B.
下面我们来看客户端A:
[cpp] view
plaincopy
#include <winsock2.h>
#include <stdio.h>
#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("192.168.1.101");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
while(1)
{
getchar();
send(sockClient, "cpp", strlen("cpp") + 1, 0);
}
closesocket(sockClient);
WSACleanup();
return 0;
}
好, 再启动客户端A.
我们在客户端A上安装wireshark并启动抓包, 实验发现:
1. 当A向B发送数据时, A上的wireshark可以抓到对应的包, 因为数据经过了A的网卡。(不管B是否有去recv)
2. 当B向A发送数据时, A上的wireshark也可以抓到对应的包, 因为数据到达了A的内核缓冲区, 也经历了A的网卡。(不管A是否有去recv)
相信通过本文的讨论, 又加深了对send和recv的认识。 未来, 路漫漫, 我们慢慢来。
相关文章推荐
- Linux下解决高并发socket最大连接数所受的各种限制(解除IO限制)
- Qt之密码框不可选中、复制、粘贴、无右键菜单等
- python与zmq系列(1)
- 查看本机SHA1码和MD5码
- JAVA虚拟机之四:G1垃圾收集器
- ftp和http断点续传及下载的Delphi实现
- W3AF:一个web应用安全的攻击、审计平台
- Android中Parcelable接口用法
- Git Bash中文乱码
- 基于QT的在线打字练习软件助手(C/S模型)
- JQuery中阻止事件冒泡方式及其区别
- 在matlab中查看变量的数据类型
- 织梦DedeCMS任意位置调用自定义字段的方法
- cgroup介绍、安装和控制cpu,内存,io示例
- zabbix 监控nginx
- UIViewController
- 移动平台GPU浮点测试
- 性能之巅:Linux网络性能分析工具
- LoadRuner的LOG日志设置
- FRM-40654 记录已经被另一个用户更新,重新查询以查看修改