1.套接字创建以后,就可以利用它来传输数据, 但有时可能对套接字的工作方式有特殊要求,此时就需要修改套接字的属性; #include <sys/types.h> #include <sys/socket.h> 函数原型:int getsockopt(int s,int level,int optname,void *optval,socklen_t *optlen); int setsockopt(int s,int level,int optname,void *optval,socklen_t *optlen); 参数s为一个套接字,参数level是进行套接字选项操作的层次,可以取SOL_SOCKET(通用套接字),IPPROTO_IP(IP层套接字),IPPROTO_TCP(TCP层套接字)等值,一般取SOL_SOCKET来进行与特定协议不相关的操作,参数optname是套接字选项的名称,对于函数getsockopt, 参数optvai 用来存放获得的套接字选项, 参数optlen在调用前其值为optval指向的空间大小,调用完成后则其值为参数optval保存的结果的实际大小.对于函数setsockopt,参数optval是待设置的套接字选项的值,参数optlen 是该选项的长度. 这两个函数执行成功返回0,出错返回-1; 下面介绍SOL_SOCKET的选项.可以使用命令"man 7 socket"获得更详细的介绍;
- SO_KEEPALIVE:如果没有设置SO_KEEPALIVE选项,那么即使TCP连接已经长时间没有数据传输时,系统也不会检测到这个连接是否还有效,对于服务器进程,如果某一客户端非正常断开连接,则服务器进程将一直被阻塞等待,因此服务器端程序要使用这个选项,如果某个客户端一端时间内没有反应则关闭该连接。
- SO_RCVLOWAT和SO_SNDLOWAT SO_RCVLOWAT 表示接收缓冲区的下限,只有当接收缓冲区的数据超过了SO_RCVOWAT才会将数据传送到上层应用程序。SO_SNDLOWAT表示发送缓冲区的下限,只有当发送缓冲区中的数据超过了SO_SNDLOWAT才会将数据发送出去,linux 下这两个值都为1,不能被改变。也就是说,只要有数据的传送和接收就能通过函数getsockopt()函数获得,不能用setsockopt()函数更改。
- SO_RCVTIMEO和 SO_SNDTIMEO可以设置对套接字读或写的超时时间:
struct timeval{ long tv_sec; //秒数 long tv_usec; //微秒数 }; 成员tv_sec指定秒数,tv_usec指定微秒数,超时时间为这两个时间的和。
- SO_BINDTODEVICE:将套接字绑定到特定的网络接口"eth0",此后只有该网络接口上的数据才会被套接字处理。如果将选项值设置为空字符或选项长度设为0将取消绑定。
- SO_DEBUG:该选项只能对TCP套接字使用 ,设置了该选项后系统将保存TCP发送和接收的所有数据的相关信息,以便调试程序;
- SO_REUSEADDR: linux系统中,如果一个socket 绑定一个端口,该socket正常关闭或程序异常退出后的一段时间内,该端口依然维持原来的绑定状态,其它程序无法绑定该端口,如果设置了该选项则可以避免这个问题,示例:
int option_value = 1; int length = sizeof(option_value); setscokopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&option_value,length);
- SO_TYPE
用于获取套接字的类型,如SOCK_DGRAM,SOCK_STREAM,SOCK_SEQPACKET,SOCK_RDM;该选项只能被getsockopt用来获取套接字类型,而不能使用函数setsockopt修改;
- SO_ACCEPTCONN:该选项检测套接字是否处于监听状态 ,如果为0表示处于非监听状态,如果为1表示正在监听,该选项只能被函数getsockopt 用来获取监听状态信息。
- SO_BROADCAST:该选项用来决定套接字是否能在网络上广播数据,实际应用要在网络上广播数据硬件必须支持广播数据(以太网支持广播)并且使用的是SOCK_DGRAM套接字,系统默认不支持广播,如果希望SOCK_DGRAM套接字支持广播,
int option_value = 1; setsockopt(sock_fd,SOL_SOCKET,SO_BROADCAST,&option_value,sizeof(int));
- SO_SNDBUF和SO_RCVBUF:这两个用于设置套接字的发送和接收区的大小,对于TCP类型的套接字,缓冲区太小会影响TCP的流量控制,
- SO_ERROR:获取套接字内部的错误变量 so_error,当套接字上发生了一步错误时,系统将设置套接字的so_error,异步错误是指错误的发生和错误被发现的时间不一致,通常是目的主机非正常关闭时发生这种错误。该选项只能被函数getsockopt用来获取so_error。
注意:调用完函数getsockopt 之后so_error的值被自动重新初始化。
2.多路复用select()
在客户端/服务器模型中,服务器端需要同时处理多个客户端的连接请求,此时就需要使用多路复用,实现多路复用最简单的方法是采用非阻塞方式套接字,服务器端不断查询各个套接字,如果有数据,则读出数据,如果没有则查看下一个套接字。 另一种方法是服务器进程并不主动询问套接字状态,而是向系统登记希望监视的套接字,然后阻塞,当套接字上有事件发生时 ,系统通知服务器进程告知哪个套接字上发生了什么事件,服务器进程查询对应的套接字,并进行处理,这就要用到函数select() #include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> 函数原型:int select(int n,fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeval *timeout); 参数n是需要监视的文件描述符数,要监听的文件描述符值为0~n-1,参数readfds指定需要监视可读文件描述符的集合 ,当这个集合中的一个描述符上有数据到达时,系统将通知 调用select函数的程序.参数writefds指定需要监视的可写文件描述符的集合,当这个集合的某个描述符可以发数据时, 程序将收到通知,参数exceptfds指定需要监视的异常文件描述符集合 ,当该集合中的一个描述符发生异常,程序会收到通知。参数timeout指定了阻塞的时间,如果在这个时间段的监视文件描述符上都没有时间发生,函数select()将返回, 这里的文件描述符既可以是普通文件的描述符,也可以是套接字描述符; 系统为文件描述符集合提供了一系列的宏以方便操作: FD_CLR(int fd,fd_set *set); //将文件描述符从集合set 中删除, FD_ISSET(int fd ,fd_set *set) //测试fd是否在set中 FD_SET(int fd,fd_set *set); //在文件描述符集合中增加文件描述符fd; FD_ZERO(fd_set *set) //清空set集合 来看个了例子
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <time.h> void display_time(const char *string) { int seconds; seconds = time((time_t *)NULL); printf("%s, %d\n",string,seconds); } int main(void) { fd_set readfds; struct timeval timeout; int ret; /*监听文件描述符0是否有数据输入,文件描述符0表示标准输入*/ FD_ZERO(&readfds); //开始使用一个文件描述符集合前一般要将其清空 FD_SET(0,&readfds); //设置阻塞的时间为10 秒 while(1) { timeout.tv_sec = 10; timeout.tv_usec = 0; display_time("before select!"); ret=select(1,&readfds,NULL,NULL,&timeout); display_time("after select"); switch(ret) { case 0: printf("NO data in ten seconds!\n"); exit(0); break; case -1: perror("select"); exit(1); break; default: getchar(); printf("Data is available now!\n"); } } return 0; }
结果:
yang@liu:~/syc/第11章$ ./a.out before select!, 1438134782 1 after select, 1438134784 Data is available now! before select!, 1438134784 after select, 1438134792 NO data in ten seconds!
程序执行时,等待几秒钟后按下任意键,从执行结果可以看出按下任意键后select立刻返回,又重新设置了select()监视键盘动作 ,可以发现,第二次只监听了8秒,好像时间是累积的,因为第一次用了2,秒,这是为什么呢,原来是系统会修改timeout的时间为剩余时间,所以应该把初始化时间放在循环内,每一次都进行初始化。