1 基本概念
- 设置套接字的选项对套接字进行控制
- 除了设置选项外,还可以获取选项
- 选项的概念相当于属性,所以套接字选项也可说是套接字属性
- 有些选项(属性)只可获取,不可设置;
- 有些选项既可设置也可获取
2 选项的级别
一些选项都是针对一种特定的协议
一些选项适用于所有类型的套接字
选项级别(level)的概念
2.1 常用的级别
SOL_SOCKET | 该级别的选项只作用于套接字本身 |
---|---|
SOL_LRLMP | 该级别的选项作用于IrDA协议 |
IPPROTO_IP | 该级别的选项作用于IPv4协议 |
IPPROTO_IPV6 | 该级别的选项作用于IPv6协议 |
IPPROTO_RM | 该级别的选项作用于可靠的多播传输 |
IPPROTO_TCP | 该级别的选项适用于流式套接字 |
IPPROTO_UDP | 该级别的选项适用于数据报套接字 |
2.2 SOL_SOCKET的常用选项
选项名称 | 说明 | 获取/设置 |
---|---|---|
SO_ACCEPTCONN | 套接字是否处于监听状态 | 获取 |
SO_BROADCAST | 允许发送广播数据 | 两者都可 |
SO_DEBUG | 允许调试 | 两者都可 |
SO_DONTROUTE | 不查找路由 | 两者都可 |
SO_ERROR | 获得套接字错误 | 获取 |
SO_KEEPALIVE | 保活连接 | 两者都可 |
SO_LINGER | 延迟关闭连接 | 两者都可 |
SO_OOBINLINE | 带外数据放入正常数据流 | 两者都可 |
SO_RCVBUF | 接收缓冲区大小 | 两者都可 |
SO_SNDBUF | 发送缓冲区大小 | 两者都可 |
SO_REUSERADDR | 允许重用本地地址和端口 | 两者都可 |
SO_TYPE | 获得套接字类型 | 获取 |
2.3 IPPROTO_IP级别的常用选项
选项名称 | 说明 | 获取/设置 |
---|---|---|
IP_OPTIONS | 获取或设置IP头部内的选项 | 两者都可 |
IP_HDRINCL | 是否将IP头部与数据一起提交给Winsock函数 | 两者都可 |
IP_TTL | IP TTL相关 | 两者都可 |
3获取套接字选项
3.1 getsockopt函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
参数
sockfd:套接字描述符
level:表示选项的级别
optname:表示要获取的选项名称
optval:指向存放接收到的选项内容的缓冲区
optlen:指向optval所指缓冲区的大小
函数返回值:
执行成功返回0
,否则返回‒1
,errno
来获取错误码
常见的错误码:
EBADF:参数sockfd不是有效的文件描述符
EFAULT:参数optlen太小或optval所指缓冲区非法
EINVAL:参数level未知或非法
ENOPROTOOPT:选项未知或不被指定的协议族所支持
ENOTSOCK:描述符不是一个套接字描述符
3.2 示例:获取流套接字和数据报套接字接收和发送的(内核)缓冲区大小
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>int main()
{int err,s = socket(AF_INET, SOCK_STREAM, 0);//创建流套接字if (s == -1) {printf("Error at socket()\n");return -1;}int su = socket(AF_INET, SOCK_DGRAM, 0); //创建数据报套接字if (s == -1) {printf("Error at socket()\n");return -1;}int optVal;int optLen = sizeof(optVal);//获取流套接字接收缓冲区大小if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&optVal,(socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);elseprintf("Size of stream socket receive buffer: %ld bytes\n", optVal);//获取流套接字发送缓冲区大小if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)&optVal,(socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);else printf("Size of streaming socket send buffer: %ld bytes\n", optVal);//获取数据报套接字接收缓冲区大小if (getsockopt(su, SOL_SOCKET, SO_RCVBUF, (char*)&optVal,(socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);elseprintf("Size of datagram socket receive buffer: %ld bytes\n", optVal);//获取数据报套接字发送缓冲区大小if (getsockopt(su, SOL_SOCKET, SO_SNDBUF, (char*)&optVal,(socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);elseprintf("Size of datagram socket send buffer:%ld bytes\n", optVal);getchar();return 0;
}
3.3 示例:获取当前套接字类型
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>int main()
{int err;int s = socket(AF_INET, SOCK_STREAM, 0); //创建流套接字if (s == -1) {printf("Error at socket()\n");return -1;}int su = socket(AF_INET, SOCK_DGRAM, 0); //创建数据报套接字if (s == -1) {printf("Error at socket()\n");return -1;}int optVal;int optLen = sizeof(optVal);//获取套接字s的类型if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);else{if (SOCK_STREAM == optVal) // SOCK_STREAM宏定义值为1printf("The current socket is a stream socket.\n"); //当前套接字是流套接字else if (SOCK_DGRAM == optVal) // SOCK_ DGRAM宏定义值为2printf("The current socket is a datagram socket.\n");//当前套接字是数据报套接字}//获取套接字su的类型if (getsockopt(su, SOL_SOCKET, SO_TYPE, (char*)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed:%d", errno);else{if (SOCK_STREAM == optVal) // SOCK_STREAM宏定义值为1printf("The current socket is a stream socket.\n");else if (SOCK_DGRAM == optVal) // SOCK_ DGRAM宏定义值为2printf("The current socket is a datagram socket.\n");}getchar();return 0;
}
3.4 示例:判断套接字是否处于监听状态
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[])
{Addr_in service;if(argc < 3){printf("%s[ADDR][PORT]\n", argv[0]);exit(0);}int s = socket(AF_INET, SOCK_STREAM, 0); //创建一个流套接字if (s == -1) ErrExit("socket");//允许地址的立即重用char on = 1;setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));service.sin_family = AF_INET;service.sin_addr.s_addr = inet_addr(argv[1]);service.sin_port = htons( atoi(argv[2]) );if (bind(s, (Addr*)&service, sizeof(service)) == -1) //绑定套接字ErrExit("bind");int optVal;int optLen = sizeof(optVal);//获取选项SO_ACCEPTCONN的值if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char*)&optVal, (socklen_t*)&optLen) == -1)printf("getsockopt failed:%d",errno);else printf("Before listening, The value of SO_ACCEPTCONN:%d, The socket is not listening\n", optVal);// 开始侦听if (listen(s, 100) == -1)ErrExit("listen");//获取选项SO_ACCEPTCONN的值if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char*)&optVal, (socklen_t*)&optLen) == -1)ErrExit("getsockopt");else printf("After listening,The value of SO_ACCEPTCONN:%d, The socket is listening\n", optVal);return 0;
}
4 设置套接字选项
4.1 setsockopt函数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数
sockfd:套接字描述符
level:表示选项的级别
optname:表示要获取的选项名称
optval:指向存放接收到的选项内容的缓冲区
optlen:指向optval所指缓冲区的大小
函数返回值:
执行成功返回0
,否则返回‒1
,errno
来获取错误码
4.2 示例:启用套接字的保活机制**
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[])
{Addr_in service;if(argc < 3){printf("%s[ADDR][PORT]\n", argv[0]);exit(0);}int s = socket(AF_INET, SOCK_STREAM, 0); //创建一个流套接字if( s < 0)ErrExit("socket");char on = 1;setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));service.sin_family = AF_INET;service.sin_addr.s_addr = inet_addr(argv[1]);service.sin_port = htons(atoi(argv[2]));if (bind(s, (Addr *) &service, sizeof(service)) == -1) //绑定套接字ErrExit("bind");int optVal = 1;//一定要初始化int optLen = sizeof(int);//获取选项SO_KEEPALIVE的值if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*)&optVal, (socklen_t *)&optLen) == -1)ErrExit("getsockopt");else printf("After listening,the value of SO_ACCEPTCONN:%d\n", optVal);optVal = 1;if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*)&optVal, optLen) != -1)printf("Successful activation of keep alive mechanism.\n");//启用保活机制成功if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*)&optVal, (socklen_t *)&optLen) == -1)ErrExit("getsockopt");else printf("After setting,the value of SO_KEEPALIVE:%d\n", optVal);return 0;
}
5 综合示例
server.c
#include "net.h"
#include <sys/select.h>
#define MAX_SOCK_FD 1024void setKeepAlive (int sockfd, int attr_on, socklen_t idle_time, socklen_t interval, socklen_t cnt)
{setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &attr_on, sizeof (attr_on));setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (const char *) &idle_time, sizeof (idle_time));setsockopt (sockfd, SOL_TCP, TCP_KEEPINTVL, (const char *) &interval, sizeof (interval));setsockopt (sockfd, SOL_TCP, TCP_KEEPCNT, (const char *) &cnt, sizeof (cnt));
}int main(int argc, char *argv[])
{int i, ret, fd, newfd;fd_set set, tmpset;Addr_in clientaddr;socklen_t clientlen = 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();}
#if 1int keepAlive = 1; //设定KeepAliveint keepIdle = 5; //开始首次KeepAlive探测前的TCP空闭时间int keepInterval = 5; //两次KeepAlive探测间的时间间隔int keepCount = 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);return 0;
}
socket.c
#include "net.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0)ErrExit("socket");/*允许地址快速重用*/int flag = 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");return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] = {};Addr_in peeraddr;socklen_t peerlen = sizeof(Addr_in);if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )perror("getpeername");int ret = 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);}return ret;
}
net.h
`#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>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif
通过wireshark查看设置keepalive的包