UC_SOCKET套接字学习小结

来源:互联网 发布:vb 删除字符串某个 编辑:程序博客网 时间:2024/06/05 05:17

/*********************************************************************
 ***  网络IPC:套接字
 *** 不同计算机上运行的进程相互通信的机制:网络进程间通信(netWork IPC)
 *** 网络IPC接口既可以用于计算机间通信,也可以用于计算机内通信
 *** 网络通信协议:TCP/IP协议、UDP协议
 *********************************************************************/

第一、套接字描述符
 1.套接字是通信端点的抽象。与应用程序要使用的文件描述符访问文件一样,套接字描述在UNIX系统中是用文件描述符实现的。许多处理文件描述符的函数都可以处理套接字描述符。
 创建套接字:#include <sys/socket.h>

int socket(int domain, int type, int protocol);//成功返回套接字描述符,若出错返回-1


 参数domain(域)确定通信的特性,表示域的常数都是以AF_开头,意指地址家族(address family).
  AF_INET----->IPv4因特网域
  AF_INET6---->IPv6因特网域
  AF_UNIX----->UNIX域
  AF_UNSPEC--->未指定
 参数type确定套接字的类型,进一步确定通信的特征.
  SOCK_DGRAM------->长度固定的,无连接的不可靠报文传递
  SOCK_RAW--------->IP协议的数据报接口
  SOCK_SEQPACKET--->长度固定,有序,可靠的面向连接的报文传递
  SOCK_STREAM------>有序、可靠、双向的面向连接字节流
 参数protocol通常是零,表示按给定的域和套接字类型选择默认协议。当对同一域和套接字类型支持多个协议时,可以用protocol参数选择一个特定协议。在AF_INET通信域中套接字类型SOCK_STREAM的默认协议时TCP,在AF_INET通信域中套接字类型SOCK_DGRAM的默认协议是UDP。
 对于数据报(SOCK_DGRAM)接口,与对方通信时不需要逻辑连接的。只需要送出一个报文,其地址是一个对方进程所使用的套接字。因此数据报提供了一个无连接的服务。另一方面,字节流(SOCK_STREAM)要求在交换数据之前,在本地套接字和与之通信的远程套接字之间建立一个逻辑连接。
 SOCK_SEQPACKET套接字和SOCK_STREAM套接字很类似,但从该套接字得到的是基于报文的服务而不是字节流服务。这意味着从SOCK_SEQPACKET套接字接收的数据量与对方所发送的一致。流控制传输协议(Stream Control Transmission Protocol,SCTP)提供了因特网域上的顺序数据包服务。
 SOCK_RAW套接字提供一个数据报接口用于直接访问下面的网络层(在因特网域中为IP)。使用这个接口时,应用程序负责构造自己的协议首部,这是因为传输协议(TCP和UDP等)被绕过了。当创建一个原始套接字时需要有超级用户特权,用以防止恶意程序绕过内建安全机制来创建报文。
 调用socket与调用open类似。在两种情况下均可获得用户输入/输出的文件描述符。当不再需要该文件描述符时,调用close来关闭对文件或套接字的访问,并且释放该描述符以便重新使用。

 2.套接字通信是双向的,可以采用函数shutdown来禁止套接字上的输入或输出。
 

#include <sys/socket.h>int shutdown(int sockfd,int how);



如果how是SHUT_RD(关闭读端),那么无法从套接字读取数据;如果how是SHUT_WR(关闭写端),那么无法使用套接字发送数据;使用SHUT_RDWR则将同时无法读取和发送数据。
 
第二、字节序
 字节序是一个处理器架构特性,用于指示像整数这样的大数据类型的内部字节顺序。
 如果处理器架构支持大端(big_endian)字节序,那么最大字节地址对应于数字最低有效字节(LSB)上;小端字节序则相反;数字最低字节对应于最小字节地址。不管字节如何排序,数字最高位总在左边,最低位总在右边。
 网络协议指定了字节序,因此异构计算机系统能够交换协议信息而不会混淆字节序。TCP/IP协议栈采用大端字节序。对于TCP/IP,地址用网络字节序来表示,所以应用程序有时需要在处理器的字节序与网络字节序之间的转换。
 #include <arpa/inet.h>
 uint32_t htonl(uint32_t hostint32);//返回以网络字节序表示的32位整数
 uint16_t htons(uint16_t hostint16);//返回以网络字节序表示的16位整数
 uint32_t ntohl(uint32_t netint32);//返回以主机字节序表示的32位整数
 uint16_t ntohs(uint16_t netint16);//返回以主机字节序表示的16位整数
 h---->主机(host),n---->网络(network),l---->长整型4字节(long)
 s---->短整型2字节(short) 

第三、地址格式
 地址标识了特定通信域中的套接字端点,地址格式与特定的通信域相关。为使不同格式的地址能够被传入到套接字函数,地址被强制转换成通用的地址结构sockaddr表示
 struct sockaddr{
  sa_family_t sa_family;/*地址家族*/
  char     sa_data[];/*变长地址地址*/
 }
 因特网地址定义在<netinet/in.h>中。在IPv4因特网域(AF_INET)中,套接字地址用如下结构sockaddr_in表示:
 struct in_addr{
  in_addr_t s_addr /*IPv4地址*/
 };
 struct sockaddr_in{
  sa_family_t sin_family;/*地址家族*/
  in_port_t   sin_port; /*端口*/
  struct in_addr sin_addr; /*IPv4地址*/
 }
 数据类型in_port_t定义成uint16_t. 数据类型in_addr_t定义成uint32_t.
 IPv6因特网域(AF_INET6)套接字地址用如下结构sockaddr_in6b表示:
 struct in6_addr{
  uint8_t s6_addr[16];/*IPv6地址*/
 }
 struct sockaddr_in6{
  sa_family_t sin6_family; /*地址家族*/
  in_port_t sin6_port;/*端口*/
  uint32_t sin6_flowifo; /*traffic class and flow info (流信息)*/
  struct in6_addr sin6_addr; /*IPv6地址*/
  uint32_t sin6_scope_id; /*set of interface for scope*/
 }
 IP地址二进制格式和点分十进制字符串表示之间相互转换。支持IPv4和IPv6
 #include <arpa/inet.h>

 将网络字节序的二进制地址转换成文本字符串格式,
 const char *inet_ntop(in domain, const void *restrict addr,
    char *retrict str, socklen_t size)
 /*成功返回地址字符串格式指针,出错返回NULL*/

 将文本字符串格式转换成网络字节序的二进制地址
 int inet_pton (int domain, const char *restrict str, void *restrict addr)
 /*成功返回1,若格式无效返回0,出错返回-1*/

 参数 domain仅支持两个值:AF_INET和AF_INET6 。对于inet_ntop参数size指定了用以保存文本字符串的缓冲区str的大小,两个常数用于简化工作:INET_ADDRSTRLEN定义了足够大的空间来存放表示IPv4地址的文本字符串,INET6_ADDRSTRLEN定义了足够大的空间来存放表示IPv6地址的文本字符串。对于inet_pton,如果domain是AF_INET,缓冲区addr需要足够大的空间来存放32位地址,如果domain是AF_INET6则需要足够大的空间来存放128位地址。

第四、将套接字与地址绑定

 1.可以用bind函数将一个众所周知的地址绑定到一个接收客户端请求的套接字上
 #include<sys/socket.h>
 int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
 /*成功返回0,失败翻译-1*/
 对于所能使用的地址有一些限制:
 1.在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址。
 2.地址必须和创建套接字时的地址族所支持的格式相匹配。
 3.端口号必须不小于1024,除非该进程具有相应的特权(即为超级用户)。
 4.一般只有套接字端点能够与地址绑定,尽管有些协议支持多重绑定。
 对于因特网域,如果指定IP地址为INADDR_ANY,套接字端点可以绑定到所有的系统网络接口。这意味着可以收到这个系统所安装的所有网卡的数据包。 
 可以调用函数getsockname来发现绑定到一个套接字的地址。

 #include <sys/socket.h>
 int getsockname(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict alenp);
 /*成功返回0,失败返回-1*/
 调用getsockname之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小。返回时,该整数会被设置成返回地址的大小,如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错。如果当前没有绑定到该套接字的地址,其结果没有定义。
 如果套接字已经和对方连接,调用getpeername来找到对方的地址。

 #include <sys/socket.h>
 int getpeername(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict alenp);
 /*若成功返回0,若出错返回-1*/
 除了还会返回对方地址外,该函数与getsockname一样。

第五、建立连接
 如果是面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKET),在开始交换数据以前,需要在客户端和服务器之间建立一个连接。可以用connect建立一个连接
 #include <sys/socket.h>
 int connect(int sockfd,const struct sockaddr *addr,socklen_t len);
 /*成功返回零,失败返回-1*/
 在connect中所指定的地址是想与之通信的服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
 

/******************************************************** *示例:支持重试
**********************************************************#include <sys/socket.h>#include "apue.h"#define MAXSLEEP 128int connect_retry(int sockfd, const struct sockaddr *addr,socklen_t len){int nsec;/*try to connect with exponential backoff*/for(nsec=1;nsec<=MAXSLEEP;nsec <<= 1){if(connect(sockfd,addr,len)==0){/*connect accepted*/return 0;}/*Delay before trying again*/if(nsec<=MAXSLEEP/2)sleep(nsec);}return -1;} **********************************************************/



 这个函数使用了名为指数补偿的算法,如果调用connect失败,进程就休眠一小段时间然后再尝试。没循环一次增加每次尝试的延迟,直到最大延迟为2分钟。

 connect函数还可以用于无连接的网络服务(SOCK_DGRAM). 这看起了有点矛盾,实际上是一个不错的选择,如果在SOCK_DGRAM套接字上调用connect,所有发送报文的目标地址设为connect调用中所指定的地址,这样每次传送报文的时候就不需要再提供地址。另外,仅能接收来自指定地址的报文。

 服务器用listen函数来宣告可以接受的连接请求数
 #include <sys/socket.h>
 int listen(int sockfd,int backlog);/*成功返回0,出错返回-1*/
 参数backlog提供了一个提示,用于表示该进程所要入队的连接请求数量。其实际值由系统决定。一旦队列满,系统会拒绝多余的连接请求,所以backlog的值应该基于服务器期望负载和接受连接请求与启动服务器的处理能力来选择。
 一旦服务器调用了listen,套接字就能接收连接请求,使用函数accept获得连接请求并建立连接。
 #include <sys/socket.h>
 int accept(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict len);
 /*若成功返回文件(套接字)描述符,若出错返回-1*/
 函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持可用状态并接受其他连接请求。
 如果不关心客户端标识,可以将参数addr和len设置为NULL。如果没有连接请求,accept会阻塞直到有一个连接请求到来,如果sockfd,处于非阻塞模式下,accept会返回-1并将errno设置为EAGAIN或EWOULDBLOCK。
 如果服务器调用了accept并且当前没有连接请求,服务器就会阻塞。另外服务器可以调用poll或select来等待一个请求的到来。

第六、数据传输
 套接字端点被表示为文件描述符,因此,只要建立连接,就可以用read和write来通过套接字通信。
 如果想指定选项,从多个客户端接收数据,需要采用六个传递数据的套接字函数中的一个。
 三个用来发送数据的函数:
 #include <sys/socket.h>
 ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);

 ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
   const struct sockaddr *destaddr, socklen_t destlen);
   
 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
 /*成功返回发送字节数,失败返回-1*/
 参数: sockfd----socket套接字
  buf-------要发送的数据缓冲区 
  nbytes----要发送数据字节数
  destaddr---要发送的地址
  destlen----要发送的地址结构的字节数
  flags------标志
  标志:
  MSG_DONTROUTE 勿将数据路由出本网络
  MSG_DONTWAIT 允许非阻塞操作(等价于使用O_NONBLOCK)
  MSG_EOR  如果协议支持,此为记录结束
  MSG_OOB  如果协议支持,发送带外数据
 
 三个用来接收数据的函数:
 #include <sys/socket.h>
 ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
 
 ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
   struct sockaddr *restrict addr, socklen_t *restrict addrlen);

 ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
 /*成功返回以字节计数接收到的消息长度,出错返回-1*/
 参数: sockfd------socket套接字
  buf---------接收数据的缓冲区
  nbytes------缓冲区的大小
  len---------缓冲区大小
  flags-------标志
  addr--------从指定地址域接收数据
  addrlen-----指定地址结构的大小
  msg---------接收消息的结构(事先定义好)。
 recv套接字调用标志:
  MSG_OOB  如果协议支持,接收带外数据
  MSG_PEEK 返回报文内容而不真正取走报文
  MSG_TRUNC 即使报文被截断,要求返回的是报文的实际长度
  MSG_WAITALL 等待直到所有的数据可用
 recvmsg标志:
  MSG_CTRUNC 控制数据被截断
  MSG_DONTWAIT recvmsg处于非阻塞模式
  MSG_EOR  接收到记录结束符
  MSG_OONB 接收到带外数据
  MSG_TRUNC 一般数据被截断