网络编程之 Socket函数 (一)

来源:互联网 发布:指纹考勤机数据统计 编辑:程序博客网 时间:2024/04/28 22:54

1. 概述

          在网络协议中,分层思想是非常重要的,各层协议分工明确,各干各事。在现实世界中,IP网际层的实现分布在路由器和各种PC终端中,TCP传输层的实现则存在于PC终端。换句话说,路由器只实现IP协议,而终端PC的操作系统同时实现了IP和TCP层协议。为了让开发者实现各种应用程序,不同的操作系统都会提供了一组socket函数,供开发者使用。通过socket函数,开发者能够进行网络通讯,并且能够对IP/TCP层协议的某些特性进行控制。

          在所有的套接字函数中,Berkeley套接字(也称为BSD套接字)使用的最为广泛。由于专利原因,Berkeley套接字由C语言实现,只被使用在UNIX操作系统上。但其接口形式成为了事实上的网络套接字的标准,不同操作系统都有类似接口,包括Linux和Window;大多数其他的编程语言也使用了与Berkeley套接字类似的接口。

          Berkeley套接字API库提供的函数包括:
          1. socket() 创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
          2. bind() 一般用于服务器端,将一个套接字与一个套接字地址结构相关联。
          3. listen() 用于服务器端,使一个绑定的TCP套接字进入监听状态。
          4. connect() 用于客户端,为一个套接字分配一个自由的本地端口号。 如果是TCP套接字的话,它会试图获得一个新的TCP连接。
          5. accept() 用于服务器端。 它接受一个从远端客户端发出的创建一个新的TCP连接的接入请求,创建一个新的套接字,与该连接相应的套接字地址相关联。
          6. send()recv(),或者write()read(),或者recvfrom()sendto(), 用于往/从远程套接字发送和接受数据。
          7. close() 用于系统释放分配给一个套接字的资源。 如果是TCP,连接会被中断。
          8. shutdown() 用于关闭socket连接。该函数允许只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。
          9. gethostbyname()gethostbyaddr() 用于解析主机名和地址。
          10. select() 用于修整有如下情况的套接字列表: 准备读,准备写或者是有错误。
          11. poll() 用于检查套接字的状态。 套接字可以被测试,看是否可以写入、读取或是有错误。
          12. getsockopt() 用于查询指定的套接字一个特定的套接字选项的当前值。
          13. setsockopt() 用于为指定的套接字设定一个特定的套接字选项。
          14. htons()htonl()ntohs()ntohl() 用于网络序和本机序转换。
          15. inet_addr() 用于字符串形式的IP地址和网络序形式的IP地址的转换。
          16. inet_ntoa() 用于网络序形式的IP地址和字符串形式的IP地址的转换。


2. 结构体和函数

2.1 结构体

          套接字用到的各种数据类型
          1. socket描述符。它是一个int值。在window下可能被定义为SOCKET,SOCKET也是一个int值,根据平台不同,被定义为int32或者int64。
[cpp] view plaincopy
  1. struct sockaddr {   
  2.    unsigned short sa_family; /* 地址家族, AF_xxx */   
  3.    char sa_data[14]; /*14字节协议地址*/   
  4. };   

          这个结构被为许多类型的套接字储存套接字地址信息。sa_family一般为"AF_INET",sa_data包含套接字中的目标地址和端口信息。在一个字符数组中同时存储目标地址和端口信息非常令人困惑,所以还存在一个并列的结构,struct sockaddr_in ("in" 代表 "Internet"。),其定义如下:
[cpp] view plaincopy
  1. #include <netinet/in.h>  
  2. struct sockaddr_in {   
  3.    short int sin_family; /* 通信类型 */   
  4.    unsigned short int sin_port; /* 端口 */   
  5.    struct in_addr sin_addr; /* Internet 地址 */   
  6.    unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/   
  7. };   


          可以看到其中sin_zero 被加入到这个结构,目的是为了使sockaddr_in和 struct sockaddr 长度一致,可以使用函数 bzero() 或 memset() 来全部置零。 而 sin_family 和 struct sockaddr 中的 sa_family 一致,能够设置为 "AF_INET"。结构体 sin_port和 sin_addr 则是网络字节顺序 (Network Byte Order)。(关于网络序可见文章数据类型转换)

          struct in_addr的定义如下:
[cpp] view plaincopy
  1. #include <netinet/in.h>  
  2. struct in_addr {   
  3.    unsigned long s_addr;   
  4. };   


2.2 字节转换:

[cpp] view plaincopy
  1. htons()--"Host to Network Short"  
  2. htonl()--"Host to Network Long"  
  3. ntohs()--"Network to Host Short"  
  4. ntohl()--"Network to Host Long"  

          其中 short (两个字节)和 long (四个字节)。这些函数对于变量类型 unsigned 也是适用对。假设想将 short 从本机字节顺序转换为网络字节顺序。可以用 "h" 表示 "本机 (host)",接着是 "to",然后用 "n" 表示 "网络 (network)",最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。


2.3 IP 地址和字符串转化:

          inet_addr()和inet_ntoa() 用于字符串和int值之间的字符串转换。使用如下:
[cpp] view plaincopy
  1. sockaddr_in ina;  
  2. ina.sin_addr.s_addr = inet_addr("192.168.1.1");  
  3.   
  4. char *a1;  
  5. a1 = inet_ntoa(ina.sin_addr); /* 这是192.168.1.1*/  
  6. printf("address 1: %s/n",a1);  


2.4 函数:

2.4.1 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 中将储存返回的错误值。

2.4.2 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会被清空,所以你不必多此一举。
0 0
原创粉丝点击