socket编程

来源:互联网 发布:人工智能读心术 小冰 编辑:程序博客网 时间:2024/06/03 19:58

一、基于TCP的socket编程

服务器端程序:

1)创建套接字(socket)。

此系统调用的实际效果是,请求操作系统把网络通信所需要的一些系统资源(存储器空间、CPU时间、网络宽带等)分配给该应用进程。

2)将套接字绑定到一个本地地址和端口上(bind)。

3)将套接字设为监听模式,准备接收客户请求(listen)。

4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。

5)用返回的套接字和客户端进行通信(send/recv)。

6)返回,等待另一客户请求。

7)关闭套接字(closesocket)。

此系统调用,通知操作系统回收与该套接字描述符相关的所有资源。

客户端程序:

1)创建套接字(socket)。

2)向服务器发出连接请求(connect)。

3)和服务器端进行通信(send/recv)。

4)关闭套接字(closesocket)。

二、基于UDP的socket编程

服务器端(接收端)程序:

1)创建套接字(socket)。

2)将套接字绑定到一个本地地址和端口上(bind)。

3)等待接收数据(recvfrom)。

4)关闭套接字(closesocket)。

客户端(发送端)程序:

1)创建套接字(socket)。

2)向服务器发送数据(sendto)。

3)关闭套接字(closesocket)。


socket的基本操作

既然socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。

3.1、socket()函数

int socket(int domain, int type, int protocol);

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

  • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。
  • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()listen()时系统会自动随机分配一个端口。

3.2、bind()函数

正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INETAF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的三个参数分别为:

  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 
    struct sockaddr_in {    sa_family_t    sin_family; /* address family: AF_INET */    in_port_t      sin_port;   /* port in network byte order */    struct in_addr sin_addr;   /* internet address */};/* Internet address. */struct in_addr {    uint32_t       s_addr;     /* address in network byte order */};
    ipv6对应的是: 
    struct sockaddr_in6 {     sa_family_t     sin6_family;   /* AF_INET6 */     in_port_t       sin6_port;     /* port number */     uint32_t        sin6_flowinfo; /* IPv6 flow information */     struct in6_addr sin6_addr;     /* IPv6 address */     uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ };struct in6_addr {     unsigned char   s6_addr[16];   /* IPv6 address */ };
    Unix域对应的是: 
    #define UNIX_PATH_MAX    108struct sockaddr_un {     sa_family_t sun_family;               /* AF_UNIX */     char        sun_path[UNIX_PATH_MAX];  /* pathname */ };
  • addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

网络字节序与主机字节序

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

  a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

  b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

3.3、listen()、connect()函数

如果作为一个服务器,在调用socket()bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

3.4、accept()函数

TCP服务器端依次调用socket()bind()listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

3.5、read()、write()等函数

万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

       #include <unistd.h>       ssize_t read(int fd, void *buf, size_t count);       ssize_t write(int fd, const void *buf, size_t count);       #include <sys/types.h>       #include <sys/socket.h>       ssize_t send(int sockfd, const void *buf, size_t len, int flags);       ssize_t recv(int sockfd, void *buf, size_t len, int flags);       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,                      const struct sockaddr *dest_addr, socklen_t addrlen);       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,                        struct sockaddr *src_addr, socklen_t *addrlen);       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

其它的我就不一一介绍这几对I/O函数了,具体参见man文档或者baidu、Google,下面的例子中将使用到send/recv。

3.6、close()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

#include <unistd.h>int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

4、socket中TCP的三次握手建立连接详解

我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

  • 客户端向服务器发送一个SYN J
  • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
  • 客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

image

图1、socket中发送的TCP三次握手

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

5、socket中TCP的四次握手释放连接详解

上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

image

图2、socket中发送的TCP四次握手

图示过程如下:

  • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
  • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
  • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
  • 接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。


thanks:http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html


函数:

 socket()函数

[cpp] view plaincopy
  1. #include <sys/types.h>;   
  2. #include <sys/socket.h>;   
  3. int socket(int domain, int type, int protocol);   

          参数 domain: 通常被设置成 "AF_INET"。
          参数 type: 告诉内核是 SOCK_STREAM 类型还是 SOCK_DGRAM 类型。
          参数 protocol :通常被设置为 "0"。
          如果需要更多的信息,可以看 socket() 的 man帮助。
          返回值: socket() 只是返回你以后在系统调用种可能用到的 socket 描述符,在错误的时候返回-1。全局变量 errno 中将储存返回的错误值。

connect()函数

[cpp] view plaincopy
  1. #include <sys/types.h>;   
  2. #include <sys/socket.h>;  
  3. int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);   

          参数 sockfd :是系统调用 socket() 返回的套接字文件描述符。
          参数 serv_addr :是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr。
          参数 addrlen: 设置 为 sizeof(struct sockaddr)。
          connect() 函数只用于客户端,使socket变成主动socket (active socket)

2.4.3 bind()函数

[cpp] view plaincopy
  1. #include <sys/types.h>;  
  2. #include <sys/socket.h>;  
  3. int bind(int sockfd, struct sockaddr *my_addr, int addrlen);   

          参数 sockfd :是调用 socket 返回的文件描述符。
          参数 my_addr :是指向数据结构 struct sockaddr 的指针,它保存地址(即端口和 IP 地址) 信息。如果IP地址信息为INADDR_ANY, 表示不关心本地地址信息。在存在多个IP地址的情况下,所有的IP都会进行被绑定。
          参数 addrlen :设置为 sizeof(struct sockaddr)。
          返回值:bind() 在错误的时候依然是返回-1,并且设置全局错误变量errno。 


          使用bind()函数需要注意以下一些问题:
          1. bind() 函数只用于服务器端,使socket变成被动socket (passive socket)
          2. 不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!可以选择1024 到65535之间的端口。见<《计算机网络》 读书笔记(四) 运输层> "1.1.3 运输层端口"。
          3. 按照下面的写法可以让系统自动处理端口和地址。
[cpp] view plaincopy
  1. my_addr.sin_port = htons(0); /* 随机选择一个没有使用的端口 */   
  2. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */   

2.4.4 listen()函数

[cpp] view plaincopy
  1. int listen(int sockfd, int backlog);   

          参数 sockfd :是调用 socket() 返回的套接字文件描述符。

          参数backlog: 是在进入队列中允许的连接数目。 进入的连接是在队列中一直等待直到你接受连接。它们的数目限制于队列的允许。 大多数系统的允许数目是20。
          返回值:和别的函数一样,在发生错误的时候返回-1,并设置全局错误变量 errno。
          listen() 函数只用于服务器端

2.4.5 accept()函数

          调用 accept() 将返回一个新的套接字文件描述符。新的套接字可以用于发送 (send()) 和接收 ( recv()) 数据。
[cpp] view plaincopy
  1. #include <sys/socket.h>;  
  2. int accept(int sockfd, void *addr, int *addrlen);   

          参数 sockfd :相当简单,是和 listen() 中一样的套接字描述符。
          参数 addr: 是个指向局部的数据结构 sockaddr_in 的指针。这是一个传出参数,可以用于测定那个地址在那个端口呼叫,这用于机器存在多个IP地址的情况。
          参数 addrlen :是个局部的整形变量,设置为 sizeof(struct sockaddr_in)。 
          返回值:同样,在错误时返回-1,并设置全局错误变量 errno。 

2.4.6 send()函数

[cpp] view plaincopy
  1. #include <sys/socket.h>;  
  2. int send(int sockfd, const void *msg, int len, int flags);   

          参数 sockfd :是你想发送数据的套接字描述符(或者是调用 socket() 或者是 accept() 返回的。
          参数 msg :是指向你想发送的数据的指针。
          参数 len :是数据的长度。 
          参数 flags :用于操作数据发送时TCP层的一些特性,如发送外带数据,通常设置为 0。
          返回值:send() 返回实际发送的数据的字节数--它可能小于要求发送的数目。 注意,有时候你告诉它要发送一堆数据可是它不能处理成功。它只是发送它可能发送的数据,然后希望你能够发送其它的数据。
          如果 send() 返回的数据和 len 不匹配,你就应该发送其它的数据。
          它在错误的时候返回-1,并设置 errno。

2.4.7 recv()函数

[cpp] view plaincopy
  1. #include <sys/socket.h>;  
  2. int recv(int sockfd, void *buf, int len, unsigned int flags);    

          参数 sockfd :是要读的套接字描述符。
          参数 buf :是要读的信息的缓冲。
          参数 len: 是缓 冲的最大长度。
          参数flags :用于控制读取行为的一些属性,如读取外带数据或者查询buf而不读取数据等,通常设置为0。
          返回值:recv() 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1, 同时设置 errno。

2.4.8 sendto()函数

[cpp] view plaincopy
  1. #include <sys/socket.h>;  
  2. int sendto(int sockfd, const void *msg, int len, unsigned int flags,  const struct sockaddr *to, int tolen);    

          sendto用于无连接数据报套接字,也就是UDP协议数据发送。
          参数 sockfd :是你想发送数据的套接字描述符(或者是调用 socket() 或者是 accept() 返回的。
          参数 msg: 是指向你想发送的数据的指针。
          参数 len: 是数据的长度。 
          参数 flags: 通常设置为0,UDP协议中是不存在外带数据的。
          参数 to :是个指向数据结构 struct sockaddr 的指针,包含了目的地的 IP 地址和端口信息。
          参数 tolen: 可以简单地设置为 sizeof(struct sockaddr)。 
          返回值:和函数 send() 类似,sendto() 返回实际发送的字节数(它也可能小于 你想要发送的字节数),或者在错误的时候返回 -1。

2.4.9 recvfrom()函数

          recvfrom用于无连接数据报套接字,也就是UDP协议数据接受。
          参数 sockfd: 是要读的套接字描述符。
          参数 buf :是要读的信息的缓冲。
          参数 len: 是缓 冲的最大长度。
          参数 flags :用于控制读取行为的一些属性,通常设置为0,同样由于UDP协议不支持外带数据,flags也无法设置为读取外带数据。

          参数 from: 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是源机器的 IP 地址和端口信息。           

          参数 fromlen: 是个int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。

          返回值:recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。

          send() 和 recv() 也可以用于UDP数据传输,只要在创建socket时指定协议类型为SOCK_DGRAM。

2.4.10 close()函数

[cpp] view plaincopy
  1. void close(sockfd);  
          参数 sockfd :是要关闭的套接字描述符
          close用于优雅的关闭socket连接,在TCP下它将按照标准TCP四次握手执行。它可以防止对套接字进行更多的数据读写,任何在另一端读写套接字的企图都将返回错误信息。

2.4.11 shutdown()函数

[cpp] view plaincopy
  1. int shutdown(int sockfd, int how);   

          它允许你将一定方向上的通讯或者双向的通讯(就象close()一 样)关闭,你可以使用:
          参数 sockfd: 是想要关闭的套接字文件描述符。
          参数 how :的值是下面的其中之 一:
   0 – 不允许接受
   1 – 不允许发送
         2 – 不允许发送和接受(和 close() 一样)
         shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。) 如果在无连接的数据报套接字中使用shutdown(),那么只不过是让 send() 和 recv() 不能使用。

2.4.12 getpeername()函数

[cpp] view plaincopy
  1. #include <sys/socket.h>;  
  2. int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);  

          函数 getpeername() 告诉在连接的流式套接字上谁在另外一边。一旦获得它们的地址,就可以使用 inet_ntoa() 或者 gethostbyaddr() 来打印或者获得更多的信息。
          参数 sockfd :是连接的流式套接字的描述符。
          参数 addr :是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,它保存着连接的另一边的 信息。
          参数 addrlen :是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)。
          返回值:函数在错误的时候返回 -1,设置相应的 errno。

2.4.13 gethostname()函数

[cpp] view plaincopy
  1. #include <unistd.h>;  
  2. int gethostname(char *hostname, size_t size);  
[cpp] view plaincopy
  1.   

          它返回程序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得机器的 IP 地址。

          参数 hostname: 是一个字符数组指针,它将在函数返回时保存主机名。

          参数 size:是hostname 数组的字节长度。

          返回值:函数调用成功时返回 0,失败时返回 -1,并设置 errno。

2.4.14 gethostbyname()函数

[cpp] view plaincopy
  1. #include <netdb.h>;  
  2. struct hostent *gethostbyname(const char *name);   

          它主要的功能是:给它一个容易记忆的某站点的地址,它转换出IP地址。
          返回值:它返回一个指向 struct hostent 的指针。这个数据结构如下:
[cpp] view plaincopy
  1. struct hostent {  
  2.    char *h_name;        //地址的正式名称  
  3.    char **h_aliases;        //空字节-地址的预备名称的指针。  
  4.    int h_addrtype;      //地址类型; 通常是AF_INET。  
  5.    int h_length;        //地址的比特长度  
  6.    char **h_addr_list;  //零字节-主机网络地址指针。网络字节顺序  
  7. };  
  8. #define h_addr h_addr_list[0]   //h_addr_list中的第一地址  

          gethostbyname() 成功时返回一个指向结构体 hostent 的指针,或者 是个空 (NULL) 指针。和以前不同,不设置errno,而用h_errno 设置错误信息。获取错误信息需要使用 herror()函数。

2.4.15 gethostbyaddr()函数

[cpp] view plaincopy
  1. #include <netdb.h>;  
  2. struct hostent gethostbyaddr(const char* addr, int len, int type);  

          参数 addr :指向网络字节顺序地址的指针。
          参数 len: 地址的长度,在AF_INET类型地址中为4。
          参数 type: 地址类型,应为AF_INET。
          返回值:它返回一个指向 struct hostent 的指针。hostent定义同上。
          gethostbyaddr() 成功时返回一个指向结构体 hostent 的指针,或者 是个空 (NULL) 指针。和以前不同,不设置errno,而用h_errno 设置错误信息。获取错误信息需要使用 herror()函数。

2.4.16 select()函数

          select() 函数可以同时监视多个套接字。它可以告诉你哪个套接字准备读,哪个又准备写,哪个套接字又发生了例外 (exception)。
[cpp] view plaincopy
  1. #include <sys/time.h>;  
  2. #include <sys/types.h>;  
  3. #include <unistd.h>;  
  4. int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  

          参数 numfds :应该等于最高的文件描述符的值加1。
          参数 readfds:为可读文件集
          参数 writefds:为可写文件集
          参数 exceptfds:为异常文件集
          参数 timeout:为超时时间,数据结构 struct timeval 如下: 
[cpp] view plaincopy
  1. struct timeval {   
  2.    int tv_sec; /* seconds */   
  3.    int tv_usec; /* microseconds */   
  4. };   


          当函数 select() 返回的时候,readfds 的值修改为反映你选择的哪个文件描述符可以读。可以用下面讲到的宏 FD_ISSET() 来测试。 
          对这些集合进行操作系统定义了一些宏,每个集合类型都是 fd_set。
          FD_ZERO(fd_set *set) – 清除一个文件描述符集合 
          FD_SET(int fd, fd_set *set) - 添加fd到集合 
          FD_CLR(int fd, fd_set *set) – 从集合中移去fd 
          FD_ISSET(int fd, fd_set *set) – 测试fd是否在集合中 


2.4.17 poll()函数

[cpp] view plaincopy
  1. #include <poll.h>  
  2. int poll(struct pollfd fds[], nfds_t nfds, int timeout);  

          参数 fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况; 

         struct pollfd结构定义如下:

[cpp] view plaincopy
  1. truct pollfd {  
  2.          int fd; /*文件描述符*/  
  3.          short events; /* 等待的需要测试事件 */   
  4.          short revents; /* 实际发生了的事件,也就是返回结果 */  
  5. };  

          event和revents可为下列选项:

          POLLIN                            普通或优先级带数据可读
          POLLRDNORM              普通数据可读
          POLLRDBAND               优先级带数据可读
          POLLPRI                         高优先级数据可读
          POLLOUT                        普通数据可写
          POLLWRNORM              普通数据可写
          POLLWRBAND               优先级带数据可写
          POLLERR                        发生错误
          POLLHUP                        发生挂起
          POLLNVAL                       描述字不是一个打开的文件

          参数 nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

          参数 timeout:是poll函数调用阻塞的时间,单位:毫秒;
          返回值:
          >0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
          ==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的socket描述符上没有任何事件发生的话,那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,如果timeout==0,那么poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;
          -1: poll函数调用失败,同时会自动设置全局变量errno;
          poll()函数与select()十分相似,当返回正值时,代表满足响应事件的文件描述符的个数,如果返回0则代表在规定时间内没有事件发生。如发现返回为负则应该立即查看 errno,因为这代表有错误发生。如果没有事件发生,revents会被清空,所以你不必多此一举。

thanks:http://blog.csdn.net/arau_sh/article/details/12030951


1. 概述

             在《网络编程之 socket函数 (一)》 中所讲到的所有socket函数并不涉及设置socket属性,而只是定义了客户端和服务器端的socket流程。服务器端和客户端的使用流程如下:
            客户端:
                 socket()->connect()->send()/recv()->close()

            服务器端:
                 socket()->bind()->listen()->accept()->close()
                                                                      |
                                                                      |  ->send()/recv()->close()
                                                                      |
                                                                      |  ->select()->send()/recv()->close()


            下面介绍的一些函数则涉及socket的属性设置。事实上socket的不同属性对socket的行为影响巨大。


2. 函数介绍

            控制套接字属性的函数包括:
            1. getsockopt(): 用于查询指定的套接字一个特定的套接字选项的当前值。
            2. setsockopt(): 用于为指定的套接字设定一个特定的套接字选项。
            3. fcntl(): 向打开的文件fd发送命令,更改其属性。在linux下所有的一切都被认为是文件,socket也不例外。可以认为fcntl()是ioctl()的一个子集。
            4. octl(): 此函数像个杂货铺,对设备的控制通常都通过这个函数来实行,具体对设备的操作方式取决于设备驱动程序的编写。fcntl()和ioctl()的区别如同fread()和read()函数的关系。
            5. ioctlsocket(): 此函数只能用于windows平台,相当于linux下套接口函数ioctl()的一个子集。

2.1 setsockopt()

[cpp] view plaincopy
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);  
  4. int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);  

            参数sock: 将要被设置或者获取选项的套接字。
            参数level: 选项所在的协议层。level指定控制套接字的层次.可以取三种值: 
                SOL_SOCKET: 通用套接字选项. 
                IPPROTO_IP:  IP选项. 
                IPPROTO_TCP: TCP选项. 
            参数optname:需要访问的选项名。
            参数optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
            参数optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。


            SOL_SOCKET通用套接字选项,可以认为是应用socket应用层的选项,包括以下各选项:
======================================================================== 
选项名称        说明                  数据类型 
======================================================================== 
SO_ACCEPTCONN                 套接口正在用listen()监听                                     int
SO_BROADCAST                    允许发送广播数据                                                 int 
SO_DEBUG                              允许调试                                                                int 
SO_DONTROUTE                    不查找路由,禁止选径                                         int 
SO_ERROR                              获得套接字错误                                                    int 
SO_KEEPALIVE                       保持连接                                                                int 
SO_LINGER                              延迟关闭连接                                                       struct linger 
SO_OOBINLINE       带外数据放入正常数据流                                     int 
SO_RCVBUF                             接收缓冲区大小                                                   int 
SO_SNDBUF                             发送缓冲区大小                                                   int 
(SO_RCVLOWAT)                    接收缓冲区下限                                                    int 
(SO_SNDLOWAT)                   发送缓冲区下限                                                    int 
(SO_RCVTIMEO)                     接收超时                                                                struct timeval 
(SO_SNDTIMEO)                     发送超时                                                                struct timeval 
SO_REUSERADDR                 允许重用本地地址和端口                                      int 
SO_TYPE                                  获得套接字类型                                                    int 
SO_BSDCOMPAT                   与BSD系统兼容                                                    int 

======================================================================== 

           注:上述括号中的选项表示window下不支持的但linux支持之选项



            IPPROTO_IP选项,可以认为是操作系统为应用程序开的接口,用于控制底层IP层的一些特性,包括以下各列:
======================================================================== 
选项名称        说明                  数据类型 
======================================================================== 
IP_HDRINCL                        在数据包中包含IP首部                                          int 
(IP_OPTINOS)                     IP首部选项                                                            int 
IP_TOS                                  服务类型 
IP_TTL                                   生存时间                                                               int 
======================================================================== 

           注:上述括号中的选项表示window下不支持的但linux支持之选项



            IPPROTO_TCP选项,可以认为是操作系统为应用程序开的接口,用于控制底层TCP层的一些特性,包括以下各列:
======================================================================== 
选项名称        说明                  数据类型 
======================================================================== 
(TCP_MAXSEG)                  TCP最大数据段的大小                                        int 
TCP_NODELAY                  不使用Nagle算法                                                 int 

======================================================================== 

           注:上述括号中的选项表示window下不支持的但linux支持之选项


            返回值(Linux):
            成功执行时,返回0。失败返回-1,errno被设为以下的某个值   
               EBADF:sock不是有效的文件描述词
               EFAULT:optval指向的内存并非有效的进程空间
               EINVAL:在调用setsockopt()时,optlen无效
               ENOPROTOOPT:指定的协议层不能识别选项
               ENOTSOCK:sock描述的不是套接字

            返回值(Windows):
               WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
               WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
               WSAEFAULT:optlen参数非法。
               WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
               WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_ACCEPTCONN、SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
              WSAENOTSOCK:描述字不是一个套接口。

            注意:
            当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。


2.2 fcntl()

[cpp] view plaincopy
  1. #include <unistd.h>  
  2. #include <fcntl.h>  
  3. int fcntl(int fd, int cmd);  
  4. int fcntl(int fd, int cmd, long arg);  
  5. int fcntl(int fd,int cmd, struct flock *lock);  

            参数fd: 是被参数cmd操作的描述符.

            参数cmd: 对描述符操作的命令码。有如下选项:

==============================================================

            FD_DUPFD                                                                     复制文件描述符
            F_GETFD或者F_SETFD                                                获得/设置文件描述符
            F_GETFL或者F_SETFL                                                获得/设置文件状态值
            F_GETOWNF_SETOWN                                          获得/设置异步I/O所有权
            F_GETLK或者F_SETLK或者F_SETLKW                  获得/设置文件记录锁

            F_GETLEASE或者F_SETLEASE                                获得/设置文件租约

===============================================================


            参数arg: 参数cmd对应的命令参数,有些命令存在此arg,有些不存在。

            1. F_GETFL和F_SETFL的标志如下面的描述:
                O_NONBLOCK        非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误
                O_APPEND              强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
                O_DIRECT               最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.如果不能够避免缓存,那么它将最小化已经被缓存了的数据造 成                                                       的影响.如果这个标志用的不够好,将大大的降低性能
                O_ASYNC                当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

             注意:
             在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

              2. F_GETLK/F_SETLK/F_SETLKW 命令及标志如下面的描述:
                   F_GETLK          通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的的锁.取得的信息将覆盖传到fcntl()的flock结构的信息.如果没有发现能够阻止本次锁(flock)生成的锁,这 个结构将不被改变,除非锁的类型被设置成F_UNLCK.   
                   F_SETLK          按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件segment锁.F_SETLK被用来实现共享(或读)锁 (F_RDLCK)或独 占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK).如果共享锁或独占锁不能被设置,fcntl()将立即返 回EAGAIN.
                   F_SETLKW          除了共享锁或独占锁被其他的锁阻塞这种情况外,这个命令和F_SETLK是一样的.如果共享锁或独占锁被其他的锁阻塞,进程将等待直到这个请求能够   完成. 当fcntl()正在等待文件的某个区域的时候捕捉到一个信号,如果这个信号没有被指定SA_RESTART,fcntl将被中断.

               当一个共享锁被set到一个文件的某段的时候,其他的进程可以set 共享锁到这个段或这个段的一部分.共享所阻止任何其他进程set独占锁到这段保护区域的任何部分.如果文件描述符没有以读的访问方式打开的话,共享锁的设置请求会失败.
               独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁.如果文件描述符不是以写的访问方式打开的话,独占锁的请求会失败。

              参数flock的指针:
              flcok结构定义如下:
[cpp] view plaincopy
  1. struct flcok   
  2. {   
  3.        short int l_type; /* 锁定的状态*/  
  4.        //这三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET,l_start=0,l_len=0;  
  5.        short int l_whence;/*决定l_start位置*/   
  6.        off_t l_start; /*锁定区域的开头位置*/   
  7.        off_t l_len; /*锁定区域的大小*/  
  8.        pid_t l_pid; /*锁定动作的进程*/   
  9. };  

              其中l_type 有三种状态: 
                F_RDLCK 建立一个供读取用的锁定 
                F_WRLCK 建立一个供写入用的锁定 
                F_UNLCK 删除之前建立的锁定

              l_whence 也有三种方式: 
                SEEK_SET 以文件开头为锁定的起始位置。 
                SEEK_CUR 以目前文件读写位置为锁定的起始位置 
                SEEK_END 以文件结尾为锁定的起始位置。 

              返回值:
              与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。
              下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。
              第一个返回新的文件描述符,
              第二个返回相应标志,
              最后一个返回一个正的进程ID或负的进程组ID。

               fcntl是控制文件属性的专用命令,和socket相关的选项,用的比较多的可能就是设置阻塞或者非阻塞socket了。如下:
[cpp] view plaincopy
  1. fcntl(fd, F_SETFL, O_NONBLOCK)   

2.3 ioctl()

[cpp] view plaincopy
  1. #include<sys/ioctl.h>  
  2. int ioctl(int handle, int cmd, char* argp);  

              参数handle: 是被参数cmd操作的描述符.
              参数cmd: 对描述符操作的命令码。有如下选项:
              参数argp: 参数cmd对应的命令参数,有些命令存在argp,有些不存在

              cmd和argp的关系如下:
============================================================================================
类别Request 说明数据类型
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
套接口                    SIOCATMARK                       是否位于带外标记                                                int
                                SIOCSPGRP                         设置套接口的进程ID 或进程组ID                        int
                                SIOCGPGRP                         获取套接口的进程ID 或进程组ID                        int
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
文件                        FIONBIO                                 设置/ 清除非阻塞I/O 标志                                     int
                                FIOASYNC                             设置/ 清除信号驱动异步I/O 标志                         int
                                FIONREAD                             获取接收缓存区中的字节数                                int
                                FIOSETOWN                         设置文件的进程ID 或进程组ID                            int
                                FIOGETOWN                         获取文件的进程ID 或进程组ID                           int
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
接口                        SIOCGIFCONF                       获取所有接口的清单                                           struct ifconf
                                SIOCSIFADDR                       设置接口地址                                                       struct ifreq
                                SIOCGIFADDR                       获取接口地址                                                       struct ifreq
                                SIOCSIFFLAGS                      设置接口标志                                                       struct ifreq
                                SIOCGIFFLAGS                      获取接口标志                                                       struct ifreq
                                SIOCSIFDSTADDR               设置点到点地址                                                   struct ifreq
                                SIOCGIFDSTADDR               获取点到点地址                                                   struct ifreq
                                SIOCGIFBRDADDR              获取广播地址                                                       struct ifreq
                                SIOCSIFBRDADDR              设置广播地址                                                       struct ifreq
                                SIOCGIFNETMASK               获取子网掩码                                                       struct ifreq
                                SIOCSIFNETMASK               设置子网掩码                                                       struct ifreq
                                SIOCGIFMETRIC                   获取接口的测度                                                   struct ifreq
                                SIOCSIFMETRIC                   设置接口的测度                                                   struct ifreq
                                SIOCGIFMTU                         获取接口MTU                                                       struct ifreq
                                SIOCxxx                                  (还有很多取决于系统的实现)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
ARP                        SIOCSARP                           创建/修改ARP 表项                                               struct arpreq         
                                SIOCGARP                           获取ARP 表项                                                       struct arpreq
                                SIOCDARP                           删除ARP 表项                                                       struct arpreq
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
路由                        SIOCADDRT                         增加路径                                                               struct rtentry
                                SIOCDELRT                         删除路径                                                               struct rtentry
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
流                              I_xxx
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

2.4 ioctlsocket()

[cpp] view plaincopy
  1. #include <winsock.h>  
  2. int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);  

            参数s:一个标识套接口的描述字。
            参数cmd:对套接口s的操作命令。有如下选择:
                FIONBIO: 允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型,如允许非阻塞模式则非零,如禁止非阻塞模式则为零。当创建一个套接口时,它就处于阻 塞模式(也就是说非阻塞模式被禁止)。这与BSD套接口是一致的。WSAAsynSelect()函数将套接口自动设置为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口]重新设置成阻塞模式的试图将以WSAEINVAL失败。为了把套接口重新设置成阻塞模式,应用程序必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁止WSAAsynSelect()。
            FIONREAD:确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。如果s是SOCKET_STREAM类型,则FIONREAD返回在一次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。如果S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。

            SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据 (SO_OOBINLINE)。如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。应用程序可用   SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据[前有常规数据,则按序接收这些数据(请注意,recv()和recvfrom() 操作不会在一次调用中混淆常规数据与带外数]据)。argp指向一个BOOL型数,ioctlsocket()在其中存入返回值。

              参数argp:指向cmd命令所带参数的指针。

thanks:http://blog.csdn.net/arau_sh/article/details/12070053