1.套接字的可选项
- 创建套接字后,可以修改套接字特性
- 套接字可选项分为 SOL_SOCKET, IPPROTO_IP, IPPROTO_TCP三层
2.相关函数
- getsockopt
#include<sys/socket.h>
// 功能:获取套接字可选项的信息
// 参数:
// sock--用于查看选项套接字文件描述符
// level--要查看的可选项的协议层
// optname--要查看的可选项名
// optval--保存要查看结果的缓冲地址值
// optlen--向第四个参数 optval 传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数
// 返回值:成功时返回 0,失败时返回 -1
int getsockopt(int sock, int level,int optname, void* optval, socklen_t* optlen);
- setsockopt
#include<sys/socket.h>
// 功能:设置套接字可选项
// 参数:
// sock--用于更改可选项的套接字文件描述符
// level--要更改的可选项协议层
// optname--要更改的可选项名
// optval--保存要更改的选项信息的缓冲地址值
// optlen--向第四个参数 optval 传递的可选项信息的字节数
// 返回值:成功时返回 0,失败时返回 -1
int setsockopt(int sock, int level, int optname, const void* optval, socklen_t optlen);
3.获取 sock_type
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int tcp_sock, udp_sock;
int sock_type;
socklen_t 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);
int 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;
}
运行结果:sock_type 只能获取,不能设置;套接字类型只能在创建时指定,不能修改。
4.获取并修改套接字缓冲大小
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int sock;
int snd_buf, rcv_buf;
sock = socket(PF_INET, SOCK_STREAM, 0);
socklen_t len = sizeof(snd_buf);
int 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("Default input buffer size: %d \n", rcv_buf);
printf("Default output buffer size: %d \n", snd_buf);
snd_buf = 1024 * 3;
rcv_buf = 1024 * 3;
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("After setting, input buffer size: %d \n", rcv_buf);
printf("After setting, output buffer size: %d \n", snd_buf);
return 0;
}
运行结果:设置的IO缓冲大小和指定的3K不一致,这个是由系统自动调整的
5.Time-wait 状态
- 通信中,一般是客户端先请求断开链接,向服务器端发送 FIN 并经过四次“挥手”过程,然后会进入 time-wait 状态;输入 ctrl+c 强制结束程序,此时由操作系统关闭文件和套接字,相当于调用 close 函数,也会发送 FIN。
- 如果是服务器端在通信过程中强制结束程序,立即再次使用同一端口运行服务器端程序,将会输入“bind() error",无法再次运行。等待几分钟才可再次运行。
- 原因分析:先断开连接的(先发送FIN)主机会经过 Time-wait 状态,此时相应端口仍是在使用状态,调用 bind() 使用同一端口会出错
- 客户端套接字的端口号是自动分配的,故无需考虑客户端的 Time-wait 状态
- 为什么会有 Time-wait 状态?假设主机A进行发送最后一个 ACK 包后立即消除套接字,但这条 ACK 消息在传输过程中丢失,未能传输给主机B,主机B会以为它之前发送的消息没有送达主机A,试图重传,但此时A已经完全终止,无法接收主机B最后传来的 ACK。有了 Time-wait 状态,主机A就会像主机B重传最后的 ACK 消息,主机B也可以正常终止。
- 以下是回声服务器/客户端通信过程,可以发现服务器端强行结束程序后,立即再次运行,会报错
6.Time-wait 状态下的端口号再分配,修改 SO_REUSEADDR 选项
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
char message[30];
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");
int option = 1;
socklen_t optlen = sizeof(option);
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
socklen_t clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
error_handling("accept() error");
int str_len;
// 收到客户端的消息后,将消息回送给客户端
while ((str_len = read(clnt_sock, message, sizeof(message))) != 0) {
write(clnt_sock, message, str_len);
write(1, message, str_len);
}
close(clnt_sock);
close(serv_sock);
return 0;
}
运行结果:可以看出,即使服务器端强行结束程序,立即重新执行程序,没有报错
7.Naggle算法
- TCP 默认使用 Naggle 算法交换数据,因此最大限度地进行缓冲,直到收到 ACK
- 传输大文件数据时最好禁用 Naggle 算法,以提高传输速度
- 只需把 TCP_NODELAY 改为 1 即可禁用 Naggle 算法
参考书籍:《TCP/IP网络编程》尹圣雨 著,金果哲 译