购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 socket套接字

socket的中文名叫套接字,是UNIX系统开发的网络通信接口,也是在Internet上进行应用开发时最为通用的API。

3.4.1 socket和TCP/IP的关系

TCP/IP属于协议栈,它描述了在网络传输过程中,每一层应该做什么。但是没有说代码具体怎样实现。而socket套接字最初是在UNIX系统上运行的代码,它实现了TCP/IP的功能,使得最初装有UNIX系统的计算机能使用TCP/IP接入Internet。

在UNIX系统中,socket位于应用层和传输层之间。socket提供一个统一的接口,应用程序可以直接通过socket进行网络通信,然后由socket将数据传递到传输层。这样做的好处是socket可以像文件一样,使用打开、读写、关闭等操作去实现网络通信,体现了UNIX万物皆文件的思想,同时标准化的socket接口使得应用程序有良好的可移植性。socket在UNIX系统的位置如图3.10所示。

图3.10 socket的位置

3.4.2 创建socket套接字

socket提供一套API方便应用程序使用。本书这里介绍几个比较重要的函数。

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

函数返回:int类型的数值,我们通常称之为socket描述符。它非常重要,后面所有的socket操作要基于socket描述符。

参数列表:

(1)protofamily:即协议域,又称为协议簇(family)。常用的协议簇有:AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,UNIX域socket)、AF_ROUTE等。协议簇决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

(2)type:指定socket类型。常用的socket类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。

(3)protocol:指定协议。常用的协议有:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、SCTP传输协议、TIPC传输协议。

注意 :并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。当我们调用socket创建一个socket时,返回的socket描述字描述它存在于协议簇(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则当调用connect()、listen()时系统会自动随机分配一个端口。

3.4.3 bind函数

正如3.2.2小节所述,每个应用程序想要使用网络功能,都需要指定唯一的一个端口号。同样,socket套接字也可以使用bind函数来为socket套接字绑定一个端口号。需要注意的是,bind函数不是必需的,当应用程序没有使用bind指定端口号时,系统会自动分配一个随机端口号。

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

函数返回:int类型的数值。返回值为0则表示bind成功。返回EADDRINUSE则表示端口号已经被其他应用程序占用。

参数列表:

(1)sockfd:socket描述符,也就是上文创建socket套接字时的返回值。

(2)addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议簇的不同而不同,如IPv4对应的是:

IPv6对应的是:

UNIX域对应的是:

(3)addrlen:对应的是地址的长度。

3.4.4 connect函数

通常在使用TCP的时候,客户端需要连接到TCP服务器,连接成功后才能继续通信。连接函数如下:

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

函数返回:int类型的数值。返回值为0则表示connect成功,其中错误返回有以下几种情况。

(1)ETIMEDOUT:TCP客户端没有收到SYN分节响应。

(2)ECONNREFUSED:服务器主机在我们指定的端口上没有进程在等待与之连接,属于硬错误(hard error)。

(3)EHOSTUNREACH或者ENETUNREACH:客户端发出的SYN在中间某个路由器上引发一个“destination unreachable”(目标地不可抵达)ICMP错误,是一种软错误(soft error)。

参数列表:

(1)sockfd:socket描述符。

(2)addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。

(3)addrlen:对应的是地址的长度。

3.4.5 listen函数

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

     int listen(int sockfd, int backlog);

函数返回:int类型的数值,0则表示成功,-1则表示出错。

参数列表:

(1)sockfd:socket描述符。

(2)backlog:为了更好地理解backlog,我们需要知道内核为任何一个给定的监听socket套接字维护两个队列。

未完成连接队列:客户端已经发出连接请求,而服务器正在等待完成响应的TCP三次握手过程。

已完成连接队列:已经完成了三次握手连接成功了的客户端。

backlog通常表示这两个队列的总和的最大值。当服务器一天需要处理几百万个连接时,此时backlog则需要定义成一个较大的数值。指定一个比内核能够支持的最大值还要大的数值也是允许的,因为内核会自动把指定的偏大值改成自身支持的最大值,而不返回错误。

3.4.6 accept函数

accept函数由服务器调用,用于处理从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,则进程会休眠。

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

函数返回:int类型的数值。如果服务器与客户已经正确建立了连接,此时accept会返回一个全新的socket套接字,服务器通过这个新的套接字来完成与客户的通信。

参数列表:

(1)sockfd:socket描述符。

(2)addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。

(3)addrlen:对应的是地址的长度。

3.4.7 read和write函数

read函数负责从网络中接收数据,write负责把数据发送到网络中,通常有下面几组。

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

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

3.4.8 close函数

通常使用close函数来关闭套接字,并终止TCP连接。

     int close(int fd);

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

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

点击中间区域
呼出菜单
上一章
目录
下一章
×