Socket库函数介绍

来源:互联网 发布:域名属性 编辑:程序博客网 时间:2024/06/04 18:22

1 全球IP因特网

      TCP/IP实际上是一个协议族,其中每一个都提供不同的功能。例如,IP提供基本的命名方法和递送机制,这种递送机制能够从一台因特网主机往其他主机发送包,即数据报。IP机制从某种意义上而言是不可靠的,因为,如果数据报在网络中丢失或者重复,它并不会试图恢复。UDP稍微扩展了IP协议,这样一来,包可以在进程间而不是在主机间传送。TCP是一个构建在IP之上的复杂协议,提供了进程间可靠的全双工连接。

1.1 IP地址

      IP地址是一个32位的无符号整数。网络程序将其存放在如下的IP地址结构中:

struct in_addr{unsigned int s_addr;/*network byte order(big-endian)*/};

      为何要将标量IP地址放在结构体中?把一个标量放在结构体中,是套接字接口早期实现的不幸产物,现在更改已经太迟了。

      因特网主机可以有不同的主机字节顺序,TCP/IP为任意整数数据项定义了统一的网络字节顺序(即大端字节顺序)。在IP地址结构中存放的地址总是以网络字节顺序存放的,即使主机字节顺序是小端法。以下函数是在网络和主机字节顺序间实现转换:

unsigned long int htonl(unsigned long int hostlong);unsigned short int htons(unsigned short int hostlong);//返回:按照网络字节顺序的值unsigned long int ntohl(unsigned long int netlong);unsigned short int ntohs(unsigned short int netlong);//返回:按照主机字节顺序的值
htonl函数将32为整数由主机字节顺序转换为网络字节顺序,ntohl函数将32位整数从网络字节顺序转换为主机字节顺序。htons和ntohs函数为16位的整数执行相应的转换。

     IP地址通常以点分十进制表示法来表示的,每个字节由它的十进制值表示,并且用句点和其他字节间分开。因特网程序使用inet_aton和inet_ntoa函数来实现IP地址和点分十进制串之间的转换:

int inet_aton(const char *cp,struct in_addr *inp);//返回:成功为1,否则出错为0;char *inet_ntoa(struct in_addr in);//返回:指向点分十进制字符串的指针

    其中ntoa,aton的含义:n表示的是网络(network),a表示应用(application),to表示转换。

1.2 因特网域名

      因特网客户端和服务器互相通信时使用的是IP地址,但对人们而言,大整数是很难记住的,所以因特网定义了一组更加人性化的域名,以及一种将域名映射到IP地址的机制。因特网定义了域名集合和IP地址集合之间的映射,由DNS维护。DNS数据库由上百万的主机条目结构(host entry structure)组成,其中每一条定义了一组域名(一个官方名字和一组别名)和一组IP地址之间的映射。从数学意义上讲,可以认为每条主机条目就是一个域名和IP地址的等价类。

struct  hostent {//host entry structure        char    * h_name;           /* official domain name of host */        char    * * h_aliases;  /* alias list */        short   h_addrtype;             /* host address type */        short   h_length;               /* length of address */        char    * * h_addr_list; /* list of addresses */};
网络应用程序可以调用gethostbyname和gethostbyaddr函数,从DNS数据库中检索任意的主机条目:

int inet_aton(const char *cp,struct in_addr *inp);//返回:成功为1,否则出错为0;char *inet_ntoa(struct in_addr in);//返回:指向点分十进制字符串的指针

gethostbyname函数返回和域名name相关的主机条目。gethostbyaddr函数返回和IP地址addr相关联的主机条目。

1.3 因特网连接

      因特网客户端和服务器通过在连接上发送和接收字节流来通信。一个套接字是连接的一个端点。每个套接字都有相应的套接字地址,是一个因特网地址和一个16位的整数端口组成的,用“地址:端口”表示。一个连接是由它两端的 套接字地址唯一确定的。这对套接字地址叫作套接字对(socket pair),由元组表示:(cliaddr:cliport,servaddr:servport)。

1.4 套接字接口

     套接字接口(socket interface)是一组函数,用以创建网络应用。

1.4.1 套接字地址结构

      从内核角度来看,一个套接字就是通信的一个端点,从程序的角度来看,套接字就是一个有相应描述符的打开文件。因特网套接字地址放在类型为sockaddr_in的16字节结构中。如下:

//Generic socket address structure(for connect,bind,and accept)struct sockaddr{unsigned short sa_family;//protocol familychar sa_data[14];//address data};//internet-style socket address structurestruct sockaddr_in{unsigned short sin_family;//Address family(always AF_INET)unsigned short sin_port;//Port number in network byte orderstruct in_addr sin_addr;//IP address in network byte orderunsigned char sin_zero[8];//Pad to sizeof(struct sockaddr)};

     _in后缀是互联网络Internet的缩写。connect、bind、accept函数要求一个指向与协议相关的套接字地址结构的指针。套接字接口的设计者面临的问题是,如何定义这些函数,使之能接受各种类型的套接字地址结构。解决办法是定义套接字函数要求一个指向通用sockaddr结构的指针,然后要求应用程序将与协议特定的结构的指针强制转换成这个通用结构

1.4.2  socket函数

        客户端和服务器使用socket函数来创建一个套接字描述符(socket descriptor)。

int socket(int domain,int type,int protocol);//返回:若成功则为非负描述符,若出错则为-1
socket返回的clientfd描述符仅是部分打开的,还不能用于读写。如何完成打开套接字的工作,取决与我们是客户端还是服务器。

1.4.3 connect函数

        客户端通过调用connect函数来建立和服务器的连接。

int connect(int sockfd, struct sockaddr* serv_addr,int addrlen);//返回:若成功则为0,若出错则为-1
connect函数试图与套接字地址为serv_addr的服务器建立一个因特网连接,其中addrlen是sizeof(sockaddr_in)。connect函数会阻塞,一直到连接成功建立或是发生错误。若成功,sockfd描述符现在就准备好可以读写了,并且得到的连接是由套接字对(x:y,serv_addr.sin_addr,serv_addr.sin_port)刻画的,其中x表示客户端的IP地址,y表示临时端口,它唯一确定了客户端主机上的客户端进程。
1.4.4 bind函数

        剩下的套接字函数bind、listen和accept被服务器用来和客户端建立连接。

int bind(int sockfd,struct sockaddr* my_addr,int addrlen);//返回:若成功则为0,若出错则为-1
bind函数告诉内核将my_addr中的服务器套接字地址和套接字描述符socket联系起来。参数addrlen就是sizeof(sockaddr_in)。

1.4.5 listen函数

       客户端是发起请求的主动实体。服务器是等待来自客户端的连接请求的被动实体。默认情况下,内核会认为socket函数创建的描述符对应于主动套接字(active socket),它存在与一个连接的客户端。服务器调用listen函数告诉内核,描述符是被服务器而不是客户端使用的

int listen(int sockfd,int backlog);//返回:若成功则为0,若出错则为-1
listen函数将sockfd从一个主动套接字转化成为一个监听套接字(listening socket),该套接字可以接受来自客户端的连接请求。backlong参数暗示了内核在开始拒绝连接请求之前,应该放入队列中等待的未完成连接请求的数量。backlog参数的确切含义要求对TCP/IP协议的理解。通常我们会把它设置为一个较大的值,如1024。

1.4.6 accept函数

       服务器通过调用accept函数来等待来自客户端的连接请求。

int accept(int listenfd,struct sockaddr* addr,int *addrlen);//返回:若此成功则为非负连接描述符,若出错则为-1
accept 函数等待来自客户端的连接请求到达侦听描述符listenfd,然后在addr中填写客户端的套接字地址,并返回一个已连接描述符(connected descriptor),这个描述符可被用来利用I/O函数与客户端通信。

      监听描述符和已连接描述符之间的区别使很多人感到迷惑。监听描述符是作为客户端连接请求的一个端点。典型地,它被创建一次,并存在于服务器的整个生命周期。已连接描述符是客户端和服务器之间已经建立起来的连接的一个端点。服务器每次接收连接请求时都会创建一次,它只存在于服务器为一个客户端服务的过程中。

       下图描绘了监听描述符和已连接描述符的角色。在第一步中,服务器调用accept,等待连接请求到达监听描述符,具体地我们设定为描述3。回忆一下,描述符0-2是预留给了标准文件的。在第二步中,客户端调用connect函数,发送一个连接请求到listenfd。第三步,accept函数打开了一个新的已连接描述符connfd(假设为4),在clientfd和connfd之间建立连接,并随后返回connfd给应用程序。客户端也从connect返回,在这一点以后,客户端和服务器就可以分别通过读和写clientfd和connfd来回传送 数据了。

为何要有监听描述符和已连接描述符之间的区别?

     你可能很想知道为什么套接字接口要区分监听描述和已连接描述符。乍一看,这像是不必要的复杂化。然而,区分这两者被证明是很有用的,因为它使得我们可以建立并发服务器,它能够同时处理许多客户端连接。例如,每次一个连接请求到达监听描述符时,我们可以派生(fork)一个新的进程,它通过已连接描述符与客户端通信。




0 0
原创粉丝点击