13.4 Linux_网络编程_套接字属性
概述
什么是选项的级别:
socket中可以设置的属性种类很多,比如socke的选项、传输层TCP/UDP的选项、数据链路层的选项。这些选项在不同的层级,这就是选项的级别。常用级别及含义如下:
级别 | 含义 |
SOL_SOCKET | 作用于套接字本身 |
IPPROTO_IP | 作用于IPv4协议 |
IPPROTO_TCP | 作用于流式套接字 |
IPPROTO_UDP | 作用于数据报套接字 |
SOL_SOCKET级别的常用选项:
IPPROTO_IP级别的常用选项:
相关函数
//获取套接字选项
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
//设置套接字选项
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
返回值:成功返回0,失败返回-1
sockfd:套接字的文件描述符
level:选项的级别
optname:要获取/设置哪一个选项
optval:获取/设置的选项存放在哪
optlen:选项存放的空间的大小
保活连接实验
什么是保活链接:
保活链接指的是在TCP通信中,为了防止长时间不交互而连接被关闭的情况,服务器会向客户端发送一些数据,以确保连接正常。当到达设置的间隔时,会发出第一个保活数据,如果服务器没有收到客户端的回应,之后会继续发出。当到达设置的发送次数依旧没有得到回应,那么服务器就会中断与客户端的连接。
保活连接包括:是否开启、未通讯多久发送第一次保活数据、之后发送的间隔、发送几次未响应中断连接。与之相对应的选项如下:
含义 | 选项级别 | 选项 | 设置值 |
是否开启保活连接 | SOL_SOCKET | SO_KEEPALIVE | 1开启0关闭 |
未通讯多久发送第一次保活数据 | SOL_TCP | TCP_KEEPIDLE | 单位s 默认7200s(2h) |
之后发送的间隔 | SOL_TCP | TCP_KEEPINTVL | 单位s 默认75s |
发送几次未响应中断连接 | SOL_TCP | TCP_KEEPCNT | 默认9次 |
server.c代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <netinet/tcp.h>
#define BACKLOG 5 //最大接入客户端数量
int socket_init(char** argv);
void getKeepAlive(int fd);
void setKeepAlive(int fd);
int main(int argc ,char** argv){
int fd;
//判断参数有效性
if(argc != 3){
printf("param err\n");
printf("%s<ip><port>\n",argv[0]);
return -1;
}
printf("ip = %s\n",argv[1]);
printf("port = %s\n",argv[2]);
//初始化socket:TCP,IPv4,本地回环测试
fd = socket_init(argv);
//设置保活链接
getKeepAlive(fd);
setKeepAlive(fd);
getKeepAlive(fd);
//接受客户端链接
int newFd;
struct sockaddr_in newAddr;
socklen_t newAddrlen;
char buf[100] = {0};
fd_set readfds,readfdsTmp;
int i,nfds;
//1.清空可读可写集合,并将服务器的fd添加进可读集合
FD_ZERO(&readfds);
FD_ZERO(&readfdsTmp);
FD_SET(fd,&readfds);
nfds = fd + BACKLOG + 1;
while(1){
readfdsTmp = readfds;
//2.以阻塞方式监听全部文件描述符,如果有文件描述符可以写入,则返回
if(select(nfds,&readfdsTmp,NULL,NULL,NULL) == -1){
perror("select");
exit(-1);
}
//3.根据监听到的文件描述符进行相应的操作
if(FD_ISSET(fd,&readfdsTmp)){//监听到了服务器的fd,代表有新的客户端接入
if((newFd = accept(fd,(struct sockaddr*)&newAddr,&newAddrlen)) < 0){
perror("accept");
return -1;
}
printf("[%s,%d]connect\n",inet_ntoa(newAddr.sin_addr),ntohs(newAddr.sin_port));
printf("newFd = %d\n",newFd);
FD_SET(newFd,&readfds);//将新的socket加入监听列表
}
else{
//printf("Debug:fd = %d\n",fd);
for(i=fd+1;i<nfds;i++){//监听到了客户端的fd,处理相应客户端信息,这里的难点是客户端fd的范围如何确定
//printf("Debug:i=%d\n",i);
if(FD_ISSET(i,&readfdsTmp)){
if(read(i,buf,sizeof(buf)) <= 0){
close(i);
FD_CLR(i,&readfds);
printf("fd=%d closed\n",i);
}else{
if(getpeername(i,(struct sockaddr*)&newAddr,&newAddrlen) == -1){//与客户端链接存在问题
perror("getpeername");
}else{
printf("[%s,%d]data:%s",inet_ntoa(newAddr.sin_addr),ntohs(newAddr.sin_port),buf);
write(i,"server\n",strlen("server\n"));
memset(buf,0,sizeof(buf));
}
}
}
}
}
}
close(fd);
return 0;
}
void setKeepAlive(int fd){
int optval;
optval = 1;
if(setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,&optval,sizeof(int)) == -1){
perror("SO_KEEPALIVE");
}
optval = 5;
if(setsockopt(fd,SOL_TCP,TCP_KEEPIDLE,&optval,sizeof(socklen_t))){
perror("TCP_KEEPIDLE");
}
optval = 2;
if(setsockopt(fd,SOL_TCP,TCP_KEEPINTVL,&optval,sizeof(socklen_t))){
perror("TCP_KEEPINTVL");
}
optval = 3;
if(setsockopt(fd,SOL_TCP,TCP_KEEPCNT,&optval,sizeof(socklen_t))){
perror("TCP_KEEPCNT");
}
}
void getKeepAlive(int fd){
int optval;
socklen_t optlen;
if(getsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,&optval,&optlen) == -1){
perror("get SO_KEEPALIVE");
}else{
printf("是否开启保活链接 = %d\n",optval);
}
if(getsockopt(fd,SOL_TCP,TCP_KEEPIDLE,&optval,&optlen) == -1){
perror("get TCP_KEEPIDLE");
}else{
printf("未通讯多久发送第一次保活数据 = %ds\n",optval);
}
if(getsockopt(fd,SOL_TCP,TCP_KEEPINTVL,&optval,&optlen) == -1){
perror("get TCP_KEEPINTVL");
}else{
printf("之后发送的间隔 = %d\n",optval);
}
if(getsockopt(fd,SOL_TCP,TCP_KEEPCNT,&optval,&optlen) == -1){
perror("get TCP_KEEPCNT");
}else{
printf("发送几次未响应中断连接 = %d\n",optval);
}
}
int socket_init(char** argv){
int fd;
struct sockaddr_in addr;
//1.创建socket
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){//IPv4,TCP协议
perror("socket");
exit(-1);
}
//2.绑定IP、端口号
addr.sin_family = AF_INET; //IPv4
addr.sin_port = htons(atoi(argv[2])); //端口号,要转化为大端子节序
addr.sin_addr.s_addr = inet_addr(argv[1]); //IP地址:0表示在本网络上的本主机,即:自己
if(bind(fd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) == -1){
perror("bind");
exit(-1);
}
//3.监听socket
if(listen(fd,BACKLOG) == -1){ //允许最多接入5个客户端
perror("listen");
exit(-1);
}
return fd;
}
代码执行结果如下: