文章目录
- 套接字属性
- 套接字选项的核心级别
- SOL_SOCKET的常用选项
- IPPROTO_IP的常用选项
- 核心函数
- 应用场景
- demo
套接字属性
- 套接字选项即套接字属性,用于对套接字的行为、功能进行精细化控制
- 选项的操作分为获取和设置,部分选项仅可获取(如套接字状态、类型),部分选项既可获取也可设置(如缓冲区大小、保活机制)
- 选项按协议 / 作用范围划分不同级别,不同级别下包含专属的选项,操作时需指定对应级别
套接字选项的核心级别
- 级别是选项的作用域标识,操作选项时必须指定,核心常用级别及作用如下:
| 级别 | 作用范围 | 适用场景 |
|---|
| SOL_SOCKET | 套接字本身(通用级别) | 所有类型套接字的基础属性控制 |
| IPPROTO_IP | IPv4 协议层 | IPv4 相关的网络属性配置 |
| IPPROTO_IPV6 | IPv6 协议层 | IPv6 相关的网络属性配置 |
| IPPROTO_TCP | TCP 协议(流式套接字) | TCP 专属属性控制 |
| IPPROTO_UDP | UDP 协议(数据报套接字) | UDP 专属属性控制 |
| 其他(如 SOL_LRLMP) | 特定协议(如 IrDA) | 小众协议的专属配置 |
SOL_SOCKET的常用选项
- 最常用的基础级别,包含套接字的核心通用属性,重点选项如下(区分获取 / 设置权限):
| 选项名 | 说明 | 操作权限 | 核心用途 |
|---|
| SO_ACCEPTCONN | 套接字是否处于监听状态 | 仅获取 | 判断 listen 是否调用成功 |
| SO_BROADCAST | 允许发送广播数据 | 获取 / 设置 | UDP 广播通信开启 |
| SO_KEEPALIVE | 保活连接 | 获取 / 设置 | TCP 长连接保活,检测断连 |
| SO_LINGER | 延迟关闭连接 | 获取 / 设置 | 控制 close 时的连接关闭行为 |
| SO_RCVBUF/SO_SNDBUF | 接收 / 发送缓冲区大小 | 获取 / 设置 | 调整套接字读写缓冲区,优化 I/O |
| SO_REUSEADDR | 允许重用本地地址和端口 | 获取 / 设置 | 解决端口占用问题(如重启服务) |
| SO_TYPE | 获得套接字类型 | 仅获取 | 判断是 TCP(SOCK_STREAM)/UDP(SOCK_DGRAM) |
| SO_ERROR | 获得套接字错误码 | 仅获取 | 排查套接字运行中的错误 |
IPPROTO_IP的常用选项
| 选项名 | 说明 | 操作权限 |
|---|
| IP_ADD_MEMBERSHIP | 加入多播组 | 仅设置 |
| IP_OPTIONS | 设置 / 获取 IP 头部选项 | 获取 / 设置 |
| IP_HDRINCL | 自定义 IP 头部(原始套接字) | 获取 / 设置 |
| IP_TTL | 设置 / 获取 IP 生存时间 | 获取 / 设置 |
核心函数
#include<sys/socket.h>//获取选项:getsockoptintgetsockopt(intsockfd,intlevel,intoptname,void*optval,socklen_t*optlen);//设置选项:setsockoptintsetsockopt(intsockfd,intlevel,intoptname,constvoid*optval,socklen_toptlen);
参数
| 参数名 | 类型 | 作用 |
|---|
| sockfd | int | 要操作的套接字描述符(socket 创建的返回值) |
| level | int | 选项的级别(如 SOL_SOCKET、IPPROTO_IP) |
| optname | int | 要操作的具体选项名(如 SO_REUSEADDR、IP_TTL) |
| optval | void* | 存放选项值的缓冲区:getsockopt:内核将选项值写入该缓冲区;setsockopt:将待设置的值传入内核 |
| optlen | socklen_t* | getsockopt:传入缓冲区大小,返回实际读取的大小; setsockopt:直接传入缓冲区的实际大小(非指针) |
返回值
- 成功:返回0
- 失败:返回-1,错误码存入errno,常见错误码:
- EBADF:无效的套接字描述符
- EINVAL:未知的级别 / 选项名
- ENOTSOCK:描述符不是套接字(是普通文件 / 设备)
- ENOPROTOOPT:协议不支持该选项
应用场景
- 服务端重启时,设置SO_REUSEADDR解决端口占用问题
需在bind前设置,否则端口重用失效,是服务端开发的必设选项
- 高并发 / 大流量场景,调整SO_RCVBUF/SO_SNDBUF扩大读写缓冲区,减少 I/O 次数
- TCP 长连接(如 IM、物联网),设置SO_KEEPALIVE开启保活机制,自动检测断连
- UDP 广播通信,设置SO_BROADCAST允许发送广播包
- 排查问题时,通过SO_ERROR获取套接字错误码,通过SO_TYPE判断套接字类型
- 多播开发时,通过IP_ADD_MEMBERSHIP让套接字加入多播组
demo
#ifndef_NET_H_#define_NET_H_#include<stdio.h>#include<stdlib.h>#include<sys/socket.h>#include<netinet/in.h>#include<netinet/tcp.h>#include<arpa/inet.h>#include<unistd.h>#include<strings.h>#include<errno.h>typedefstructsockaddrAddr;typedefstructsockaddr_inAddr_in;#defineBACKLOG5#defineErrExit(msg)do{perror(msg);exit(EXIT_FAILURE);}while(0)voidArgment(intargc,char*argv[]);intCreateSocket(char*argv[]);intDataHandle(intfd);#endif
#include"net.h"voidArgment(intargc,char*argv[]){if(argc<3){fprintf(stderr,"%s<addr><port>\n",argv[0]);exit(0);}}intCreateSocket(char*argv[]){/*创建套接字*/intfd=socket(AF_INET,SOCK_STREAM,0);if(fd<0)ErrExit("socket");/*允许地址快速重用*/intflag=1;if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag)))perror("setsockopt");/*设置通信结构体*/Addr_in addr;bzero(&addr,sizeof(addr));addr.sin_family=AF_INET;addr.sin_port=htons(atoi(argv[2]));/*绑定通信结构体*/if(bind(fd,(Addr*)&addr,sizeof(Addr_in)))ErrExit("bind");/*设置套接字为监听模式*/if(listen(fd,BACKLOG))ErrExit("listen");returnfd;}intDataHandle(intfd){charbuf[BUFSIZ]={};Addr_in peeraddr;socklen_tpeerlen=sizeof(Addr_in);if(getpeername(fd,(Addr*)&peeraddr,&peerlen))perror("getpeername");intret=recv(fd,buf,BUFSIZ,0);if(ret<0)perror("recv");if(ret>0){printf("[%s:%d]data: %s\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port),buf);}returnret;}
#include"net.h"#include<sys/select.h>#defineMAX_SOCK_FD1024voidsetKeepAlive(intsockfd,intattr_on,socklen_tidle_time,socklen_tinterval,socklen_tcnt){setsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(constchar*)&attr_on,sizeof(attr_on));setsockopt(sockfd,SOL_TCP,TCP_KEEPIDLE,(constchar*)&idle_time,sizeof(idle_time));setsockopt(sockfd,SOL_TCP,TCP_KEEPINTVL,(constchar*)&interval,sizeof(interval));setsockopt(sockfd,SOL_TCP,TCP_KEEPCNT,(constchar*)&cnt,sizeof(cnt));}intmain(intargc,char*argv[]){inti,ret,fd,newfd;fd_set set,tmpset;Addr_in clientaddr;socklen_tclientlen=sizeof(Addr_in);/*检查参数,小于3个 直接退出进程*/Argment(argc,argv);/*创建已设置监听模式的套接字*/fd=CreateSocket(argv);FD_ZERO(&set);FD_ZERO(&tmpset);FD_SET(fd,&set);while(1){tmpset=set;if((ret=select(MAX_SOCK_FD,&tmpset,NULL,NULL,NULL))<0){perror("select");getchar();}if(FD_ISSET(fd,&tmpset)){/*接收客户端连接,并生成新的文件描述符*/if((newfd=accept(fd,(Addr*)&clientaddr,&clientlen))<0){perror("accept");getchar();}#if1intkeepAlive=1;//设定KeepAliveintkeepIdle=5;//开始首次KeepAlive探测前的TCP空闭时间intkeepInterval=5;//两次KeepAlive探测间的时间间隔intkeepCount=3;//判定断开前的KeepAlive探测次数setKeepAlive(newfd,keepAlive,keepIdle,keepInterval,keepCount);#endifprintf("[%s:%d]已建立连接\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));FD_SET(newfd,&set);}else{//处理客户端数据for(i=fd+1;i<MAX_SOCK_FD;i++){if(FD_ISSET(i,&tmpset)){if(DataHandle(i)<=0){if(getpeername(i,(Addr*)&clientaddr,&clientlen))perror("getpeername");printf("[%s:%d]断开连接\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));FD_CLR(i,&set);close(i);}}}}}close(fd);return0;}