lv8 嵌入式开发-网络编程开发 17 套接字属性设置

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函数

c 复制代码
#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 示例:获取流套接字和数据报套接字接收和发送的(内核)缓冲区大小

c 复制代码
#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 示例:获取当前套接字类型

c 复制代码
#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 示例:判断套接字是否处于监听状态

c 复制代码
#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函数

c 复制代码
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 示例:启用套接字的保活机制**

c 复制代码
#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

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

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

c 复制代码
`#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的包

相关推荐
律宏阔2 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔2 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
Zwawa2 小时前
把按键和 LED 状态显示出来:第一次看见程序到底在干什么
单片机·嵌入式
穷人小水滴2 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
小程序设计3 小时前
基于STM32的办公室智能空气净化系统设计
stm32·单片机
乱码三千3 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei373 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei373 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei373 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW3 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux