套接字的多种可选项
我们之前写好的程序都是创建好套接字后(未经特别操作)直接使用的,此时通过默认的套接字特性进行通信。之前的示例较为简单,无需特别操作套接字特性,但有时的确需要更改。这里列出一部分套接字。
level | Optname | get | set | 说明 | 标志 | 数据类型 |
SOL_SOCKET | SO_BROADCAST | y | y | 允许发送广播数据报 | y | int |
SO_DEBUG | y | y | 使能调试跟踪 | y | int | |
SO_DONTROUTE | y | y | 旁路路由表查询 | y | int | |
SO_ERROR | y | 获取待处理错误并消除 | int | |||
SO_KEEPALIVE | y | y | 周期性测试连接是否存活 | y | int | |
SO_LINGER | y | y | 若有数据待发送则延迟关闭 | linger{} | ||
SO_OOBINLINE | y | y | 让接收到的带外数据继续在线存放 | y | int | |
SO_RCVBUF | y | y | 接收缓冲区大小 | int | ||
SO_SNDBUF | y | y | 发送缓冲区大小 | int | ||
SO_RCVLOWAT | y | y | 接收缓冲区低潮限度 | int | ||
SO_SNDLOWAT | y | y | 发送缓冲区低潮限度 | int | ||
SO_RCVTIMEO | y | y | 接收超时 | timeval{} | ||
SO_SNDTIMEO | y | y | 发送超时 | timeval{} | ||
SO_REUSEADDR | y | y | 允许重用本地地址 | y | int | |
SO_REUSEPORT | y | y | 允许重用本地地址 | y | int | |
SO_TYPE | y | 取得套接口类型 | int | |||
SO_USELOOPBACK | y | y | 路由套接口取得所发送数据的拷贝 | y | int | |
IPPROTO_IP | IP_HDRINCL | y | y | IP头部包括数据 | y | int |
IP_OPTIONS | y | y | IP头部选项 | 见后面说明 | ||
IP_RECVDSTADDR | y | y | 返回目的IP地址 | y | int | |
IP_RECVIF | y | y | 返回接收到的接口索引 | y | int | |
IP_TOS | y | y | 服务类型和优先权 | int | ||
IP_TTL | y | y | 存活时间 | int | ||
IP_MULTICAST_IF | y | y | 指定外出接口 | in_addr{} | ||
IP_MULTICAST_TTL | y | y | 指定外出TTL | u_char | ||
IP_MULTICAST_LOOP | y | y | 指定是否回馈 | u_char | ||
IP_ADD_MEMBERSHIP | y | 加入多播组 | ip_mreq{} | |||
IP_DROP_MEMBERSHIP | y | 离开多播组 | ip_mreq{} | |||
IPPROTO_ICMPV6 | ICMP6_FILTER | y | y | 指定传递的ICMPv6消息类型 | icmp6_filter{} | |
IPPROTO_IPV6 | IPV6_ADDRFORM | y | y | 改变套接口的地址结构 | int | |
IPV6_CHECKSUM | y | y | 原始套接口的校验和字段偏移 | int | ||
IPV6_DSTOPTS | y | y | 接收目标选项 | y | int | |
IPV6_HOPLIMIT | y | y | 接收单播跳限 | y | int | |
IPV6_HOPOPTS | y | y | 接收步跳选项 | y | int | |
IPV6_NEXTHOP | y | y | 指定下一跳地址 | y | sockaddr{} | |
IPV6_PKTINFO | y | y | 接收分组信息 | y | int | |
IPV6_PKTOPTIONS | y | y | 指定分组选项 | 见后面说明 | ||
IPV6_RTHDR | y | y | 接收原路径 | y | int | |
IPV6_UNICAST_HOPS | y | y | 缺省单播跳限 | int | ||
IPV6_MULTICAST_IF | y | y | 指定外出接口 | in6_addr{} | ||
IPV6_MULTICAST_HOPS | y | y | 指定外出跳限 | u_int | ||
IPV6_MULTICAST_LOOP | y | y | 指定是否回馈 | y | u_int | |
IPV6_ADD_MEMBERSHIP | y | 加入多播组 | ipv6_mreq{} | |||
IPV6_DROP_MEMBERSHIP | y | 离开多播组 | ipv6_mreq{} | |||
IPPROTO_TCP | TCP_KEEPALIVE | y | y | 控测对方是否存活前连接闲置秒数 | int | |
TCP_MAXRT | y | y | TCP最大重传时间 | int | ||
TCP_MAXSEG | y | y | TCP最大分节大小 | int | ||
TCP_NODELAY | y | y | 禁止Nagle算法 | y | int | |
TCP_STDURG | y | y | 紧急指针的解释 | y | int |
从这个表可以看出,套接字的选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关的事项,SOL_SOCKET层是套接字相关的通用可选项。
getsockopt & setsockopt
我们几乎可以针对上表中的所有可选项进行读取(Get)和设置(Set)(当然,有些可选项只能进行一种操作)。可选项的读取和设置通过如下2个函数完成。
#include <sys/socket.h>
int getsockopt(int sock,int level,int optname,void* optval,socklen_t *optlen);
-------sock 用于查看选项套接字文件描述符。
-------level 要查看的可选项的协议层。
-------optname 要查看的可选项名。
-------optval 保存查看结果的缓冲地址值。
-------optlen 向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的
选项信息的字节数目。
上述函数用于读取套接字可选项,并不难。接下来介绍更改可选项时调用的函数
#include <sys/socket.h>
int setsockopt(int sock,int level,int optname,const void* optval,socklen_t optlen);
-------sock 用于更改可选项的套接字文件描述符。
-------level 要更改的可选项协议层。
-------optname 要更改的可选项名。
-------optval 保存要更改的选项信息的缓冲地址值。
-------optlen 向第四个参数optval传递的可选项信息的字节数。
接下来介绍这些函数的调用方法。关于setsockopt函数的调用方法在其他示例中给出,先介绍getsockopt函数的调用方法。下列示例用协议层为SOL_SOCKET,名为SO_TYPE的可选项查看套接字类型(TCP或UDP)。
sock_type.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
void error_handling(char *message);
int main(int argc,char *argv[]){
int tcp_sock,udp_sock;
int sock_type;
socklen_t optlen;
int state;
optlen = sizeof(sock_type);
tcp_sock = socket(PF_INET,SOCK_STREAM,0);
udp_sock = socket(PF_INET,SOCK_DGRAM,0);
printf("SOCK_STREAM: %d \n",SOCK_STREAM);
printf("SOCK_DGRAM: %d\n",SOCK_DGRAM);
state = getsockopt(tcp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen);
if(state)
error_handling("getsocket() error!");
printf("Socket type one: %d \n",sock_type);
state = getsockopt(udp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen);
if(state)
error_handling("getsockopt() error!");
printf("Socket type two: %d \n",sock_type);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
运行结果:sock_type.c
ycz@debian8470p:~/program/TCP&&IP/chapter9$ gcc sock_type.c -o socktype
ycz@debian8470p:~/program/TCP&&IP/chapter9$ ./socktype
SOCK_STREAM: 1
SOCK_DGRAM: 2
Socket type one: 1
Socket type two: 2
上述示例给出了调用getsockopt函数查看套接字信息的方法。另外,用于验证套接字类型的SO_TYPE是典型的只读可选项,这一点可以通过下面这句话解释:
“套接字的类型只能在创建时决定,以后不能更改。”
SO_SNDBUF & SO_RCVBUF
SO_RCVBUF是输入缓冲大小相关可选项,SO_SNDBUF是输出缓冲大小相关可选项。用这2个可选项既可以读取当前I/O缓冲大小,也可以进行更改。通过下列示例读取创建套接字时默认的I/O缓冲大小。
get_buf.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
void error_handling(char *message);
int main(int argc,char *argv[]){
int sock;
int snd_buf = 1024*3,rcv_buf = 1024*3;
int state;
socklen_t len;
sock = socket(PF_INET,SOCK_STREAM,0);
state = setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,sizeof(rcv_buf));
if(state)
error_handling("setsockopt() error!");
state = setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,sizeof(snd_buf));
if(state)
error_handling("setsockopt() error!");
len = sizeof(snd_buf);
state = getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
if(state)
error_handling("getsockopt() error!");
len = sizeof(rcv_buf);
state = getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
if(state)
error_handling("getsockopt() error!");
printf("Input buffer size: %d \n",rcv_buf);
printf("Output buffer size: %d \n",snd_buf);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
运行结果:get_buf.c
ycz@debian8470p:~/program/TCP&&IP/chapter9$ gcc get_buf.c -o getbuf
ycz@debian8470p:~/program/TCP&&IP/chapter9$ ./getbuf
Input buffer size: 87380
Output buffer size: 16384
这是我系统中的运行结果,与各位的运行结果相比可能有较大差异。接下来的程序中将更改I/O缓冲大小。
set_buf.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
void error_handling(char *message);
int main(int argc,char *argv[]){
int sock;
int snd_buf = 1024*3,rcv_buf = 1024*3;
int state;
socklen_t len;
sock = socket(PF_INET,SOCK_STREAM,0);
state = setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,sizeof(rcv_buf));
if(state)
error_handling("setsockopt() error!");
state = setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,sizeof(snd_buf));
if(state)
error_handling("setsockopt() error!");
len = sizeof(snd_buf);
state = getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
if(state)
error_handling("getsockopt() error!");
len = sizeof(rcv_buf);
state = getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
if(state)
error_handling("getsockopt() error!");
printf("Input buffer size: %d \n",rcv_buf);
printf("Output buffer size: %d \n",snd_buf);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
运行结果:set_buf.c
ycz@debian8470p:~/program/TCP&&IP/chapter9$ gcc set_buf.c -o setbuf
ycz@debian8470p:~/program/TCP&&IP/chapter9$ ./setbuf
Input buffer size: 6144
Output buffer size: 6144
SO_REUSEADDR
发生地址分配错误(Bindling Error)
学习SO_REUSEADDR可选项之前,应理解好Time-wait状态。
reuseadr_eserver.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define TRUE 1
#define FALSE 0
void error_handling(char *message);
int main(int argc,char *argv[]){
int serv_sock,clnt_sock;
char message[30];
int option,str_len;
socklen_t optlen,clnt_adr_sz;
struct sockaddr_in serv_adr,clnt_adr;
if(argc!=2){
printf("Usage : %s<port>\n",argv[0]);
exit(1);
}
serv_sock = socket(PF_INET,SOCK_STREAM,0);
if(serv_sock==-1)
error_handling("socket() error!");
/*
optlen = sizeof(option);
option = TRUE;
setsockopt(serv_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);
*/
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)))
error_handling("bind() error");
if(listen(serv_sock,5)==1)
error_handling("listen error");
clnt_adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
while((str_len = read(clnt_sock,message,sizeof(message)))!=0){
write(clnt_sock,message,str_len);
write(1,message,str_len);
}
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
此示例是之前已经实现过多次的回声服务器端,可以结合第4章介绍过的回声客户端运行。
运行结果:reuseadr_eserver.c
ycz@debian8470p:~/program/TCP&&IP/chapter9$gcc reuseadr_eserver.c -o reuseadr_eserver
ycz@debian8470p:~/program/TCP&&IP/chapter9$ ./reuseadr_eserver 9190
wait
what the message
success
copy that
CET-6
运行结果:eclient.c
ycz@debian8470p:~/program/TCP&&IP/chapter4$ gcc eclient.c -o eclient
ycz@debian8470p:~/program/TCP&&IP/chapter4$ ./eclient 127.0.0.1 9190
Connected............
Inputs message(Q to quit): wait
Message from server: wait
Inputs message(Q to quit): what the message
Message from server: what the message
Inputs message(Q to quit): success
Message from server: success
Inputs message(Q to quit): copy that
Message from server: copy that
Inputs message(Q to quit): CET-6
Message from server: CET-6
Inputs message(Q to quit): Q