setsockopt函数使用

本文介绍了socket函数原型,包括sockfd、level、optname等参数含义。同时阐述了其多种使用场景,如重用socket、避免TIME_WAIT、设置收发时限和缓冲区等,还提及提高性能、广播特性、存活检测、延迟接收和关闭等方面的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数原型

#include <sys/types.h >
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  • sockfd:标识一个套接口的描述字
  • level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
  • optname:需设置的选项,而有部分选项需在listen/connect调用前设置才有效,这部分选项如下:SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG、TCP_NODELAY
  • optval:指针,指向存放选项值的缓冲区
  • optlen:optval缓冲区长度

使用场景

1. 重用socket

如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用close(socket)(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:

bool reuse=1;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(bool));

注意:必须在调用bind函数之前设置SO_REUSEADDR选项。

2. 不经历TIME_WAIT

如果要已经处于连接状态的soket在调用close(socket)后强制关闭,不经历TIME_WAIT的过程:

int reuse=0;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));

3. 设置收发时限

在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:

int nNetTimeout=1000; // 1秒
// 发送时限
setsockopt(socket,SOL_S0CKET, SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
// 接收时限
setsockopt(socket,SOL_S0CKET, SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4. 设置收发缓冲区

在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:

// 接收缓冲区
int nRecvBuf=32*1024; // 设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
// 发送缓冲区
int nSendBuf=32*1024; // 设置为32K
setsockopt(fd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

注意:并不是说你设置的多大,系统就会设置多大,系统一般会将我们设置的缓冲区大小加倍,并且不得小于tcp的接收缓冲区和发送缓冲区设置的默认最小值。

5. 在发送数据的时,不执行由系统缓冲区到socket缓冲区的拷贝,以提高程序的性能:

int nZero=0;
setsockopt(fd,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(int));

6. 在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区:

int nZero=0;
setsockopt(fd,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7. 使socket发送的数据具有广播特性

一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

bool bBroadcast = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(bool));

8. 存活检测

int opt = 1;
if (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(int)) == -1)
{
    return 0;
}

9. 延迟接收

实际上就是当接收到第一个数据之后,才会创建连接。对于像http这类非交互式的服务器,这个很有意义,可以防御空连接攻击。

int val = 5;
setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val));

打开这个功能后,内核在val时间之类还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
从三次握手上讲,就是设置这个状态之后,就算完成了三次握手,服务器socket状态也不是ESTABLISHED,而依然是 SYN_RCVD,不会去接收数据。

10. 延迟socket关闭

linger:徘徊、延迟。
在默认情况下,当调用close关闭socke的使用,close会立即返回,但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回。
SO_LINGER选项就是为了延迟socket关闭。

#include <sys/socket.h>
struct linger {
      int l_onoff  //0=off, nonzero=on(开关)
      int l_linger //linger time(延迟时间)
}
struct linger Linger;
Linger.l_onoff = 1; //在调用close(socket)时还有数据未发送完,允许等待
// 若Linger.l_onoff=0;则调用closesocket()后强制关闭
Linger.l_linger = 5; //设置等待时间为5秒
setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char*)&Linger, sizeof(struct linger));
<think>我们被要求回答关于使用setsockopt函数设置IPV6_PKTINFO选项的问题。根据引用[3]我们知道setsockopt函数用于设置套接字选项,而IPV6_PKTINFO是IPv6协议层的一个选项。 IPV6_PKTINFO选项用于在发送或接收IPv6数据包时获取或设置数据包的信息,比如源地址和发送接口索引。这在多宿主主机(多个网络接口)上特别有用,可以指定从哪个接口发送数据包,或者获取接收数据包的接口。 在发送时,我们可以通过设置IPV6_PKTINFO选项来指定发送数据包的源地址和接口索引。在接收时,我们可以通过获取这个选项来得知数据包到达的接口和目的地址。 下面是一个使用setsockopt设置IPV6_PKTINFO选项的示例(用于发送时指定源地址和接口): ```c #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip6.h> // 包含in6_pktinfo结构体 int main() { int sockfd = socket(AF_INET6, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); return 1; } // 准备要设置的数据包信息 struct in6_pktinfo pktinfo; memset(&pktinfo, 0, sizeof(pktinfo)); // 设置发送的源地址(可选,如果不设置,则使用默认地址) // 假设我们有一个源地址in6addr_src,这里用in6addr_any表示任意地址,实际使用中应指定 pktinfo.ipi6_addr = in6addr_any; // 或者指定具体的IPv6地址 // 设置发送的接口索引(必须,0表示未指定,通常需要指定) pktinfo.ipi6_ifindex = 2; // 假设接口索引为2 // 设置IPV6_PKTINFO选项 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo)) < 0) { perror("setsockopt IPV6_PKTINFO"); close(sockfd); return 1; } // 然后就可以使用sendto等函数发送数据了 // 注意:在发送时,sendto指定的目标地址是目的地址,而源地址由pktinfo指定(如果非零地址) close(sockfd); return 0; } ``` 注意:在发送时,如果指定了源地址,那么该源地址必须是分配给本地某个接口的地址。如果指定了接口索引,则数据包将从该接口发送,并且源地址也可以由接口索引隐含(如果设置源地址为in6addr_any,则系统会自动选择该接口上的一个合适地址)。 另外,我们也可以设置套接字以在接收数据包时返回IPV6_PKTINFO信息。这需要先设置IPV6_RECVPKTINFO选项(或IPV6_PKTINFO,取决于系统,但通常使用IPV6_RECVPKTINFO来启用接收时的控制信息)为1,然后在recvmsg时使用辅助数据(ancillary data)来获取in6_pktinfo结构。 设置接收时返回IPV6_PKTINFO: ```c int on = 1; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) { perror("setsockopt IPV6_RECVPKTINFO"); } ``` 然后在接收数据时,使用recvmsg函数,并解析控制消息。当收到数据包时,控制消息中如果包含IPV6_PKTINFO,则可以通过它获取到达的接口索引和目的地址。 引用说明: - 引用[3]指出setsockopt用于设置套接字选项,我们使用它设置IPV6_PKTINFO选项。 - 引用[1]提到对于ICMPv6原始套接字,内核会计算校验和,但这里我们使用的是UDP套接字,所以不涉及。 注意事项: - 需要包含必要的头文件:<sys/socket.h>, <netinet/in.h>, <netinet/ip6.h>(注意:有些系统可能使用<netinet/in6.h>) - 结构体in6_pktinfo的定义在<netinet/ip6.h>或<netinet/in6.h>中 - 接口索引可以通过if_nametoindex函数获取,例如根据接口名(如"eth0")得到索引 最后,生成相关问题:</think>### 使用 `setsockopt` 设置 `IPV6_PKTINFO` 的完整指南 `IPV6_PKTINFO` 选项用于在 IPv6 套接字操作中获取或设置数据包的关键信息,包括源地址和接收接口索引。以下是详细使用方法: #### 1. **核心结构体定义** ```c #include <netinet/in.h> struct in6_pktinfo { struct in6_addr ipi6_addr; // 源/目的 IPv6 地址 unsigned int ipi6_ifindex; // 发送/接收接口索引 }; ``` #### 2. **发送时设置数据包信息** ```c int sockfd = socket(AF_INET6, SOCK_DGRAM, 0); struct in6_pktinfo pktinfo; // 指定源地址(可选) inet_pton(AF_INET6, "2001:db8::1", &pktinfo.ipi6_addr); // 指定发送接口(必须) pktinfo.ipi6_ifindex = if_nametoindex("eth0"); // 设置IPV6_PKTINFO选项 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo)) < 0) { perror("setsockopt failed"); } ``` > 发送数据包时,内核将使用指定的接口和源地址[^3] #### 3. **接收时获取数据包信息** ```c // 启用控制信息接收 int on = 1; setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); // 接收数据时使用recvmsg struct msghdr msg = {0}; struct cmsghdr *cmsg; char buffer[4096]; char ctrl_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); recvmsg(sockfd, &msg, 0); // 解析控制信息 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *info = (struct in6_pktinfo *)CMSG_DATA(cmsg); printf("Received via interface %d\n", info->ipi6_ifindex); char addr_str[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &info->ipi6_addr, addr_str, sizeof(addr_str)); printf("Destination address: %s\n", addr_str); } } ``` #### 关键应用场景 1. **多宿主主机** 指定从特定接口发送数据包: ```c pktinfo.ipi6_ifindex = if_nametoindex("wlan0"); setsockopt(sockfd, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo)); ``` 2. **源地址选择** 避免使用默认地址: ```c inet_pton(AF_INET6, "2001:db8::cafe", &pktinfo.ipi6_addr); ``` 3. **接收端信息获取** 识别数据包到达的接口和目的地址[^5] #### 注意事项 - **头文件需求**:`#include <netinet/in.h>`, `#include <sys/socket.h>` - **接口索引获取**:使用 `if_nametoindex("eth0")` 转换接口名 - **作用域**:仅影响后续 `sendmsg` 调用,不影响 `connect` 绑定的地址 - **原始套接字**:需自行构建IPv6头部[^1] - **Windows差异**:选项名为 `IPV6_PKTINFO`,但需使用 `WSASendMsg`[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值