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);
else
printf("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);
else
printf("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);
else
printf("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宏定义值为1
printf("The current socket is a stream socket.\n"); //当前套接字是流套接字
else if (SOCK_DGRAM == optVal) // SOCK_ DGRAM宏定义值为2
printf("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宏定义值为1
printf("The current socket is a stream socket.\n");
else if (SOCK_DGRAM == optVal) // SOCK_ DGRAM宏定义值为2
printf("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 1024
void 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 1
int keepAlive = 1; //设定KeepAlive
int keepIdle = 5; //开始首次KeepAlive探测前的TCP空闭时间
int keepInterval = 5; //两次KeepAlive探测间的时间间隔
int keepCount = 3; //判定断开前的KeepAlive探测次数
setKeepAlive (newfd, keepAlive, keepIdle, keepInterval, keepCount);
#endif
printf("[%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的包