其实说是原创有点勉强,应该只是把它们集合起来,再添上少少自己的理解,随着后面的深入理解,有待继续添加内容。。。。
1、fd_set描述
WinSock2.h 中fd_set的定义:
typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
WinSock2.h 中SOCKET的定义:
typedef UINT_PTR SOCKET;
BaseTsd.h中UINT_PTR的定义:
#if defined(_WIN64) typedef __int64 INT_PTR, *PINT_PTR; typedef unsigned __int64 UINT_PTR, *PUINT_PTR; typedef __int64 LONG_PTR, *PLONG_PTR; typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; #define __int3264 __int64 #else typedef _W64 int INT_PTR, *PINT_PTR; typedef _W64 unsigned int UINT_PTR, *PUINT_PTR; typedef _W64 long LONG_PTR, *PLONG_PTR; typedef _W64 unsigned long ULONG_PTR, *PULONG_PTR; #define __int3264 __int32 #endif
2、SOCKET描述
每一个socket用一个半相关描述: (协议,本地地址,本地端口)
SOCKET socket( int af, int type, int protocol );
应用程序调用socket函数来创建一个能够进行网络通信的套接字。
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET;
第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW(WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据报以及协议首部);
第三个参数指定应用程序所使用的通信协议。
该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。
3、关于fd
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
如图所示:
那么对应到socket里面,fd就是SOCKET这个描述符,而v节点的内容大概就是(int af, int type, int protocol)这三个值(个人理解)。
4、关于fd_set类型通过下面四个宏来操作:
fd_set set; FD_CLR(fd,&set); FD_SET(fd,&set); FD_ZERO(&set); FD_ISSET(fd,&set);
在WinSock2.h中定义如下:
#define FD_CLR(fd, set) do { \ u_int __i; \ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \ if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \ while (__i < ((fd_set FAR *)(set))->fd_count-1) { \ ((fd_set FAR *)(set))->fd_array[__i] = \ ((fd_set FAR *)(set))->fd_array[__i+1]; \ __i++; \ } \ ((fd_set FAR *)(set))->fd_count--; \ break; \ } \ } \ } while(0) #define FD_SET(fd, set) do { \ u_int __i; \ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \ break; \ } \ } \ if (__i == ((fd_set FAR *)(set))->fd_count) { \ if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ ((fd_set FAR *)(set))->fd_array[__i] = (fd); \ ((fd_set FAR *)(set))->fd_count++; \ } \ } \ } while(0) #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0) #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))5、fd_set和SOCKET的关系
一般用法是,先通过SOCKET socket( int af, int type, int protocol )创建一个socket,这个socket返回了一个文件描述符,再把它存放到fd_set的fd_array[]里面,以方便对socket进行操作,常用的有send() 和 recv()以及select()。
关于select函数的描述如下:
select函数的接口比较简单: int select(int nfds, fd_set *readset,fd_set *writeset, fd_set* exceptset, struct tim*timeout); 功能: 测试指定的fd可读?可写?有异常条件待处理? 参数: nfds: 需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd 值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大 的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所 有1024位。 readset: 用来检查可读性的一组文件描述字。 writeset: 用来检查可写性的一组文件描述字。 exceptset:用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内) timeout:有三种可能: 1:timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回) 2:timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数 均返回) 3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回) 返回值: 返回对应位仍然为1的fd的总数。 Remarks: 三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。 使用select函数的过程一般是: 先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数 select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。 以下是一个测试单个文件描述字可读性的例子: int isready(int fd) { int rc; fd_setfds; structtimtv; FD_ZERO(&fds); FD_SET(fd,&fds); tv.tv_sec= tv.tv_usec =0; rc= select(fd+1, &fds, NULL, NULL,&tv); if(rc < 0) //error return-1; returnFD_ISSET(fd,&fds) ? 1 : 0; } 下面还有一个复杂一些的应用: //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd uint32 SocketWait(TSocket *s,boolrd,bool wr,uint32timems) { fd_set rfds,wfds; #ifdef _WIN32 TIM tv; #else struct tim tv; #endif FD_ZERO(&rfds); FD_ZERO(&wfds); if(rd) //TRUE FD_SET(*s,&rfds); //添加要测试的描述字 if(wr) //FALSE FD_SET(*s,&wfds); tv.tv_sec=timems/1000; //second tv.tv_usec=timems00; //ms for (;;) //如果errno==EINTR,反复测试缓冲区的可读性 switch(select((*s)+1,&rfds,&wfds,NULL, (timems==TIME_INFINITE?NULL:&tv))) //测试在规定的时间内套接口接收缓冲区中是否有数据可读 { //0--超时,-1--出错 case0: return 0; case(-1): if (SocketError()==EINTR) break; return 0; //有错但不是EINTR default: if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0 return 1; if (FD_ISSET(*s,&wfds)) return 2; return 0; }; }关于这个select的说明,本人觉得这里写的很到位,所以直接复制过来。
引自:http://blog.sina.com.cn/s/blog_5c8d13830100erzs.html
参考链接:
socket:http://baike.baidu.com/view/13870.htm
fd:http://baike.baidu.com/view/1303430.htm