setsockopt()
setsockopt()
是一个用于设置套接字选项的 Unix
系统调用。它允许程序员修改套接字的行为,以适应不同的网络环境和应用程序需求。
1. 函数介绍
函数原型:
#include <sys/socket.h>
/**
* level:选项所在的协议层。例如,SOL_SOCKET 表示套接字层,IPPROTO_TCP 或 IPPROTO_IP 表示 TCP 或 IP 层。
* optname:要设置的选项的名称。每个协议层都定义了自己的一组选项。
* optval:指向一个变量的指针,该变量包含选项的新值。这个变量的类型取决于 optname 的值。
* @return 成功(0);失败(-1)
*/
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
使用实例:
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt(SO_REUSEADDR)");
// 错误处理
}
2.常见参数介绍
2.1 SO_REUSEADDR
1. 介绍TIME_WAIT
四次挥手:
当服务器和客户端建立连接以后,如果服务器关闭了客户端的连接,服务器处于TIME_WAIT状态。此时关闭服务器,再次开启服务器会发现bind()
函数调用失败。
也就是,服务器关闭后短时间内无法再次打开。
2. 测试代码
1.服务器端:
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#define SERV_PORT 9000
int main(int argc,char** argv)
{
int listenfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret=0;
int opt = 1;
//setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ret = bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret==-1){
printf("bind()函数调用失败\n");
return -1;
}
ret = listen(listenfd,32);
if(ret==-1){
printf("listen()函数调用失败\n");
return -1;
}
int connfd;
const char* pcontent = "I sent sth to client!";
for(;;){
printf("服务器进入等待状态!\n");
connfd = accept(listenfd,(struct sockaddr*)NULL,NULL);
int n = write(connfd,pcontent,strlen(pcontent));
printf("数据写入成功,connfd=%d,写入数据量为%d\n",connfd,n);
close(connfd);
}
close(listenfd);
return 0;
}
2.客户端:
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#define SERV_PORT 9000 //要连接到的服务器端口,服务器必须在这个端口上listen着
int main(int argc, char *const *argv)
{
//这些演示代码的写法都是固定套路,一般都这么写
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建客户端的socket,大家可以暂时不用管这里的参数是什么,知道这个函数大概做什么就行
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
//设置要连接到的服务器的信息
serv_addr.sin_family = AF_INET; //选择协议族为IPV4
serv_addr.sin_port = htons(SERV_PORT); //连接到的服务器端口,服务器监听这个地址
if(inet_pton(AF_INET,"192.168.0.126",&serv_addr.sin_addr)<=0)
{
printf("调用inet_pton()失败,退出!\n");
exit(-1);
}
if(connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)
{
printf("调用connect()失败,退出!\n");
exit(-1);
}
printf("client开始读取数据!\n");
int n;
char recvline[1000+1];
while(( n = read(sockfd,recvline,1000)) > 0)
{
printf("读取的数据数目为%d\n",n);
recvline[n]=0;
printf("收到的内容为:%s\n",recvline);
}
close(sockfd);
printf("程序执行完毕,退出!\n");
return 0;
}
2.2 SO_REUSEPORT
1.多进程端口复用
在多进程开发中,我们可以使用多个进程监听同一个IP:PORT,由操作系统分配传入的数据包,充分利用多核CPU,从而提高了整体的系统性能和吞吐量。
2.代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
void create_and_listen(int port) {
int listen_fd;
struct sockaddr_in addr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 没有设置 SO_REUSEPORT 选项
/*
int opt = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT , &opt, sizeof(opt)) < 0) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
*/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
close(listen_fd);
exit(EXIT_FAILURE);
}
if (listen(listen_fd, SOMAXCONN) < 0) {
perror("listen");
close(listen_fd);
exit(EXIT_FAILURE);
}
printf("Listening on port %d\n", port);
while (1) {
int conn_fd = accept(listen_fd, NULL, NULL);
if (conn_fd < 0) {
perror("accept");
close(listen_fd);
exit(EXIT_FAILURE);
}
// 处理连接的代码
close(conn_fd);
}
close(listen_fd);
}
int main() {
int port = 5000;
for (int i = 0; i < 4; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
create_and_listen(port);
exit(0);
}
}
// 父进程等待子进程退出
while (wait(NULL) > 0);
return 0;
}
没有开启SO_REUSEPORT
的时候:
只有一个进程可以绑定到这个套接字。
开启后:
有多个进程可以绑定到这个套接字。