UDP-阻塞与非阻塞设置socket非阻塞阻塞查看数据从哪儿来操作系统相关获取缓冲区的大小发送函数的阻塞和非阻塞UDP数据报的特点
UDP-阻塞与非阻塞
基于UDP
//阻塞: // 没有接受到数据,就不运行接收函数。 // 像这类必须要发生事件才能继续往下进行的函数称为阻塞函数 // 例:老王烧水,水壶没开,就一直等,直到水壶开才去做别的。 // // 非阻塞函数: // 例:老王烧水,水壶没开,老王去做别的,每隔一段时间回来看水壶开没开 // // // 实质: // socket是阻塞的,和函数没有关系。 // 关系socket使用的方式,是阻塞的还是非阻塞的。 // socket默认是阻塞的。
设置socket非阻塞
语法
int WSAAPI ioctlsocket(
[in] SOCKET s,
[in] long cmd,
[in, out] u_long *argp
);
参数
[in] s
标识套接字的描述符。
[in] cmd
要 对套接字执行的命令。
[in, out] argp
指向 cmd 参数的指针。
返回值
成功完成后, ioctlsocket 将返回零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
//-------------------------
// Set the socket I/O mode: In this case FIONBIO
// enables or disables the blocking mode for the
// socket based on the numerical value of iMode.
// If iMode = 0, blocking is enabled;
// If iMode != 0, non-blocking mode is enabled.
0阻塞,1 非阻塞
实例
//-------------------------
// Set the socket I/O mode: In this case FIONBIO
// enables or disables the blocking mode for the
// socket based on the numerical value of iMode.
// If iMode = 0, blocking is enabled;
// If iMode != 0, non-blocking mode is enabled.
iResult = ioctlsocket(m_socket, FIONBIO, &iMode);
if (iResult != NO_ERROR)
printf("ioctlsocket failed with error: %ld\n", iResult);
代码
//设置socket是非阻塞模式
u_long iMode = 1;
ioctlsocket(sock,FIONBIO,&iMode)
运行结果
修改:
更改报错条件
断点调试结果
没有收到数据也会反复运行recv函数。
阻塞
停在recv函数也就是79的位置,不会继续往下运行。
查看
ctrl+shift+esc=打开任务管理器
非阻塞模式
阻塞模式
非阻塞模式会比阻塞模式占用更多的CPU。
特点
非阻塞
- 等待的事情发生不一定第一时间知道。
- CPU 的占用率高。
阻塞
- 等待的事情发生第一时间知道。
- 没有接收到数据时任务挂起,CPU占用率低。
数据从哪儿来
操作系统相关
- 进程与内存
操作系统会给每个进程分配4G的虚拟内存。实际的分配内存不到4G。操作系统会实时分配内存给进程,一个进程的最大大小为4G。
- 虚拟内存
- 物理地址通过映射为虚拟地址。
- 一个进程的实际空间最大4G,4G并不是实际的大小。
- 虚拟内存的分配
内核空间:0-2G
是所有进程公有的。
socket :创建时操作系统会给每一个socket创建两个缓冲区。
当数据到达操作系统,会根据对应的端口号发往对应的socket,写入其对应的接收缓冲区里。
接收缓冲区:接收对方数据
发送缓冲区:缓存待发送数据
当要发送数据的时候,操作系统会将要发送的数据写入对应的socket的发送缓冲区。
用户空间:2G-4G
是一个进程自己私有的。
当程序调用recvfrom 函数,数据被拷贝进recvBuf。
recvBuf:将数据从内核空间的接收缓冲区拷贝到recvBuf里。
实际发送,操作系统通过调度,使用空闲网卡发送数据。
sendBuf:将数据从内核空间的发送缓冲区拷贝到sendBuf。
- 总体描述
- 操作系统给每个进程分配虚拟存储空间在内存。最大为4G。
- 其中2G为内核空间。内核空间为公共的,所有进程可以访问。一个进程每创建一个socket,操作系统就会为其创建一个接收缓冲区,和一个发送缓冲区。
- 虚拟内存的另外2G是内存空间。内存空间是私有的,只有该进程可以访问。用于存放函数调用时需要使用的数据如recvBuf。
- 数据发送时操作系统会对应数据包中的端口号找到对应的socket,将该数据存入该socket的发送缓冲区。
- 当操作系统调用函数recvfrom时,会将数据从内核空间的接收缓冲区拷贝到内存空间。
- 发送数据需要操作系统调度。只要发送缓冲区有数据,就会使用空闲网卡发送到对应的端口。
获取缓冲区的大小
getsockopt
语法
int WSAAPI getsockopt(
[in] SOCKET s,
[in] int level,
[in] int optname,
[out] char *optval,
[in, out] int *optlen
);
参数
[in] s
标识套接字的描述符。
[in] level
定义选项的级别。 示例: SOL_SOCKET。
[in] optname
要为其检索值的套接字选项。 示例: SO_ACCEPTCONN。 optname 值必须是在指定级别内定义的套接字选项,否则行为未定义。
[out] optval
一个指向缓冲区的指针,在该缓冲区中,将返回所请求的选项的值。
[in, out] optlen
指向 optval 缓冲区的大小(以字节为单位)的指针。
返回值
如果未发生错误, 则 getsockopt 返回零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
注解
如果从未使用 setsockopt 设置选项,则 getsockopt 将返回该选项的默认值。
getsockopt 支持以下选项。 “类型”列标识 optval 寻址的数据类型。
有关套接字选项的详细信息,请参阅 套接字选项。
当 level 参数设置为 SOL_SOCKET 时,optname 参数的值表有效。
值 | 类型 | 含义 |
---|---|---|
SO_ACCEPTCONN | BOOL | 套接字正在侦听。 |
SO_BROADCAST | BOOL | 套接字配置为传输和接收广播消息。 |
SO_BSP_STATE | CSADDR_INFO | 返回本地地址、本地端口、远程地址、远程端口、套接字类型和套接字使用的协议。 |
SO_CONDITIONAL_ACCEPT | BOOL | 从上一次调用 setsockopt 或系统默认值返回当前套接字状态。 |
SO_CONNECT_TIME | DWORD | 返回已连接套接字的秒数。 此套接字选项仅适用于面向连接的协议。 |
SO_DEBUG | BOOL | 已启用调试。 |
SO_DONTLINGER | BOOL | 如果 为 TRUE,则禁用SO_LINGER选项。 |
SO_DONTROUTE | BOOL | 路由功能被禁用。 此设置成功,但在AF_INET套接字上将被忽略;使用 WSAENOPROTOOPT AF_INET6套接字失败。 ATM 套接字不支持此选项。 |
SO_ERROR | int | 检索错误状态并清除。 |
SO_EXCLUSIVEADDRUSE | BOOL | 阻止任何其他套接字绑定到同一地址和端口。 在调用 绑定 函数之前,必须设置此选项。 |
SO_GROUP_ID | GROUP | 保留。 |
SO_GROUP_PRIORITY | int | 保留。 |
SO_KEEPALIVE | BOOL | 正在发送“保持连接”。 ATM 套接字不支持。 |
SO_LINGER | LINGER 结构 | 返回当前的逗留选项。 |
SO_MAX_MSG_SIZE | unsigned int | 面向消息的套接字类型的消息的最大大小 (例如,SOCK_DGRAM) 。 对于面向流的套接字没有意义。 |
SO_OOBINLINE | BOOL | 正在正常数据流中接收 OOB 数据。 (有关本主题的讨论,请参阅 Windows 套接字 1.1 阻止例程和 EINPROGRESS 部分。) |
SO_PORT_SCALABILITY | BOOL | 通过为本地计算机上的不同本地地址端口对多次分配通配符端口,允许最大化端口分配,从而为套接字启用本地端口可伸缩性。 |
SO_PROTOCOL_INFO | WSAPROTOCOL_INFO | 绑定到此套接字的协议的协议信息的说明。 |
SO_RCVBUF | int | 为接收保留的每个套接字的总缓冲区空间。 这与SO_MAX_MSG_SIZE无关,也不一定对应于 TCP 接收窗口的大小。 |
SO_REUSEADDR | BOOL | 可以将套接字绑定到已在使用中的地址。 不适用于 ATM 插座。 |
SO_SNDBUF | int | 为发送保留的每个套接字缓冲区总空间。 这与SO_MAX_MSG_SIZE无关,也不一定对应于 TCP 发送窗口的大小。 |
SO_TYPE | int | 套接字的类型 (例如,SOCK_STREAM) 。 |
PVD_CONFIG | 服务提供程序依赖 | 与 套接字关联的服务提供程序中的不透明数据结构对象。 此对象存储服务提供程序的当前配置信息。 此数据结构的确切格式特定于服务提供程序。 |
- level的其它选项
- 水平 = IPPROTO_TCP
代码
//获取缓冲区大小
int bufLen;
int optval;
int optvalLen = sizeof(optval);
err=getsockopt(sock,SOL_SOCKET, SO_RCVBUF,(char*)&optval,&optvalLen);
if (SOCKET_ERROR == err) {
cout << "getsockopt recv error" << GetLastError() << endl;
return 1;
}
else {
cout << optval << " getsockopt recv success" << endl;
}
int bufLenSend;
int optvalSend;
int optvalLenSend = sizeof(optvalSend);
err = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&optvalSend, &optvalLenSend);
if (SOCKET_ERROR == err) {
cout << "getsockopt send error" << GetLastError() << endl;
return 1;
}
else {
cout << optvalSend << " getsockopt send success" << endl;
}
运行结果
结果分析
65536B=64KB
发送缓冲区的大小是64KB,发送的数据大小尽量不要超过超过64KB,以避免数据丢失。
第一次往发送缓冲区拷贝30KB的数据,网卡忙碌,数据缓存没有发出去,下一次拷贝数据时就只有30KB的空间。
也就是发送缓冲区可能缓存不止一次需要发送的数据。
- 如何判断数据的大小
- 函数sendto的返回值是实际拷贝的数据的大小。
- 如果sendNum的大小等于sizeof(sendBuf)要发送的数据的大小。那么数据被完全拷贝发送。
- 如果sendNum<sizeof(sendBuf),那么需要再写一个发送函数对数据进行拷贝发送。
- 客户端不需要这么麻烦,一个网卡只为一端服务。
发送函数的阻塞和非阻塞
当发送缓冲区的空间不足够大的时候,才会阻塞。
阻塞发送:等到空间足够大的时候,再往缓冲区拷贝数据。
非阻塞发送:当空间不够大的时候,非阻塞发送就是有多少空间拷贝多少数据,剩余的数据程序自己处理。丢弃,或者人工处理。
UDP数据报的特点
- 面向非连接,可以接收任意人发来的数据。可以是1对1,也可以是1对多。(创建既发送)
- 通讯方式是数据报文的通讯方式,(多个)数据报之间不会粘连,(单个)数据报不可拆分。
- 传输效率高。(对比TCP)
- 会产生丢包和乱序。(丢包:在数据传输的路上,也就是路由器转发的时候丢失)(迷路的数据会在路由器中不断转发,一段时间后就会被丢弃。)(乱序:后发送的数据包比先发送的数据包更快到达目的端口。)
- UDP没有丢包处理,但同时它的传输效率高。