15.2 套接字连接
你可以把套接字连接想象为打电话进一个繁忙的办公大楼。一个电话打到一家公司,接线员接听电话并把它转到正确的部门(服务器进程),然后再从那里转到电话要找的人(服务器套接字)。每个进入的电话呼叫(客户)都被转到正确的终端节点,而中间介入的接线员则可以空出来处理后续的电话。在开始学习Linux系统中的套接字连接是如何建立之前,我们需要先理解套接字应用程序是如何通过套接字来维持一个连接的。
首先,服务器应用程序用系统调用socket来创建一个套接字,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他进程共享。
接下来,服务器进程会给套接字起个名字。本地套接字的名字是Linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中。对于网络套接字,它的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点)。这个标识符允许Linux将进入的针对特定端口号的连接转到正确的服务器进程。例如,Web服务器一般在80端口上创建一个套接字,这是一个专用于此目的的标识符。Web浏览器知道对于用户想要访问的Web站点,应该使用端口80来建立HTTP连接。我们用系统调用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个命名套接字。系统调用listen的作用是,创建一个队列并将其用于存放来自客户的进入连接。服务器通过系统调用accept来接受客户的连接。
服务器调用accept时,它会创建一个与原有的命名套接字不同的新套接字。这个新套接字只用于与这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自其他客户的连接。如果服务器编写得当,它就可以充分利用多个连接带来的好处。Web服务器就会这么做以同时服务来自许多客户的页面请求。对一个简单的服务器来说,后续的客户将在监听队列中等待,直到服务器再次准备就绪。
基于套接字系统的客户端更加简单。客户首先调用socket创建一个未命名套接字,然后将服务器的命名套接字作为一个地址来调用connect与服务器建立连接。
一旦连接建立,我们就可以像使用底层的文件描述符那样用套接字来实现双向的数据通信。
实 验 一个简单的本地客户
下面是一个非常简单的套接字客户程序的例子client1.c。它创建一个未命名的套接字,然后把它连接到服务器套接字server_socket。关于socket系统调用的细节,我们将在讨论完与地址相关的一些问题之后再来介绍。
(1)包含一些必要的头文件并设置变量:
(2)为客户创建一个套接字:
(3)根据服务器的情况给套接字命名:
(4)将我们的套接字连接到服务器的套接字:
(5)现在就可以通过sockfd进行读写操作了:
运行这个程序时,它会失败,因为你还没有创建服务器端的命名套接字(具体的错误信息将随系统的不同而不同)。
实 验 一个简单的本地服务器
下面是一个非常简单的服务器程序server1.c,它接受来自客户程序的连接。它首先创建一个服务器套接字,将它绑定到一个名字,然后创建一个监听队列,开始接受客户的连接。
(1)包含必要的头文件并设置变量:
(2)删除以前的套接字,为服务器创建一个未命名的套接字:
(3)命名套接字:
(4)创建一个连接队列,开始等待客户进行连接:
(5)接受一个连接:
(6)对client_sockfd套接字上的客户进行读写操作:
实验解析
这个例子中的服务器程序一次只能为一个客户服务。它从客户那里读取一个字符,增加它的值,然后再把它写回去。在更加复杂的系统中,服务器需要为每个客户执行更多的处理工作,这种一次只为一个客户服务的做法就变得不可接受了,因为其他客户只有等到服务器结束上一个客户的处理任务后才能处理它的连接。我们将在后面看到几个允许同时处理多个连接的解决方案。
运行服务器程序时,它创建一个套接字并开始等待客户的连接。如果你在后台启动它,让它独立地运行,就可以在前台启动客户程序。如下所示:
服务器在开始等待客户连接时会打印出一条消息。在上面的例子中,服务器等待的是一个文件系统套接字,所以可以用普通的ls命令来看到它。
记住:用完一个套接字后,就应该把它删除掉,即使是在程序因接收到一个信号而异常终止的情况下也应该这么做。这可以避免文件系统应充斥着无用的文件而变得混乱。
访问权限前面的字母s和这一行末尾的等号=表示该设备的类型是“套接字”。套接字的创建过程与普通文件一样,它的访问权限会被当前的掩码值所修改。如果使用ps命令,你可以看到服务器正运行在后台。它目前处于休眠状态(STAT栏显示的是S),因此它没有消耗CPU资源。如下所示:
现在运行客户程序,你就可以成功地连接到服务器了。因为服务器套接字已经存在,所以你可以连接到它并与服务器进行通信。如下所示:
服务器的输出和客户的输出在我们的终端上混在了一起,但还是可以看出服务器从客户那里接收了一个字符,将它的值增加,然后再返回它。接着服务器继续运行并等待下一个客户的到来。如果同时运行多个客户,它们将被依次服务,但你看到的输出结果可能会更加混乱。如下所示:
15.2.1 套接字属性
要想完全理解在上面例子中所使用的系统调用,你需要先学习一些UNIX网络方面的知识。
套接字的特性由3个属性确定,它们是:域(domain)、类型(type)和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocol family)的不同而不同。每个协议族又可以使用一个或多个地址族来定义地址格式。
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它指的是Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即人们常说的IP地址。
“下一代”互联网协议Ipv6被设计用于克服标准IP带来的一些问题,特别是可用地址数量有限的问题。IPv6使用一个不同的套接字域AF_INET6和一个不同的地址格式。人们期望它能最终替换IP,但这一过程将需要经过许多年。虽然Linux也支持Ipv6实现,但这超出了本书介绍的范围。
虽然我们几乎总是用域名来指定因特网上的联网机器,但它们都会被转换为底层的IP地址。例如192.168.1.99就是一个IP地址。所有的IP地址都用4个数字来表示,每个数字都小于256,即所谓的点分四元组表示法(dotted quad)。当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址。
服务器计算机上可能同时有多个服务正在运行。客户可以通过IP端口来指定一台联网机器上的某个特定服务。在系统内部,端口通过分配一个唯一的16位的整数来标识,在系统外部,则需要通过IP地址和端口号的组合来确定。套接字作为通信的终点,它必须在开始通信之前绑定一个端口。
服务器在特定的端口等待客户的连接。知名服务所分配的端口号在所有Linux和UNIX机器上都是一样的。它们通常(但并不总是如此)小于1024,比如打印机缓冲队列进程(515)、rlogin(513)、ftp(21)和httpd(80)等。其中最后一个就是Web服务器的标准端口。一般情况下,小于1024的端口号都是为系统服务保留的,并且所服务的进程必须具有超级用户权限。X/Open规范在头文件netdb.h中定义了一个常量IPPORT_RESERVED,它代表保留端口号的最大值。
因为标准服务都对应标准的端口号,所以计算机之间可以轻松地互连,而不需要首先协商一个正确的端口号。本地服务可以使用非标准的端口地址。
第一个例子中的域是UNIX文件系统域AF_UNIX,即使是一台还未联网的计算机上的套接字也可以使用这个域。这个域的底层协议就是文件输入/输出,而它的地址就是文件名。我们的服务器套接字的地址是server_socket,当我们运行服务器程序时,就可以在当前目录下看到这个地址。
其他可以使用的域还包括:基于ISO标准协议的网络所使用的AF_ISO域和用于施乐(Xerox)网络系统的AF_XNS域。它们都不在本章的讨论范围之内。
2.套接字类型
一个套接字域可能有多种不同的通信方式,而每种通信方式又有其不同的特性。但AF_UNIX域的套接字没有这样的问题,它们提供了一个可靠的双向通信路径。在网络域中,我们就需要注意底层网络的特性,以及不同的通信机制是如何受到它们的影响的。
因特网协议提供了两种通信机制:流(stream)和数据报(datagram)。它们有着截然不同的服务层次。
● 流套接字
流套接字(在某些方面类似于标准的输入/输出流)提供的是一个有序、可靠、双向字节流的连接。因此,发送的数据可以确保不会丢失、复制或乱序到达,并且在这一过程中发生的错误也不会显示出来。大的消息将被分片、传输、再重组。这很像一个文件流,它接收大量的数据,然后以小数据块的形式将它们写入底层磁盘。流套接字的行为是可预见的。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。它们也是AF_UNIX域中常用的套接字类型。在本章中,我们将重点学习SOCK_STREAM套接字,因为它们在编写网络程序时是最常用的。
TCP/IP代表的是传输控制协议(Transmission Control Protocol)/网际协议(Internet Protocol)。IP协议是针对数据包的底层协议,它提供从一台计算机通过网络到达另一台计算机的路由。TCP协议提供排序、流控和重传,以确保大数据的传输可以完整地到达目的地或报告一个适当的错误条件。
● 数据报套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接。它对可以发送的数据报的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失、复制或乱序到达。
数据报套接字是在AF_INET域中通过UDP/IP连接实现的,它提供的是一种无序的不可靠服务(UDP代表的是用户数据报协议)。但从资源的角度来看,相对来说它们开销比较小,因为不需要维持网络连接。而且因为无需花费时间来建立连接,所以它们的速度也很快。
数据报适用于信息服务中的“单次”(single-shot)查询,它主要用来提供日常状态信息或执行低优先级的日志记录。它的优点是服务器的崩溃不会给客户造成不便,也不会要求客户重启,因为基于数据报的服务器通常不保留连接信息,所以它们可以在不打扰其客户的前提下停止并重启。
现在,我们暂时离开对数据报的讨论,关于数据报的更多信息请阅读本章最后一节。
3.套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。在本章中,我们将重点讨论UNIX网络套接字和文件系统套接字,它们不需要你选择一个特定的协议,只需要使用其默认值即可。
15.2.2 创建套接字
socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。
创建的套接字是一条通信线路的一个端点。domain参数指定协议族,type参数指定这个套接字的通信类型,protocol参数指定使用的协议。
domain参数可以指定的协议族如表15-1所示。
表 15-1
最常用的套接字域是AF_UNIX和AF_INET,前者用于通过UNIX和Linux文件系统实现的本地套接字,后者用于UNIX网络套接字。AF_INET套接字可以用于通过包括因特网在内的TCP/IP网络进行通信的程序。微软Windows系统的Winsock接口也提供了对这个套接字域的访问功能。
socket函数的参数type指定用于新套接字的通信特性。它的取值包括SOCK_STREAM和SOCK_DGRAM。
SOCK_STREAM是一个有序、可靠、面向连接的双向字节流。对AF_INET域套接字来说,它默认是通过一个TCP连接来提供这一特性的,TCP连接在两个流套接字端点之间建立。数据可以通过套接字连接进行双向传递。TCP协议所提供的机制可以用于分片和重组长消息,并且可以重传可能在网络中丢失的数据。
SOCK_DGRAM是数据报服务。我们可以用它来发送最大长度固定(通常比较小)的消息,但消息是否会被正确传递或消息是否不会乱序到达并没有保证。对于AF_INET域套接字来说,这种类型的通信是由UDP数据报来提供的。
通信所用的协议一般由套接字类型和套接字域来决定,通常不需要选择。只有当需要选择时,我们才会用到protocol参数。将该参数设置为0表示使用默认协议,我们将在本章的所有例子中都这样做。
socket系统调用返回一个描述符,它在许多方面都类似于底层的文件描述符。当这个套接字连接到另一端的套接字后,我们就可以用read和write系统调用,通过这个描述符来在套接字上发送和接收数据了。close系统调用用于结束套接字连接。
15.2.3 套接字地址
每个套接字域都有其自己的地址格式。对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件sys/un.h中。
因此,对套接字进行处理的系统调用可能需要接受不同类型的地址,每种地址格式都使用一种类似的结构来描述,它们都以一个指定地址类型(套接字域)的成员(在本例中是sun_family)开始。在AF_UNIX域中,套接字地址由结构中的sun_path成员中的文件名所指定。
在当前的Linux系统中,由X/Open规范定义的类型sa_family_t在头文件sys/un.h中声明,它是短整数类型。此外,sun_path指定的路径名长度也是有限制的(Linux规定的是108个字符,其他系统可能使用的是更清楚的常量,如UNIX_MAX_PATH)。因为地址结构的长度不一致,所以许多套接字调用需要用到一个用来复制特定地址结构的长度变量或将它作为一个输出。
在AF_INET域中,套接字地址由结构sockaddr_in来指定,该结构定义在头文件netinet/in.h中,它至少包含以下几个成员:
IP地址结构in_addr被定义为:
IP地址中的4个字节组成一个32位的值。一个AF_INET套接字由它的域、IP地址和端口号来完全确定。从应用程序的角度来看,所有套接字的行为就像文件描述符一样,并且通过一个唯一的整数值来区分。
15.2.4 命名套接字
要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名,正如你在server1例子中所看到的。AF_INET套接字就会关联到一个IP端口号。
bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字。地址结构的长度由参数address_len传递。
地址的长度和格式取决于地址族。bind调用需要将一个特定的地址结构指针转换为指向通用地址类型(struct sockaddr *)。
bind调用在成功时返回0,失败时返回-1并设置errno为表15-2中的一个值。
表 15-2
AF_UNIX域套接字还有其他一些错误代码,如表15-3所示。
表 15-3
15.2.5 创建套接字队列
为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成这一工作。
Linux系统可能会对队列中可以容纳的未处理连接的最大数目做出限制。为了遵守这个最大值限制,listen函数将队列长度设置为backlog参数的值。在套接字队列中,等待处理的进入连接的个数最多不能超过这个数字。再往后的连接将被拒绝,导致客户的连接请求失败。listen函数提供的这种机制允许当服务器程序正忙于处理前一个客户请求的时候,将后续的客户连接放入队列等待处理。backlog参数常用的值是5。
listen函数在成功时返回0,失败时返回-1。错误代码包括EBADF、EINVAL和ENOTSOCK,其含义与上面bind系统调用中说明的一样。
15.2.6 接受连接
一旦服务器程序创建并命名了套接字之后,它就可以通过accept系统调用来等待客户建立对该套接字的连接。
accept系统调用只有当有客户程序试图连接到由socket参数指定的套接字上时才返回。这里的客户是指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
套接字必须事先由bind调用命名,并且由listen调用给它分配一个连接队列。连接客户的地址将被放入address参数指向的sockaddr结构中。如果我们不关心客户的地址,也可以将address参数指定为空指针。
参数address_len指定客户结构的长度。如果客户地址的长度超过这个值,它将被截断。所以在调用accept之前,address_len必须被设置为预期的地址长度。当这个调用返回时,address_len将被设置为连接客户地址结构的实际长度。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户建立连接为止。我们可以通过对套接字文件描述符设置O_NONBLOCK标志来改变这一行为,使用的函数是fcnt1,如下所示:
当有未处理的客户连接时,accept函数将返回一个新的套接字文件描述符。发生错误时,accept函数将返回-1。可能的错误情况大部分与bind、listen调用类似,其他的错误有EWOULDBLOCK和EINTR。前者是当指定了O_NONBLOCK标志,但队列中没有未处理连接时产生的错误。后者是当进程阻塞在accept调用时,执行被中断而产生的错误。
15.2.7 请求连接
客户程序通过在一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。它们通过connect调用来完成这一工作。
参数socket指定的套接字将连接到参数address指定的服务器套接字,address指向的结构的长度由参数address_len指定。参数socket指定的套接字必须是通过socket调用获得的一个有效的文件描述符。
成功时,connect调用返回0,失败时返回-1。可能的错误代码见表15-4。
表 15-4
如果连接不能立刻建立,connect调用将阻塞一段不确定的超时时间。一旦这个超时时间到达,连接将被放弃,connect调用失败。但如果connect调用被一个信号中断,而该信号又得到了处理,connect调用还是会失败(errno被设置为EINTR),但连接尝试并不会被放弃,而是以异步方式继续建立,程序必须在此后进行检查以查看连接是否成功建立。
与accept调用一样,connect调用的阻塞特性可以通过设置该文件描述符的O_NONBLOCK标志来改变。此时,如果连接不能立刻建立,connect将失败并把errno设置为EINPROGRESS,而连接将以异步方式继续进行。
虽然异步连接难于处理,但我们可以在套接字文件描述符上,用select调用来检查套接字是否已处于写就绪状态。我们将在本章的后面介绍select调用。
15.2.8 关闭套接字
你可以通过调用close函数来终止服务器和客户上的套接字连接,就如同对底层文件描述符进行关闭一样。你应该总是在连接的两端都关闭套接字。对于服务器来说,应该在read调用返回0时关闭套接字,但如果套接字是一个面向连接类型的,并且设置了SOCK_LINGER选项,close调用会在该套接字还有未传输数据时阻塞。你将在本章后面的内容中学习到如何设置套接字选项。
15.2.9 套接字通信
在介绍完与套接字相关的基本系统调用后,我们来看几个示例程序。我们将尽量使用网络套接字而不是文件系统套接字。文件系统套接字的缺点是,除非程序员使用一个绝对路径名,否则套接字将创建在服务器程序的当前目录下。为了让它更具通用型,你需要将它创建在一个服务器及其客户都认可的可全局访问的目录(如/tmp目录)中。而对网络套接字来说,你只需要选择一个未被使用的端口号即可。
我们的例子将选择端口号9734,这个端口号是在避开标准服务的前提下随意选择的(我们不能使用小于1024的端口号,因为它们都是为系统使用保留的)。其他端口号及通过它们提供的服务通常都列在系统文件/etc/services中。编写基于套接字的应用程序时,请注意总要选择没有列在该配置文件中的端口号。
请注意在程序client2.c和server2.c中有个我们故意设置的错误,我们将在client3.c和server3.c中修复这个错误。所以请不要将client2.c和server2.c中的代码用到你自己的程序中。
我们将在局域网中运行我们的客户和服务器,但网络套接字不仅可用于局域网,任何带有因特网连接(即使是一个调制解调器拨号连接)的机器都可以使用网络套接字来彼此通信。甚至可以在一台UNIX单机上运行基于网络的程序,因为UNIX计算机通常会配置了一个只包含它自身的回路(loopback)网络。出于演示的目的,我们将使用这个回路网络。回路网络对调试网络应用程序也很有用,因为它排除了任何外部网络问题。
回路网络中只包含一台计算机,传统上它被称为localhost,它有一个标准的IP地址127.0.0.1。这就是本地主机。你可以在网络主机文件/etc/hosts中找到它的地址,在该文件中还列出了在共享网络中的其他主机的名字和对应的地址。
每个与计算机进行通信的网络都有一个与之关联的硬件接口。一台计算机可能在每个网络中都有一个不同的网络名,当然也就会有几个不同的IP地址。例如,Neil的机器tilde就有3个网络接口,因此也就有3个IP地址。它们被记录在文件/etc/hosts中,如下所示:
第一个就是简单的回路网络,第二个是通过一块以太网卡来访问的局域网,第三个是到一个因特网接入服务提供商的调制解调器连接。你编写的基于套接字的网络程序,可以不做任何修改就能通过任何一个网络接口与服务器进行通信。
实 验 网络客户
下面是一个修改过的客户程序client2.c,它通过回路网络连接到一个网络套接字。这个程序有一个与硬件相关的细微错误,我们将在本章的后面再讨论它。
(1)包含必要的头文件并设置变量:
(2)为客户创建一个套接字:
(3)命名套接字,与服务器保持一致:
这个程序的剩余部分与本章前面的client1.c完全一样。运行这个版本的客户程序时,它将连接失败,因为还没有服务器运行在这台计算机的9734端口上。
实验解析
客户程序用在头文件netinet/in.h中定义的sockaddr_in结构指定了一个AF_INET地址。它试图连接到IP地址为127.0.0.1的主机上的服务器。它用inet_addr函数将IP地址的文本表示方式转换为符合套接字地址要求的格式。inet的手册页中有对其他地址转换函数的详细说明。
实 验 网络服务器
你还需要修改服务器程序,让它在选好的端口号上等待客户的连接。下面是修改过的服务器程序server2.c。
(1)包含必要的头文件并设置变量:
(2)为服务器创建一个未命名套接字:
(3)命名套接字:
从这以后的代码与server1.c完全一样。运行client2和server2将显示与你在前面运行client1和server1一样的结果。
实验解析
服务器程序创建一个AF_INET域的套接字,并安排在它之上接受连接。这个套接字被绑定到你选择的端口。指定的地址决定了允许建立连接的计算机。通过指定像客户程序中一样的回路地址,你就把通信限制在本地主机上。
如果想允许服务器和远程客户进行通信,就必须指定一组你允许连接的IP地址。你可以用特殊值INADDR_ANY来表示,你将接受来自计算机任何网络接口的连接。如果你愿意,还可以通过分离如内部局域网和外部广域网连接的方式来区分不同的网络接口。INADDR_ANY是一个32位的整数值,它可以用在地址结构的sin_addr.s_addr域中。但首先你需要解决一个问题,如下节所示。
15.2.10 主机字节序和网络字节序
当在基于Intel处理器的Linux机器上运行新版本的服务器和客户程序时,我们可以用netstat命令来查看网络连接状况。这个命令在大多数配置了网络功能的UNIX系统上都能找到。它显示了客户/服务器连接正在等待关闭。连接将在一小段超时时间之后关闭(具体的输出内容将随Linux版本的不同而不同)。
在尝试运行本书中其他示例程序之前,请确保已终止正在运行的示例服务器程序,因为它们会争夺来自客户的连接,会导致运行结果混乱。你可以用下面的命令来将它们(包括本章后面将介绍的示例程序)一起杀掉:
kilall server1 server2 server3 server4 server5
你可以看到这条连接对应的服务器和客户的端口号。local address一栏显示的是服务器,而foreign address一栏显示的是远程客户(即使是在同一台机器上,它仍然是通过网络连接的)。为了确保所有套接字都是不同的,这些客户端口一般都与服务器监听套接字不同,并且在这台计算机上是唯一的。
可是,显示的本地地址(服务器套接字)端口是1574(或者你可能会看到显示的是一个服务名mvel-lm),而我们选择的端口是9734。为什么会不一样呢?答案是,通过套接字接口传递的端口号和地址都是二进制数字。不同的计算机使用不同的字节序来表示整数。例如,Intel处理器将32位的整数分为4个连续的字节,并以字节序1-2-3-4存储到内存中,这里的1表示最高位的字节。而IBM PowerPC处理器是以字节序4-3-2-1的方式来存储整数。如果保存整数的内存只是以逐个字节的方式来复制,两个不同的计算机得到的整数值就会不一致。
为了使不同类型的计算机可以就通过网络传输的多字节整数的值达成一致,你需要定义一个网络字节序。客户和服务器程序必须在传输之前,将它们的内部整数表示方式转换为网络字节序。它们通过定义在头文件netinet/in.h中的函数来完成这一工作。这些函数如下所示:
这些函数将16位和32位整数在主机字节序和标准的网络字节序之间进行转换。函数名是与之对应的转换操作的简写形式。例如“host to network, long”(htonl,长整数从主机字节序到网络字节序的转换)和“host to network,short”(htons,短整数从主机字节序到网络字节序的转换)。如果计算机本身的主机字节序与网络字节序相同,这些函数的内容实际上就是空操作。
为了保证16位的端口号有正确的字节序,你的服务器和客户需要用这些函数来转换端口地址。新服务器程序server3.c中的改动是:
你不需要对函数调用inet_addr(“127.0.0.1”)进行转换,因为inet_addr已被定义为产生一个网络字节序的结果。新客户程序client3.c中的改动是:
服务器也做了改动,通过用INADDR_ANY来允许到达服务器任一网络接口的连接。
现在,运行server3和client3时,你将看到本地连接使用的是正确的端口。
请记住,如果你使用的计算机上的主机字节序和网络字节序相同,你将不会看到任何差异。但为了让不同体系结构的计算机上的客户和服务器可以正确地操作,总是在网络程序中使用这些转换函数仍然是非常重要的。