插口API简介.doc

上传人:本田雅阁 文档编号:2527384 上传时间:2019-04-05 格式:DOC 页数:42 大小:324.52KB
返回 下载 相关 举报
插口API简介.doc_第1页
第1页 / 共42页
插口API简介.doc_第2页
第2页 / 共42页
插口API简介.doc_第3页
第3页 / 共42页
插口API简介.doc_第4页
第4页 / 共42页
插口API简介.doc_第5页
第5页 / 共42页
点击查看更多>>
资源描述

《插口API简介.doc》由会员分享,可在线阅读,更多相关《插口API简介.doc(42页珍藏版)》请在三一文库上搜索。

1、第2章 插口API简介本章介绍Unix系统中,插口API的基本概念、数据结构和函数。它们是基于插口API的网络应用程序设计的基础。2-1 概 述在Unix系统中,插口API是最常用的网络应用编程接口。本章主要介绍插口API中的基本概念和数据结构,基本插口函数,插口I/O函数,插口选项函数,以及基于插口的网络应用程序中要用到的其他处理函数,如地址转换函数、字节操纵函数、主机信息查询函数等。本章是基于插口API的网络应用程序的基础。2-2 端口和插口访问TCP协议和UDP协议的插口应用程序设计中一个非常重要的概念是端口(port)。因为TCP和UDP均使用了与应用层接口处的端口与上层的应用进程进行

2、通信。换个角度来说,所有的应用进程必须通过相应的端口才能与运输层实体进行交互。端口是一个16bit的地址,用端口号来标识。端口号分为两类:(1)熟知端口(well-known port)。这类端口是专门分配给一些最常用的应用层程序的,数值为11023。这些端口由Internet已分配数值权威机构IANA分配和控制。如,访问UDP协议的著名应用所使用的端口号有:RPC的端口号是111,SNMP的端口号是161和162,TFTP的端口号是69;访问TCP协议的著名应用所使用的端口号有:SMTP端口号为25,FTP的端口号为21和22,TELNET的端口号为23等。在Unix系统中,将11023之间

3、的任何端口称为保留端口(reserved ports)。只有超级用户进程才可以使用这些端口。所有熟知端口均为保留端口,因此,使用这些端口的服务器启动时必须具有超级用户的特权。(2)一般端口(102465535)。这类端口不受IANA控制,只要不发生使用冲突,任何应用进程均可分配使用。一般来说,服务器进程需要用户指定自己的端口号,因为客户进程只有知道服务器的地址和端口号才能发起连接。而客户进程一般不关心自己使用的端口号,因此通常情况下,它的端口号由操作系统自动分配,系统能够保证不会分配正在使用的端口号给客户进程。但是,只有端口号还不能标识客户和服务器之间的通信连接。例如,主机A上的进程P1使用端

4、口号500同主机C上的使用端口25的进程P3建立连接,同时主机B上的进程P2也使用端口号500同主机C上的使用端口25的进程P3建立连接,显然,只有端口号P3是无法区分开P1和P2的。所以,应该将主机的IP地址和端口号结合起来一起使用。一条连接由它的两个端点来标识。端点就是我们熟悉的插口,插口API的名称因此而来。每一个插口是一个二元组(IP地址,端口号)。一对插口即可标识一条连接,它是一个四元组: (本地IP地址,本地端口,远程IP地址,远程端口)在整个Internet中,在运输层通信的一对插口必须是唯一的。对于提供无连接服务的UDP,同样适合上面的概念。这样才能区分开同时通信的多个主机中的

5、多个进程。事实上,可以将插口理解为一次通信过程的标识,而不管其是否是有连接的。这时,可以用一个五元组来标识一个通信过程: (本地IP地址,本地端口,使用协议,远程IP地址,远程端口)在进行基于插口API的网络应用程序设计时,必须指定或获取连接标识四元组中的每一个元素后,通信才能开始。通信完成后,这些资源将被释放。2-3 基本数据结构在插口API中,最基本的也是最常用的数据结构是插口地址结构。因为每一种协议的地址格式可能不同,所以每个协议必须定义自己的插口地址结构。这些结构的名字均以 “sockaddr_”开头,外加一个唯一的后缀来区分不同的协议。同时,为了方便插口地址参数的传递,插口API又定

6、义了一个通用的插口地址结构sockaddr。本节将介绍一些常见的插口地址结构和通用插口地址结构。尽管不同系统中的协议实现因为遵循的标准有所不同,导致插口地址结构的定义上有些区别,但插口地址结构中的一些主要成员的名称都是一样的。这是为了基于插口API的网络应用程序的可移植性,同时也是Unix标准Posix.1中所要求的结果。2-3-1 IPv4插口地址结构IPv4插口地址结构在头文件中定义,结构名称为sockaddr_in。Unix标准Posix.1g中有关sockaddr_in的定义为: struct in_addr in_addr_t s_addr; /* 32-bit IPv4地址 */

7、; struct sockaddr_in uint8_t sin_len; /* length of structure(16) */ sa_family_t sin_family; /* AF_INET */. in_port_t sin_port; /* 16-bit TCP or UDP port number, network byte ordered */ struct in_addr sin_addr; /* 32-bit IPv4 address, network byte ordered */ char sin_zero8; /* unused */ ;上述结构中的长度成员si

8、n_len是为了支持OSI协议而设的,第一次出现在4.3BSD Reno中,以前的实现中第一个成员应为sin_family。今天,不同的系统实现中,有些有这个成员,有些则没有,如在Solaris 2.5操作系统中则没有这个成员,而在Digital Unix中有这个成员。在Solaris 2.5操作系统中,结构 in_addr 被定义为:struct in_addr union struct u_char s_b1, s_b2, s_b3, s_b4; S_un_b;struct u_short s_w1, s_w2; S_un_w;u_long S_addr; S_un;#defines_ad

9、drS_un.S_addr/* should be used for all code */#defines_hostS_un.S_un_b.s_b2/* OBSOLETE: host on imp */#defines_netS_un.S_un_b.s_b1/* OBSOLETE: network */#defines_impS_un.S_un_w.s_w2/* OBSOLETE: imp */#defines_impnoS_un.S_un_b.s_b4/* OBSOLETE: imp # */#defines_lhS_un.S_un_b.s_b3/* OBSOLETE: logical h

10、ost */;定义如此复杂的in_addr结构主要是为了与以前的Sun OS 3.x和4.2 BSD系统兼容,现在的大多数系统已经不再使用这种定义联合的方法,而是将in_addr定义为仅有一个无符号长整数成员的结构,如标准Posix.1g中的定义以及下面将要介绍的Digital Unix系统中的定义。在Solaris 2.5中sockaddr_in被定义为:struct sockaddr_in shortsin_family; u_shortsin_port; structin_addr sin_addr; char sin_zero8; ;在Digital Unix 操作系统中,上述两个结构

11、的定义稍为复杂一些,这主要是为了不同版本的兼容性: struct in_addr #ifdef _XOPEN_SOURCE_EXTENDED /* typedef changed for Spec 1170 conformance: */ /* from u_int to in_addr_t (both are defined as unsigned int)*/in_addr_t s_addr;#elseunsigned int s_addr;#endif;#if defined(_SOCKADDR_LEN) | defined(_KERNEL) | defined(_XOPEN_SOURC

12、E_EXTENDED) struct sockaddr_in unsigned charsin_len; sa_family_tsin_family;/* New typedef for Spec 1170 */ in_port_tsin_port;/* New typedef for Spec 1170 */ structin_addr sin_addr;#ifdef _XOPEN_SOURCE_EXTENDED unsigned charsin_zero8;/* Changed type from char to*/* unsigned char for Spec 1170*/#else

13、char sin_zero8;#endif;#elsestruct sockaddr_in unsigned shortsin_family;unsigned shortsin_port;structin_addr sin_addr;char sin_zero8;#endif从以上的讨论可以看出,对于长度成员sin_len,各种实现表现不一。因此,即使有长度成员,一般的网络应用程序也不要设置它。它主要是被内核用作处理来自不同协议族的插口地址结构的例程(如,路由表代码)所使用的。 一般情况下,网络应用程序只需要用到sockaddr_in中的sin_family,sin_port,sin_addr

14、等三个成员,而且在所有的实现中,这三个成员的名称都是一样的,尽管其对应的类型可能会有所不同。除此之外,几乎所有的实现中均有sin_zero8成员,因此,所有的插口地址结构大小至少是16字节。成员sin_zero一般不用,所以应将其置0,通常的做法是在使用sockaddr_in之前将整个结构清0。 还有一点要说明的是,成员sin_port和sin_addr.s_addr应总是以网络字节顺序而不是主机字节顺序来存储。2-3-2 IPv6插口地址结构IPv6插口地址结构与IPv4插口地址结构有些不同,因为IPv6中的IP地址的长度是128 比特。有关支持IPv6的插口API的定义涉及的标准是RFC2

15、133,Posix.1g中还没有这方面的内容。因此,有很多操作系统上的插口API中还没有IPv6的地址结构。IPv6插口地址结构如下: struct in6_addr uint8_t s6_addr16; /* 128-bit IPv6 address, network byte ordered */ ; #define SIN6_LEN /* required for compile-time tests */ struct sockaddr_in6 uint8_t sin6_len; /* length of this struct(24) */ sa_family_t sin6_fami

16、ly; /* AF_INET6 */ in_port_t sin6_port; /* transport layer ports, network byte ordered */ uint32_t sin6_flowinfo; /* priority & flow label, network byte ordered */ struct in6_addr sin_addr; /* IPv6 address, network byte ordered */ ;从上面的定义可以看出,IPv6插口地址结构中的各成员名与IPv4插口地址结构中对应的成员名字不相同。另外,IPv6插口地址结构比IPv4

17、插口地址结构的成员多,结构长度也大一些。2-3-3 通用插口地址结构同IPv4插口地址结构相似,通用插口地址结构中也存在着是否有无长度成员的问题。一些实现中有长度成员字段sa_len,一些实现中没有。但是,在同一个实现中,在通用插口地址结构和IPv4插口地址结构中,要么都有长度成员,要么都没有。下面举例来说明通用插口地址结构sockaddr的定义。在Solaris 2.5中sockaddr的定义如下: struct sockaddr u_shortsa_family;/* address family */ char sa_data14;/* up to 14 bytes of direct

18、address */ ;在Digital Unix中sockaddr的定义如下: #if defined(_SOCKADDR_LEN) | defined(_KERNEL) | defined(_XOPEN_SOURCE_EXTENDED) | defined(_POSIX_PII_SOCKET) struct sockaddr unsigned char sa_len;/* total length */sa_family_tsa_family;/* address family */charsa_data14;/* actually longer; address value */ ;#e

19、lse/* BSD4.3 */ struct sockaddr unsigned shortsa_family;/* address family */charsa_data14;/* up to 14 bytes of direct address */ ;#endif/* BSD4.3 */定义通用插口地址结构的目的是为了解决插口函数中插口地址参数的传递问题。具体来说,当将插口地址作为参数传递给任何一个插口函数时,总是通过指针来传递,但是插口函数必须处理来自所有支持的任何协议族的插口地址结构,所以需要定义一个通用的插口地址结构作为传递参数的指针类型。例如,用ANSI C来描述插口函数bin

20、d的原形如下: int bind(int, struct sockaddr *, socklen_t);这就要求对这些函数的任何调用必须将指向特定协议的插口地址结构的指针类型转换成指向通用插口地址结构的指针。如: struct sockaddr_in client; /* IPv4插口地址结构 */ /* 省去了中间代码 */ bind(sockfd, (struct sockaddr *)&client, sizeof(client);如果不进行强制类型转换,则在编译时会出现类型不兼容的编译告警。2-4 基本插口函数本节介绍基本的插口函数:socket(),bind(),connect(),

21、accept(),listen(),getsockname(),getpeername(),gethostbyname(),close()和shutdown()。2-4-1 socket函数任何基于插口API的网络编程,第一个要调用的插口函数就是socket(),应用进程用它来创建一个新的指定类型(由协议族、类型和协议来确定)的插口。socket函数原型如下: #include int socket(int family, int type, int protocol); 第一个参数family指定协议族,通常使用的协议族如表2-1所示。表2-1 socket函数中的family值的常量定义协

22、 议 族 (family)含 义AF_INETIPv4协议AF_INET6IPv6协议AF_LOCALUnix域协议AF_ROUTE路由插口AF_KEY密钥插口AF_LINK数据链路层接口AF_OSIOSI协议AF_DECnetDECnet协议AF_SNAIBM的SNA协议AF_CCITT10CCITT协议,如X.25但是在很多实现中,还有另一组常量与上述定义相类似,这些定义以PF_开头,每一个以AF_开头的常量定义都有一个PF_开头的常量定义相对应,除常量名的前缀(AF_与PF_)不一样,后缀都是一样的,如AF_INET对应PF_INET,并且几乎所有的实现都利用AF_的值来定义PF_的值。

23、这是由历史原因造成的。前缀AF_表示地址族,PF_表示协议族。历史上曾认为,单个协议族可以支持多个地址族,PF_值用来创建插口,而AF_值用于指定插口地址结构。但实际上,支持多个地址族的协议从来没有实现过。因此,出现了上述常量定义一一对应的情况。一般的网络应用程序都采用AF_开头的常量定义。第二个参数type指定插口类型,可用的类型常量定义如表2-2所示。表2-2 socket函数中的type值的常量定义类 型 (type)含 义SOCK_STREAM字节流插口SOCK_DGRAM数据报插口SOCK_RAW原始插口(raw socket)表2-1和表2-2中的常量在头文件socket.h中定义

24、,不同实现中这些常量的名字可能会有些差别。例如,在Digital Unix中,AF_LOCAL的名称为AF_UNIX,SOCK_RAW的名称为SOCK_RAW3;而在Solaris 2.5中,则为SOCK_RAWNC_TPI_RAW。而且,常量的个数(即支持的协议种类)也会有所不同。但是,一些常见的常量定义则都是一样的,如AF_INET,AF_ROUTE,SOCK_STREAM,SOCK_DGRAM。另外,并非所有插口family与type的组合都是有效的,表2-3列出了一些常见的有效组合和对应的网络协议。表2-3 socket函数中的协议族(family)与类型(type)的组合类型(typ

25、e)AF_INETAF_INET6AF_LOCALAF_ROUTEAF_KEYSOCK_STREAMTCPTCP支持不支持不支持SOCK_DGRAMUDPUDP支持不支持不支持SOCK_RAWIPv4IPv6不支持支持支持第三个参数protocol为协议,一般将其设置为0,除非用在原始插口上。protocol的常量在头文件中定义,常量名以IPPROTO_开头,如IPPROTO_IGMP。如果函数调用成功,则返回插口描述符(descriptor,也可称为“句柄”或“描述字”等,本书用“描述符”)给应用进程。其他的插口API函数需要使用这个描述符来标识插口。如果失败,则返回-1,错误代码保存在er

26、rno中。2-4-2 bind函数bind函数将一个本地的协议地址和插口联系起来。对于网际协议,协议地址由一个二元组构成:(IP地址,端口号)。对于IPv4而言,IP地址长度为32比特,对于IPv6而言,长度为128比特。一般来说,作为客户的进程并不关心它的本地地址是什么,在这种情况下,进程在通信之前没有必要调用bind,内核会自动为其选择一个本地地址。bind函数原型如下: #include int bind(int sockfd, const sockaddr *myaddr, socklen_t addrlen);bind函数的第一个参数为插口描述符sockfd,它是socket()函数

27、的成功返回值。第二个参数是一个指向与协议有关的地址结构的指针,第三个参数则为地址的长度。对于服务器端而言,需要将插口绑定到一个已知端口上,因为只有这样客户端才知道该往何处发起连接。为了做到这一点,需要调用bind()函数,并设置bind函数的第二个参数和第三个参数。表2-4列出了常见的几种设置插口地址结构的方法。表2-4 设置插口地址结构的几种常见方式进 程 指 定说 明IP地址端口通配地址0内核自动选择IP地址和端口通配地址非0内核自动选择IP地址,进程指定端口本地IP地址0进程指定IP地址,端口由内核自动选择本地IP地址非0IP地址和端口均由应用进程指定表2-4中的“通配地址”通常由常量I

28、NADDR_ANY来指定,但对于IPv6的IP地址而言,就不能采用这种方式,因为IPv6 的地址是一个结构。一般来说,对于客户端而言,如果调用bind(),通常选择“由内核自动选择IP地址和端口”的方式。对于服务器端而言,如果调用bind(),且主机只有一个IP地址,则通常选择“内核自动选择IP地址,进程指定端口”的方式。如果主机有多个IP地址,则通常选择“IP地址和端口均由应用进程指定”的方式。如果想了解由内核分配的IP地址和端口号,可以调用函数getsockname()来获取,bind()函数并不提供这些信息。如果调用成功,函数返回 0,否则返回-1,错误代码保存在errno中。最常见的错

29、误是EADDRINUSE(地址已使用),对于这一点在插口选项函数中将有所论述。下面是一个调用bind函数的例子:struct sockaddr_in serv_addr;int sockfd; /* 插口描述符 */short port=3500; /* 端口号 */bzero(char *)&serv_addr,sizeof(serv_addr); /* 将地址结构缓存清0 */serv_addr.sin_family=AF_INET;/* 设置IP地址,并将IP地址转换为网络字节顺序,但对于INADDR_ANY 可不转换,因为INADDR_ANY的值等于 0 */ serv_addr.si

30、n_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(port); /* 将端口号转换成网络字节顺序 */if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)0) /* 错误处理 */ /* 成功处理 */ 上例中,由内核自动选择IP地址,应用进程指定端口号。2-4-3 connect函数connect()函数的功能是建立一条与TCP服务器的连接。如果进程在调用connect()之前没有调用bind()来绑定本地地址,则内核选择并且隐式地绑定一个地址到插口

31、。connect()函数原型如下: #include int connect(int sockfd, const struct sockaddr *servaddr, socklen addrlen);函数的第一个参数sockfd为socket调用返回的插口描述符。第二个参数指向插口地址结构的指针,第三个参数为地址结构的大小。插口地址结构中必须包含有服务器的IP地址和端口号。对于TCP而言,connect函数将激活TCP的建立连接的三次握手过程,且仅在连接成功或出错时才返回,返回的错误可能有以下几种:(1)如果TCP客户没有收到SYN报文段的响应,则返回ETIMEOUT。(2)如果服务器对客户

32、的SYN的响应是RST,则表明该服务器主机在客户端指定的端口上没有进程在等待连接请求,在这种情况下,客户端一收到RST,就立即返回错误ECONNREFUSED。(3)如果客户发出的SYN在中间路由器上引发了一个目的地不可达ICMP错误,内核将保存此消息,并按一定间隔连续发出SYN。若在规定的时间内仍未收到响应,则返回错误EHOSTUNREACH或ENETUNREACH。有关TCP的三次握手的连接过程将在下一章详细讨论。如果成功,则返回0,否则返回-1,错误代码保存在errno中。有一点特别重要:如果函数connect()失败,则插口不可用,必须关闭,不能对此插口再调用connect()函数。如

33、果要重新建立连接,必须重新调用socket函数。下面是一个调用connect()函数的例子: struct sockaddr_in serv_addr;char srvname= “10.65.19.10”; /* 服务器地址 */int sockfd; /* 插口描述符 */ /* 已调用socket产生sockfd */bzero(char *)&serv_addr,sizeof(serv_addr); /* 初始化服务器地址结构 */serv_addr.sin_family=AF_INET; /* 协议地址类型 */serv_addr.sin_addr.s_addr=inet_addr(

34、srvrname); /*将点分IP地址转换成整型地址 */serv_addr.sin_port=htons(port); /*将端口号转换为网络字节顺序 */ if (connect(sockfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr)0) /* 错误处理 */else /* 连接建立成功 */ 有一点需要说明的是,connect()函数也可用于UDP客户,但在这种情况下,connect()函数并不是真正地发出连接请求,而是仅仅将服务器的地址信息保存下来。在后续的UDP插口上发送数据的过程中,由插口层自动在发送函数中填入服务器地址而不需

35、要由应用程序调用发送函数时填写。2-4-4 listen函数listen()函数的功能是通知协议进程准备接收插口上的连接请求。因此,它只用于TCP服务器。同时,它还指定插口上可以排队等待的连接数的门限值。超过此门限值时,插口层将拒绝进入的连接请求排队等待。在这种情况下,TCP将忽略进入的连接请求。在服务器端,一般来说,listen()函数应在调用socket()和bind()之后,accept()调用之前才被调用;并且在调用accept()之前,必须调用listen()函数。listen()函数的原型如下: #include int listen(int sockfd, int backlog

36、);函数的第一个参数sockfd为socket函数返回的插口描述符,第二个参数规定了内核为此插口排队的最大连接数。对于给定的监听插口,内核要维护两个队列:未完成连接队列(incomplete connection queue)和已完成连接队列(completed connection queue)。已完成连接队列中存放那些已完成TCP的三次握手过程的连接,插口处于ESTABLISHED状态,其他的连接均放在未完成连接队列中。函数listen的第二个参数backlog被规定为两个队列总和的最大值,不能将此参数设置为0。有关backlog值的确切含义比较复杂,有兴趣的读者可参文献1。如果成功则返回

37、0,否则返回-1,错误代码保存在errno中。2-4-5 accept函数accept函数的功能是等待客户端的连接请求。如果已完成连接队列非空,则返回下一个已完成连接的连接描述符,代表与客户进程的连接;否则,如果插口是阻塞方式,则进程将睡眠等待;如果是非阻塞方式,进程将立即返回-1。在成功返回连接描述符后,原来的插口仍然是未连接的,并准备接收下一条连接。accept函数原型如下: #include int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);函数的第二个参数cliaddr和第三个参数addrlen分别

38、用来返回连接对方进程的协议地址和地址长度。调用accept()时,参数addrlen指向的值是传入的地址结构的长度,当函数成功返回时,addrlen指向的值等于内核中返回的协议地址的长度。如果服务器进程不需要知道对方的地址信息,调用accept函数时,可以将这两个参数置为NULL。通常将accept函数中的第一个参数称为监听插口(listening socket)描述符(由socket函数产生的描述符,并作为bind和listen的第一个参数),而将accept()调用成功返回的插口描述符称为已连接插口(connected socket)描述符。一个服务器的监听插口打开后一直存在,直到该服务器

39、关闭,而对于已连接插口描述符,只要服务器完成与某客户的服务就可关闭其连接插口描述符。accept()函数只用于TCP服务器端。下面的例子说明对accept()的调用: struct sockaddr_in cli_addr; int clilen,fd,sockfd; /* 已调用socket()产生sockfd,并已成功调用bind(), listen() */ clilen=sizeof(struct sockaddr_in); /* 必须设置地址结构长度值 */ fd=accept(sockfd, (struct sockaddr *)&cli_addr,&clilen); if (fd

40、 0) /* 调用成功,服务客户 */else /* 失败处理 */ 2-4-6 getsockname函数getsockname函数的功能是获取与插口关联的本地协议地址。其函数原型如下: #include int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);函数的第一个参数sockfd为socket函数返回的插口描述符。第二个参数localaddr和第三个参数addrlen分别用来返回与插口关联的本地协议地址和地址长度。调用getsockname时,需将addrlen设置为结构localadd

41、r的大小,函数返回时由内核设置addrlen的值。一般来说,只有在内核自动分配IP地址或端口的情况下,应用进程才有可能需要调用此函数来获取插口的协议地址信息。如果调用失败,则返回-1,错误代码保存在errno中。2-4-7 getpeername函数getpeername函数的功能是获取与插口关联的远程协议地址。其函数原型如下: #include int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);函数的第一个参数sockfd为插口描述符。第二个参数peeraddr和第三个参数addrlen分别用

42、来返回与插口关联的远程协议地址和地址长度。调用getpeername时,需将addrlen设置为结构peeraddr的大小,函数返回时由内核设置addrlen的值。如果调用失败,则返回-1,错误代码保存在errno中。2-4-8 shutdown函数shutdown函数的主要功能是按指定方式来关闭插口的读通道、写通道或读写通道。函数原型如下: #include int shutdown(int sockfd, int howto);函数的第二个参数howto指定关闭方式,关闭方式的定义如表2-5所示。表2-5 shutdown函数中的关闭方式关闭方式(howto)说 明SHUT_RD关闭连接的

43、读通道。在插口上不能再发出读请求,并且插口接收缓存中的所有数据都被丢弃;再接收的任何数据也被TCP丢弃。但是进程仍可往插口上发送数据,对插口的发送缓存没有任何影响SHUT_WR关闭连接的写通道。在插口上不能再发送数据,但插口发送缓存中的数据仍将被发送出去。发送完成后,发送正常的TCP连接终止序列(FIN)。但进程仍可从插口上接收数据,对插口的接收缓存没有任何影响SHUT_RDWR关闭连接的读写通道。在插口上不能再发出任何读、写请求。完成的功能相当于分别执行了SHUT_RD和SHUT_WRshutdown函数并没有关闭插口和释放描述符。为了关闭插口和释放描述符,必须调用close函数,但可以在没

44、有调用shutdown的情况下直接调用close。如果调用成功则返回0,否则返回-1,错误代码保存在errno.h中。2-4-9 close函数在插口编程中,函数close的主要功能是关闭插口,终止TCP连接,但在Unix系统中,它可应用于任何种类的描述符。close函数的原型如下: #include int close(int sockfd);每个文件或插口都有一个访问计数,它表示当前指向该文件或插口的打开的描述符个数。每调用一次close,close就将描述符的访问计数减1,当此计数为0时close才将插口关闭。因此,在使用子进程的方式实现的并发服务器中,在父进程中关闭连接插口描述符以及在

45、子进程中关闭监听插口描述符均没有导致插口被关闭就是这个原因(有关细节将在后续章节中介绍)。而shutdown函数则不管访问计数,而是立即发起TCP的正常的连接终止序列。另外,close函数终止插口上的读写通道。这一点与shutdown的功能也有所不同,该函数可以指定关闭读通道或写通道。使用close关闭TCP插口时,默认功能是在插口上打上“已关闭”标记,并立即返回到进程。此后,这个插口描述符就不能再为进程所用,即不能用它作为I/O函数的参数。但TCP协议层仍然试着发送已排队待发的数据,然后按正常的TCP连接终止序列终止连接。但在设置了SO_LINGER插口选项后,close的功能有所不同,详细情况将在本章中的插口选项中讨论。同其他类型的描述符一样,当进程结束时,内核将调用close,关闭所有还没有被进程关闭的描述符。如果调用成功则返回0,否则返回-1,错误

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1