企业自建网站局域网做网站 内网穿透
- 作者: 五速梦信息网
- 时间: 2026年03月21日 09:56
当前位置: 首页 > news >正文
企业自建网站,局域网做网站 内网穿透,网站怎么显示建设中,网站优化排名《TCP/IP网络编程》学习笔记 | Chapter 9#xff1a;套接字的多种可选项 《TCP/IP网络编程》学习笔记 | Chapter 9#xff1a;套接字的多种可选项套接字可选项和 I/O 缓冲大小套接字多种可选项getsockopt setsockoptSO_SNDBUF SO_RCVBUF SO_REUSEADDR发生地址绑定… 《TCP/IP网络编程》学习笔记 | Chapter 9套接字的多种可选项 《TCP/IP网络编程》学习笔记 | Chapter 9套接字的多种可选项套接字可选项和 I/O 缓冲大小套接字多种可选项getsockopt setsockoptSO_SNDBUF SO_RCVBUF SO_REUSEADDR发生地址绑定错误Binding ErrorTIME_WAIT 状态地址再分配 TCP_NODELAYNagle 算法Nagle 算法的优缺点禁用 Nagle 算法 基于 Windows 的实现基于 Windows 的套接字类型示例程序基于 Windows 的套接字 I/O 缓冲大小示例程序基于 Windows 的 TCP_NODELAY 示例程序 习题1下列关于Time-wait状态的说法错误的是2TCP_NODELAY可选项与Nagle算法有关可通过它禁用Nagle算法。请问何时应考虑禁用Nagle算法结合收发数据的特性给出说明。 《TCP/IP网络编程》学习笔记 | Chapter 9套接字的多种可选项
套接字可选项和 I/O 缓冲大小
套接字多种可选项
前文关于套接字的描述仅仅是使用其默认套接字特性来进行数据通信这对于简单的使用场景来说似乎是可以的然而实际工作场景中的确需要配置相关套接字选项来满足一些特殊需求。下图所示是一些常用的套接字可选配置选项。 从图中可以看出套接字可选项是分层的。IPPROTO_IP层可选项是IP协议相关事项IPPROTO_TCP层可选项是TCP协议相关事项SOL_SOCKET层是套接字相关的通用可选项。
getsockopt setsockopt
针对上文所描述的套接字可选项可分别通过getsockopt函数和setsockopt函数来进行读取Get和设置Set有些选项可能仅支持一种操作。
读取套接字可选项
#include sys/socket.hint getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);成功时返回0失败时返回-1。
参数
sock要查看的套接字的文件描述符。level要查看的可选项的协议层。optname要查询的可选项的名称。optval指向一个缓冲区该缓冲区将接收查询的选项值。optlen向optval缓冲区传递的缓冲大小。调用成功后它会被设置为选项值的实际长度。
更改套接字可选项
#include sys/socket.hint setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);成功时返回0失败时返回-1。
参数
sock要更改的套接字的文件描述符。level要更改的可选项的协议层。optname要更改的可选项的名称。optval要更改的选项信息的缓冲选项值。optlen向optval传递的可选项信息的字节数。
示例程序
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/socket.hvoid error_handling(char *message);int main(int argc, char *argv[])
{int tcp_sock, udp_sock;int sock_type;socklen_t optlen;int state;optlen sizeof(sock_type);tcp_sock socket(PF_INET, SOCK_STREAM, 0);udp_sock socket(PF_INET, SOCK_DGRAM, 0);printf(SOCK_STREAM: %d \n, SOCK_STREAM);printf(SOCK_DGRAM: %d \n, SOCK_DGRAM);state getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void *)sock_type, optlen);if (state)error_handling(getsockopt() error!);printf(Socket type one: %d \n, sock_type);state getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void *)sock_type, optlen);if (state)error_handling(getsockopt() error!);printf(Socket type two: %d \n, sock_type);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}本程序分别生成TCP、UDP套接字输出创建TCP、UDP套接字时传入的SOCK_STREAM、SOCK_DGRAM。获取套接字类型信息如果是TCP套接字将获得SOCK_STREAM常数值1如果是UDP套接字则获得SOCK_DGRAM的常数值2。
运行结果 上述示例给出了调用getsockopt函数查看套接字信息的方法另外用于验证套接字类型的SO_TYPE是典型的只读可选项即套接字类型只能在创建时决定以后不能再更改。
SO_SNDBUF SO_RCVBUF
前文中我们提到套接字的输入输出缓冲区而SO_SNDBUF 和SO_RCVBUF便是与套接字缓冲区大小相关的两个可选项。通过这两个选项我们可以获取当前套接字的输入输出缓冲区大小或者设置相应缓冲区的大小。如下是这两个选项使用的相关示例代码。
get_buf.c
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/socket.hvoid error_handling(char *message);int main(int argc, char *argv[])
{int sock;int snd_buf, rcv_buf, state;socklen_t len;sock socket(PF_INET, SOCK_STREAM, 0);len sizeof(snd_buf);state getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)snd_buf, len);if (state)error_handling(getsockopt() error);len sizeof(rcv_buf);state getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)rcv_buf, len);if (state)error_handling(getsockopt() error);printf(Input buffer size: %d \n, rcv_buf);printf(Outupt buffer size: %d \n, snd_buf);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}运行结果 注每个人的电脑系统不同运行结果可能会有差异。 set_buf.c
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/socket.hvoid error_handling(char *message);int main(int argc, char *argv[])
{int sock;int snd_buf 1024 * 3, rcv_buf 1024 * 3;int state;socklen_t len;sock socket(PF_INET, SOCK_STREAM, 0);state setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)rcv_buf, sizeof(rcv_buf));if (state)error_handling(setsockopt() error!);state setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)snd_buf, sizeof(snd_buf));if (state)error_handling(setsockopt() error!);len sizeof(snd_buf);state getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)snd_buf, len);if (state)error_handling(getsockopt() error!);len sizeof(rcv_buf);state getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)rcv_buf, len);if (state)error_handling(getsockopt() error!);printf(Input buffer size: %d \n, rcv_buf);printf(Output buffer size: %d \n, snd_buf);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}运行结果 从运行结果可以看出对于缓冲大小的设置并非完全生效。实际上这些设置只是传递了我们的要求而最终的生效值操作系统会根据当前环境做出设置不过配置值的大小趋势和我们期望的一致。
SO_REUSEADDR
发生地址绑定错误Binding Error
我们之前介绍了回声服务器端/客户端的实现。其中服务器端代码稍作改变如下
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h
#include sys/socket.h#define TRUE 1
#define FALSE 0void error_handling(char *message);int main(int argc, char *argv[])
{int serv_sock, clnt_sock;char message[30];int option, str_len;socklen_t optlen, clnt_adr_sz;struct sockaddr_in serv_adr, clnt_adr;if (argc ! 2){printf(Usage : %s port\n, argv[0]);exit(1);}serv_sock socket(PF_INET, SOCK_STREAM, 0);if (serv_sock -1)error_handling(socket() error);/optlensizeof(option);optionTRUE;setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, option, optlen);/memset(serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family AF_INET;serv_adr.sin_addr.s_addr htonl(INADDR_ANY);serv_adr.sin_port htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr *)serv_adr, sizeof(serv_adr)))error_handling(bind() error );if (listen(serv_sock, 5) -1)error_handling(listen error);clnt_adr_sz sizeof(clnt_adr);clnt_sock accept(serv_sock, (struct sockaddr *)clnt_adr, clnt_adr_sz);while ((str_len read(clnt_sock, message, sizeof(message))) ! 0){write(clnt_sock, message, str_len);write(1, message, str_len);}close(clnt_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}客户端通过输入“Q”消息或是通过CTRLC终止程序两种方式客户端都会执行close函数向服务器端传递FIN消息经过四次挥手后断开连接。
现在考虑另一种情况如果服务器端和客户端在已建立连接的状态下向服务器端执行CTRLC终止程序会发生什么
这种情况服务器端会主动向客户端发送FIN消息断开连接并退出程序。此时如果再次以相同端口号启动服务器端则会发生错误bind()报错“Address already in use”通常需要等待1~4分钟才能再次运行服务器端。
客户端主动发送FIN消息断开连接不影响客户端或服务器端的再次运行而服务器端主动发送FIN消息断开连接则会影响服务器端的再次运行为什么会出现这种现象呢
TIME_WAIT 状态
四次挥手过程 从图中可以看出主动断开连接的主机先发送FIN消息会经过TIME_WAIT的状态持续时间为2MSLMaximum Segment Lifetime最长分节生命期时间为30s~2min。而处于TIME_WAIT状态时相应的端口号是正在使用状态因此若服务器端先断开连接则无法立即重新运行。 无论是服务器端还是客户端套接字都会有TIME_WAIT过程。先断开连接的套接字必然会经过TIME_WAIT过程。与服务器端不同客户端由于每次运行都会动态分配端口号因此不受TIME_WAIT状态的影响。 TIME_WAIT状态的存在有两个理由
可靠地实现TCP全双工连接的终止允许老的重复分节在网络中消逝
第一个理由可以假设上述四次握手过程最终的ACK丢失了来解释。主机B将重新发送它的最终那个FIN因此主机A必须维护状态信息以允许它重新发送最终那个ACK。如果主机A不维护状态信息它将以一个RST另外一种类型的TCP分节消息来响应该分节将被主机B解释为一个错误消息。如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流即全双工关闭那么它必须正确处理连接终止序列4个分节中任何一个分节丢失的情况。本例也说明了为什么执行主动关闭的那一端需要处于TIME_WAIT状态因为它可能不得不重传最终那个ACK。
为理解存在TIME_WAIT状态的第二个理由我们假设在12.106.32.254的1500端口和206.168.112.219的21端口之间有一个TCP连接。我们关闭这个连接过一段时间后在相同的IP地址和端口之间建立另一个连接。后一个连接称为前一个连接的化身incarnation因为它们的IP地址和端口号都相同。TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现从而被误解成属于同一连接的某个新的化身。为做到这一点TCP将不给处于TIME_WAIT状态的连接发起新的化身。既然TIME_WAIT状态的持续时间是MSL的2倍这就足以让某个方向上的分组最多存活MSL秒即被丢弃另一个方向上的应答最多存活MSL秒也被丢弃。通过实施这个规则我们就能保证每成功建立一个TCP连接时来自该连接先前化身的老的重复分组都已在网络中消逝了单向传输一个分节的最长生命周期是MSLTIME-WAIT状态的2MSL是考虑了一次双向信息交互的最长时间。比如最后的ACK丢失后来自对端重发的FIN消息也会在2MSL内消逝。
地址再分配
从上文的描述来看TIME_WAIT状态在可靠通信过程中似乎起到了重要的作用但它也有其自身的缺点。
比如下图的情况收到FIN消息的主机A发送ACK消息至主机B并启动Time-wait定时器如果网络状态不好致使ACK消息不断丢失则主机B重传FIN消息收到FIN消息的主机A将重启Time-wait定时器TIME-WAIT状态可能一直持续下去。 另一种情况考虑正在工作中的服务器突然故障停机而需要快速重启这时由于TIME_WAIT状态则必须等几分钟也会带来严重的影响。
针对以上TIME_WAIT状态所带来的影响可以通过配置可选项SO_REUSEADDR来解决。默认情况下SO_REUSEADDR选项处于关闭状态值为0假即无法分配处于TIME_WAIT状态下套接字端口。因此我们需要将该选项置为1真即可。
int opt_val 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)opt_val, sizeof(opt_val));SO_REUSEADDR可选项有效解决了以上问题UNP中也有这么一句描述“所有TCP服务器都应该指定本套接字选项以允许服务器在这种情形下被重新启动”。同时我们也应该意识到SO_REUSEADDR其实无视了TIME_WAIT状态的一些作用此时如果收到一些不期望的数据旧连接的分片可能会导致服务程序混乱不过这种可能性极低。
TCP_NODELAY
Nagle 算法
Nagle算法的出现是为了防止因数据包过多而导致的网络过载它应用于TCP层其作用如下图所示。 不难看出只有收到前一数据的ACK消息时Nagle算法才会发送下一数据。TCP套接字默认使用Nagle算法因此可以最大限度地进行缓冲直到收到ACK。 上图的演示中使用Nagle算法发送一个字符串消息需要传递4个数据包而不使用Nagle算法则需要传递10个数据包对网络流量Traffic网络负载或混杂程度产生了较大的影响。当然上图的演示只是一种极端的情况特定场景下字符串中的字符需要间隔一定的时间来传输至缓冲区实际程序中将字符串传输至缓冲区并非逐个字符进行的。 根据数据传输的特性网络流量未受太大影响时不使用Nagle算法反而更快。典型的场景就是“传输大文件数据”此时即使不使用Nagle算法也会在填满缓冲区时传输数据。这种情况并没有增加数据包的数量反而由于无需等待ACK而可以连续传输大大提高了传输速度。
是否使用Nagle算法需要根据使用与否对网络流量影响的差别大小确定。通常情况不使用Nagle算法可以提高传输速度。但为了保证网络流量在未准确判断数据特性时不应该禁止Nagle算法。
Nagle 算法的优缺点
优点
减少网络拥塞 通过减少小包的数量可以降低网络拥塞特别是在高延迟网络中。提高带宽利用率 通过发送更大的数据包可以更有效地利用可用带宽。
缺点
增加交互式应用的延迟 对于需要快速响应的交互式应用如远程登录或在线游戏Nagle算法可能会导致感知上的延迟因为它延迟了小数据包的发送。与某些应用协议不兼容 一些应用协议依赖于小数据包的快速传输Nagle算法可能会干扰这些协议的正常工作。
禁用 Nagle 算法
如果有必要就应禁用Nagle算法。 Nagle 算法使用与否在网络流量上差别不大。若使用了Nagle 算法传输速度更慢。 禁止Nagle算法的方法就是将套接字可选项TCP_NODELAY改为1真
int opt_val 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)opt_val, sizeof(opt_val));也可以查看Nagle算法的设置状态
int opt_val;
socklen_t opt_len sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)opt_val, opt_len);如果正在使用Nagle算法opt_val 0否则为 1。
基于 Windows 的实现
套接字可选项及其相关内容与操作系统无关。
#include winsock2.hint getsockopt(SOCKET sock, int level, int optname, char *optval, int *optlen);
int setsockopt(SOCKET sock, int level, int optname, const char *optval, int optlen);成功时返回0失败时返回 SOCKET_ERROR。
基于 Windows 的套接字类型示例程序
#include stdio.h
#include stdlib.h
#include string.h
#include winsock2.hvoid ErrorHanding(char *message);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET tcp_sock, udp_sock;int sock_type;int opt_len;int state;if (argc ! 2){printf(Usage : %s addr\n, argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0)ErrorHanding(WSAStartup() error!);tcp_sock socket(PF_INET, SOCK_STREAM, 0);udp_sock socket(PF_INET, SOCK_DGRAM, 0);printf(SOCK_STREAM: %d \n, SOCK_STREAM);printf(SOCK_DGRAM: %d \n, SOCK_DGRAM);opt_len sizeof(sock_type);state getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void *)sock_type, opt_len);if (state)ErrorHanding(getsockopt() error!);printf(Socket type one: %d \n, sock_type);state getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void *)sock_type, opt_len);if (state)ErrorHanding(getsockopt() error!);printf(Socket type two: %d \n, sock_type);closesocket(tcp_sock);closesocket(udp_sock);WSACleanup();return 0;
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}编译
gcc sock_type_win.c -lwsock32 -o sockTypeWin运行结果
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9sockTypeWin
Usage : sockTypeWin addrC:\Users\81228\Documents\Program\TCP IP Project\Chapter 9sockTypeWin 9190
SOCK_STREAM: 1
SOCK_DGRAM: 2
Socket type one: 1
Socket type two: 2基于 Windows 的套接字 I/O 缓冲大小示例程序
#include stdio.h
#include stdlib.h
#include string.h
#include winsock2.hvoid ErrorHanding(char *message);
void ShowSocketBufSize(SOCKET sock);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;int sndBuf, rcvBuf, state;if (argc ! 2){printf(Usage : %s addr\n, argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0)ErrorHanding(WSAStartup() error!);sock socket(PF_INET, SOCK_STREAM, 0);ShowSocketBufSize(sock);sndBuf rcvBuf 3 * 1024;state setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)sndBuf, sizeof(sndBuf));if (state SOCKET_ERROR)ErrorHanding(setsockopt() error!);state setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)rcvBuf, sizeof(rcvBuf));if (state SOCKET_ERROR)ErrorHanding(setsockopt() error!);ShowSocketBufSize(sock);closesocket(sock);WSACleanup();return 0;
}void ShowSocketBufSize(SOCKET sock)
{int sndBuf, rcvBuf, state, len;len sizeof(sndBuf);state getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)sndBuf, len);if (state SOCKET_ERROR)ErrorHanding(getsockopt() error!);len sizeof(rcvBuf);state getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)rcvBuf, len);if (state SOCKET_ERROR)ErrorHanding(getsockopt() error!);printf(Input buffer size: %d\n, rcvBuf);printf(Output buffer size: %d\n, sndBuf);
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}编译
gcc buf_win.c -lwsock32 -o bufWin运行结果
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9bufWin 9190
Input buffer size: 65536
Output buffer size: 65536
Input buffer size: 3072
Output buffer size: 3072基于 Windows 的 TCP_NODELAY 示例程序
#include stdio.h
#include stdlib.h
#include string.h
#include winsock2.hvoid ErrorHanding(char *message);int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;int opt_val;int opt_len;int state;if (argc ! 2){printf(Usage : %s addr\n, argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0)ErrorHanding(WSAStartup() error!);sock socket(PF_INET, SOCK_STREAM, 0);opt_len sizeof(opt_val);state getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)opt_val, opt_len);if (state)ErrorHanding(getsockopt() error!);printf(TCP_NODELAY value: %d \n, opt_val);opt_val 0;opt_len sizeof(opt_val);state setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)opt_val, opt_len);if (state)ErrorHanding(setsockopt() error!);opt_len sizeof(opt_val);state getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)opt_val, opt_len);if (state)ErrorHanding(getsockopt() error!);printf(TCP_NODELAY value: %d \n, opt_val);closesocket(sock);WSACleanup();return 0;
}void ErrorHanding(char *message)
{fputs(message, stderr);fputc(\n, stderr);exit(1);
}编译
gcc tcp_nodelay_win.c -lwsock32 -o nagleWin运行结果
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 9nagleWin 9190
TCP_NODELAY value: 6356992
TCP_NODELAY value: 0
习题
1下列关于Time-wait状态的说法错误的是
a. Time-wait状态只在服务器端的套接字中发生。 b. 断开连接的四次握手过程中先传输FIN消息的套接字将进入Time-wait状态。 c. Time-wait状态与断开连接的过程无关而与请求连接过程中SYN消息的传输顺序有关。 d. Time-wait状态通常并非必要应尽可能通过更改套接字可选项防止其发生。
答a、c、d。
2TCP_NODELAY可选项与Nagle算法有关可通过它禁用Nagle算法。请问何时应考虑禁用Nagle算法结合收发数据的特性给出说明。
根据数据传输的特性网络流量未受太大影响时可以考虑禁用Nagle算法。
典型的场景就是“传输大文件数据”此时即使不使用Nagle算法也会在填满缓冲区时传输数据。这种情况并没有增加数据包的数量反而由于无需等待ACK而可以连续传输大大提高了传输速度。
- 上一篇: 企业自己建网站怎么建江苏建设考试网官网
- 下一篇: 企业自助网站建设实名认证域名可以做电影网站吗
相关文章
-
企业自己建网站怎么建江苏建设考试网官网
企业自己建网站怎么建江苏建设考试网官网
- 技术栈
- 2026年03月21日
-
企业自己建网站怎么建广州安全教育平台网
企业自己建网站怎么建广州安全教育平台网
- 技术栈
- 2026年03月21日
-
企业注册平台windows优化大师会员兑换码
企业注册平台windows优化大师会员兑换码
- 技术栈
- 2026年03月21日
-
企业自助网站建设实名认证域名可以做电影网站吗
企业自助网站建设实名认证域名可以做电影网站吗
- 技术栈
- 2026年03月21日
-
企业做网站分哪几种推广运营策略
企业做网站分哪几种推广运营策略
- 技术栈
- 2026年03月21日
-
企业做网站维护价格深圳网站定制 开发
企业做网站维护价格深圳网站定制 开发
- 技术栈
- 2026年03月21日






