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"获得更详细的介绍;
struct timeval{ long tv_sec; //秒数 long tv_usec; //微秒数 }; 成员tv_sec指定秒数,tv_usec指定微秒数,超时时间为这两个时间的和。
int option_value = 1; int length = sizeof(option_value); setscokopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&option_value,length);
用于获取套接字的类型,如SOCK_DGRAM,SOCK_STREAM,SOCK_SEQPACKET,SOCK_RDM;该选项只能被getsockopt用来获取套接字类型,而不能使用函数setsockopt修改;
int option_value = 1; setsockopt(sock_fd,SOL_SOCKET,SO_BROADCAST,&option_value,sizeof(int));
注意:调用完函数getsockopt 之后so_error的值被自动重新初始化。
在客户端/服务器模型中,服务器端需要同时处理多个客户端的连接请求,此时就需要使用多路复用,实现多路复用最简单的方法是采用非阻塞方式套接字,服务器端不断查询各个套接字,如果有数据,则读出数据,如果没有则查看下一个套接字。 另一种方法是服务器进程并不主动询问套接字状态,而是向系统登记希望监视的套接字,然后阻塞,当套接字上有事件发生时 ,系统通知服务器进程告知哪个套接字上发生了什么事件,服务器进程查询对应的套接字,并进行处理,这就要用到函数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的时间为剩余时间,所以应该把初始化时间放在循环内,每一次都进行初始化。