传统的网络应用设计模式,客户机(client)/服务器(server)模式。须要在通信两端各自部署客户机和服务器来完成数据通讯。linux
浏览器(Browser)/服务器(server)模式。只需在一端部署服务器,而另一端使用每台PC都默认配置的浏览器便可完成数据的传输。面试
所以在开发过程当中,模式的选择由上述各自的特色决定。根据实际需求选择应用程序设计模式。shell
在网络通信时,源主机的应用程序知道目的主机的IP地址和端口号,殊不知道目的主机的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,若是接收到的数据包的硬件地址与本机不符,则直接丢弃。所以在通信前必须得到目的主机的硬件地址。ARP协议就起到这个做用。源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF:FF:FF:FF:FF:FF表示广播),目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应答数据包给源主机,将本身的硬件地址填写在应答包中。数据库
每台主机都维护一个ARP缓存表,能够用arp -a命令查看。缓存表中的表项有过时时间(通常为20分钟),若是20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP请求来得到目的主机的硬件地址。想想,为何表项要有过时时间而不是一直有效?编程
ARP数据报的格式以下所示:设计模式
源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的状况是多余的,但若是链路层是其它类型的网络则有多是必要的。硬件类型指链路层网络类型,1为以太网,协议类型指要转换的地址类型,0x0800为IP地址,后面两个地址长度对于以太网地址和IP地址分别为6和4(字节),op字段为1表示ARP请求,op字段为2表示ARP应答。api
请求帧以下(为了清晰在每行的前面加了字节计数,每行16个字节):数组
以太网首部(14字节) 0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06 ARP帧(28字节) 0000: 00 01 0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37 0020: 00 00 00 00 00 00 c0 a8 00 02 填充位(18字节) 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太网首部:目的主机采用广播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。浏览器
ARP帧:硬件类型0x0001表示以太网,协议类型0x0800表示IP协议,硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4,op为0x0001表示请求目的主机的MAC地址,源主机MAC地址为00:05:5d:61:58:a8,源主机IP地址为c0 a8 00 37(192.168.0.55),目的主机MAC地址全0待填写,目的主机IP地址为c0 a8 00 02(192.168.0.2)。缓存
因为以太网规定最小数据长度为46字节,ARP帧长度只有28字节,所以有18字节填充位,填充位的内容没有定义,与具体实现相关。
应答帧以下:
以太网首部 0000: 00 05 5d 61 58 a8 00 05 5d a1 b8 40 08 06 ARP帧 0000: 00 01 0010: 08 00 06 04 00 02 00 05 5d a1 b8 40 c0 a8 00 02 0020: 00 05 5d 61 58 a8 c0 a8 00 37 填充位 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太网首部:目的主机的MAC地址是00:05:5d:61:58:a8,源主机的MAC地址是00:05:5d:a1:b8:40,上层协议类型0x0806表示ARP。
ARP帧:硬件类型0x0001表示以太网,协议类型0x0800表示IP协议,硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4,op为0x0002表示应答,源主机MAC地址为00:05:5d:a1:b8:40,源主机IP地址为c0 a8 00 02(192.168.0.2),目的主机MAC地址为00:05:5d:61:58:a8,目的主机IP地址为c0 a8 00 37(192.168.0.55)。
思考题:若是源主机和目的主机不在同一网段,ARP请求的广播帧没法穿过路由器,源主机如何与目的主机通讯?
IP数据报的首部长度和数据长度都是可变长的,但老是4字节的整数倍。对于IPv4,4位版本字段是4。4位首部长度的数值是以4字节为单位的,最小值为5,也就是说首部长度最小是4x5=20字节,也就是不带任何选项的IP首部,4位能表示的最大值是15,也就是说首部长度最大是60字节。8位TOS字段有3个位用来指定IP数据报的优先级(目前已经废弃不用),还有4个位表示可选的服务类型(最小延迟、最大?吐量、最大可靠性、最小成本),还有一个位老是0。总长度是整个数据报(包括IP首部和IP层payload)的字节数。每传一个IP数据报,16位的标识加1,可用于分片和从新组装数据报。3位标志和13位片偏移用于分片。TTL(Time to live)是这样用的:源主机为数据包设定一个生存时间,好比64,每过一个路由器就把该值减1,若是减到0就表示路由已经太长了仍然找不到目的主机的网络,就丢弃该包,所以这个生存时间的单位不是秒,而是跳(hop)。协议字段指示上层协议是TCP、UDP、ICMP仍是IGMP。而后是校验和,只校验IP首部,数据的校验由更高层协议负责。IPv4的IP地址长度为32位。
想想,前面讲了以太网帧中的最小数据长度为46字节,不足46字节的要用填充字节补上,那么如何界定这46字节里前多少个字节是IP、ARP或RARP数据报然后面是填充字节?
下面分析一帧基于UDP的TFTP协议帧。
以太网首部 0000: 00 05 5d 67 d0 b1 00 05 5d 61 58 a8 08 00 IP首部 0000: 45 00 0010: 00 53 93 25 00 00 80 11 25 ec c0 a8 00 37 c0 a8 0020: 00 01 UDP首部 0020: 05 d4 00 45 00 3f ac 40 TFTP协议 0020: 00 01 'c'':''\''q' 0030: 'w''e''r''q''.''q''w''e'00 'n''e''t''a''s''c''i' 0040: 'i'00 'b''l''k''s''i''z''e'00 '5''1''2'00 't''i' 0050: 'm''e''o''u''t'00 '1''0'00 't''s''i''z''e'00 '0' 0060: 00以太网首部:源MAC地址是00:05:5d:61:58:a8,目的MAC地址是00:05:5d:67:d0:b1,上层协议类型0x0800表示IP。
IP首部:每个字节0x45包含4位版本号和4位首部长度,版本号为4,即IPv4,首部长度为5,说明IP首部不带有选项字段。服务类型为0,没有使用服务。16位总长度字段(包括IP首部和IP层payload的长度)为0x0053,即83字节,加上以太网首部14字节可知整个帧长度是97字节。IP报标识是0x9325,标志字段和片偏移字段设置为0x0000,就是DF=0容许分片,MF=0此数据报没有更多分片,没有分片偏移。TTL是0x80,也就是128。上层协议0x11表示UDP协议。IP首部校验和为0x25ec,源主机IP是c0 a8 00 37(192.168.0.55),目的主机IP是c0 a8 00 01(192.168.0.1)。
UDP首部:源端口号0x05d4(1492)是客户端的端口号,目的端口号0x0045(69)是TFTP服务的well-known端口号。UDP报长度为0x003f,即63字节,包括UDP首部和UDP层pay-load的长度。UDP首部和UDP层payload的校验和为0xac40。
TFTP是基于文本的协议,各字段之间用字节0分隔,开头的00 01表示请求读取一个文件,接下来的各字段是:
c:\qwerq.qwe netascii blksize 512 timeout 10 tsize 0
通常的网络通讯都是像TFTP协议这样,通讯的双方分别是客户端和服务器,客户端主动发起请求(上面的例子就是客户端发起的请求帧),而服务器被动地等待、接收和应答请求。客户端的IP地址和端口号惟一标识了该主机上的TFTP客户端进程,服务器的IP地址和端口号惟一标识了该主机上的TFTP服务进程,因为客户端是主动发起请求的一方,它必须知道服务器的IP地址和TFTP服务进程的端口号,因此,一些常见的网络协议有默认的服务器端口,例如HTTP服务默认TCP协议的80端口,FTP服务默认TCP协议的21端口,TFTP服务默认UDP协议的69端口(如上例所示)。在使用客户端程序时,必须指定服务器的主机名或IP地址,若是不明确指定端口号则采用默认端口,请读者查阅ftp、tftp等程序的man page了解如何指定端口号。/etc/services中列出了全部well-known的服务端口和对应的传输层协议,这是由IANA(Internet Assigned Numbers Authority)规定的,其中有些服务既能够用TCP也能够用UDP,为了清晰,IANA规定这样的服务采用相同的TCP或UDP默认端口号,而另一些TCP和UDP的相同端口号却对应不一样的服务。
不少服务有well-known的端口号,然而客户端程序的端口号却没必要是well-known的,每每是每次运行客户端程序时由系统自动分配一个空闲的端口号,用完就释放掉,称为ephemeral的端口号,想一想这是为何?
前面提过,UDP协议不面向链接,也不保证传输的可靠性,例如:
发送端的UDP协议层只管把应用层传来的数据封装成段交给IP协议层就算完成任务了,若是由于网络故障该段没法发到对方,UDP协议层也不会给应用层返回任何错误信息。
接收端的UDP协议层只管把收到的数据根据端口号交给相应的应用程序就算完成任务了,若是发送端发来多个数据包而且在网络上通过不一样的路由,到达接收端时顺序已经错乱了,UDP协议层也不保证按发送时的顺序交给应用层。
一般接收端的UDP协议层将收到的数据放在一个固定大小的缓冲区中等待应用程序来提取和处理,若是应用程序提取和处理的速度很慢,而发送端发送的速度很快,就会丢失数据包,UDP协议层并不报告这种错误。
所以,使用UDP协议的应用程序必须考虑到这些可能的问题并实现适当的解决方案,例如等待应答、超时重发、为数据包编号、流量控制等。通常使用UDP协议的应用程序实现都比较简单,只是发送一些对可靠性要求不高的消息,而不发送大量的数据。例如,基于UDP的TFTP协议通常只用于传送小文件(因此才叫trivial的ftp),而基于TCP的FTP协议适用于 各类文件的传输。TCP协议又是如何用面向链接的服务来代替应用程序解决传输的可靠性问题呢。
在这个例子中,首先客户端主动发起链接、发送请求,而后服务器端响应请求,而后客户端主动关闭链接。两条竖线表示通信的两端,从上到下表示时间的前后顺序,注意,数据从一端传到网络的另外一端也须要时间,因此图中的箭头都是斜的。双方发送的段按时间顺序编号为1-10,各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK1001, ,表示该段中的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认序号是1001,带有一个mss(Maximum Segment Size,最大报文长度)选项值为1024。
创建链接(三次握手)的过程:
一、客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程当中的段1。
客户端发出段1,SYN位表示链接请求。序号是1000,这个序号在网络通信中用做临时的地址,每发一个数据字节,这个序号要加1,这样在接收端能够根据序号排出数据包的正确顺序,也能够发现丢包的状况,另外,规定SYN位和FIN位也要占一个序号,此次虽然没发数据,可是因为发了SYN位,所以下次再发送应该用序号1001。mss表示最大段尺寸,若是一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了不这种状况,客户端声明本身的最大段尺寸,建议服务器端发来的段不要超过这个长度。
二、服务器端回应客户端,是三次握手中的第2个报文段,同时带ACK标志和SYN标志。它表示对刚才客户端SYN的回应;同时又发送SYN给客户端,询问客户端是否准备好进行数据通信。
服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其之前全部的段,请你下次发送序号为1001的段”,也就是应答了客户端的链接请求,同时也给客户端发出一个链接请求,同时声明最大尺寸为1024。
三、客户必须再次回应服务器端一个ACK报文,这是报文段3。
客户端发出段3,对服务器的链接请求进行应答,确认序号是8001。在这个过程当中,客户端和服务器分别给对方发了链接请求,也应答了对方的链接请求,其中服务器的请求和应答在一个段中发出,所以一共有三个段用于创建链接,称为“三方握手(three-way-handshake)”。在创建链接的同时,双方协商了一些信息,例如双方发送序号的初始值、最大段尺寸等。
在TCP通信中,若是一方收到另外一方发来的段,读出其中的目的端口号,发现本机并无任何进程使用这个端口,就会应答一个包含RST位的段给另外一方。例如,服务器并无任何进程使用8080端口,咱们却用telnet客户端去链接它,服务器收到客户端发来的SYN段就会应答一个RST段,客户端的telnet程序收到RST段后报告错误Connection refused:
$ telnet 192.168.0.200 8080 Trying 192.168.0.200... telnet: Unable to connect to remote host: Connection refused
九、接收端的应用程序在提走所有数据后,决定关闭链接,发出段17包含FIN位,发送端应答,链接彻底关闭。
上图在接收端用小方块表示1K数据,实心的小方块表示已接收到的数据,虚线框表示接收缓冲区,所以套在虚线框中的空心小方块表示窗口大小,从图中能够看出,随着应用程序提走数据,虚线框是向右滑动的,所以称为滑动窗口。
从这个例子还能够看出,发送端是一K一K地发送数据,而接收端的应用程序能够两K两K地提走数据,固然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据。也就是说,应用程序所看到的数据是一个总体,或说是一个流(stream),在底层通信中这些数据可能被拆成不少数据包来发送,可是一个数据包有多少字节对应用程序是不可见的,所以TCP协议是面向流的协议。而UDP是面向消息的协议,每一个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不一样的。
CLOSED:表示初始状态。
LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,能够接受链接。
SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT链接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
SYN_RCVD: 该状态表示接收到SYN报文,在正常状况下,这个状态是服务器端的SOCKET在创建TCP链接时的三次握手会话过程当中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。
ESTABLISHED:表示链接已经创建。
FIN_WAIT_2:主动关闭连接的一方,发出FIN收到ACK之后进入该状态。称之为半链接或半关闭状态。该状态下的socket只能接收数据,不能发。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后便可回到CLOSED可用状态。若是FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,能够直接进入到TIME_WAIT状态,而无须通过FIN_WAIT_2状态。
CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常状况下,当你发送FIN报文后,按理来讲是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。可是CLOSING状态表示你发送FIN报文后,并无收到对方的ACK报文,反而却也收到了对方的FIN报文。什么状况下会出现此种状况呢?若是双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的状况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET链接。
CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给本身,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,若是没有能够 close这个SOCKET,发送FIN报文给对方,即关闭链接。因此在CLOSE_WAIT状态下,须要关闭链接。
LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,便可以进入到CLOSED可用状态。
当TCP连接中A发送FIN请求关闭,B端回应ACK后(A端进入FIN_WAIT_2状态),B没有当即发送FIN给A时,A方处在半连接状态,此时A能够接收B发送的数据,可是A已不能再向B发送数据。
从程序的角度,能够使用API来控制实现半链接状态。
#include <sys/socket.h> int shutdown(int sockfd, int how); sockfd: 须要关闭的socket的描述符 how: 容许为shutdown操做选择如下几种方式: SHUT_RD(0): 关闭sockfd上的读功能,此选项将不容许sockfd进行读操做。 该套接字再也不接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。 SHUT_WR(1): 关闭sockfd的写功能,此选项将不容许sockfd进行写操做。进程不能在对此套接字发出写操做。 SHUT_RDWR(2): 关闭sockfd的读写功能。至关于调用shutdown两次:首先是以SHUT_RD,而后以SHUT_WR。
使用close停止一个链接,但它只是减小描述符的引用计数,并不直接关闭链接,只有当描述符的引用计数为0时才关闭链接。
shutdown不考虑描述符的引用计数,直接关闭描述符。也可选择停止一个方向的链接,只停止读或只停止写。
另一个概念叫作incarnation connection,指跟上次的socket pair一摸同样的新链接,叫作incarnation of previous connection。lost uplicate加上incarnation connection,则会对咱们的传输形成致命的错误。
TCP是流式的,全部包到达的顺序是不一致的,依靠序列号由TCP协议栈作顺序的拼接;假设一个incarnation connection这时收到的seq=1000, 来了一个lost duplicate为seq=1000,len=1000, 则TCP认为这个lost duplicate合法,并存放入了receive buffer,致使传输出现错误。经过一个2MSL TIME_WAIT状态,确保全部的lost duplicate都会消失掉,避免对新链接形成错误。
如何正确对待2MSL TIME_WAIT?
RFC要求socket pair在处于TIME_WAIT时,不能再起一个incarnation connection。但绝大部分TCP实现,强加了更为严格的限制。在2MSL等待期间,socket中使用的本地端口在默认状况下不能再被使用。
若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666创建了链接,A主动关闭,那么在A端只要port为1234,不管对方的port和ip是什么,都不容许再起服务。这甚至比RFC限制更为严格,RFC仅仅是要求socket pair不一致,而实现当中只要这个port处于TIME_WAIT,就不容许起链接。这个限制对主动打开方来讲是无所谓的,由于通常用的是临时端口;但对于被动打开方,通常是server,就悲剧了,由于server通常是熟知端口。好比http,通常端口是80,不可能容许这个服务在2MSL内不能起来。
解决方案是给服务器的socket设置SO_REUSEADDR选项,这样的话就算熟知端口处于TIME_WAIT状态,在这个端口上依旧能够将服务启动。固然,虽然有了SO_REUSEADDR选项,但sockt pair这个限制依旧存在。好比上面的例子,A经过SO_REUSEADDR选项依旧在1234端口上起了监听,但这时咱们如果从B经过6666端口去连它,TCP协议会告诉咱们链接失败,缘由为Address already in use.
RFC 793中规定MSL为2分钟,实际应用中经常使用的是30秒,1分钟和2分钟等。
RFC (Request For Comments),是一系列以编号排定的文件。收集了有关因特网相关资讯,以及UNIX和因特网社群的软件文件。
作一个测试,首先启动server,而后启动client,用Ctrl-C终止server,立刻再运行server,运行结果:
itcast$ ./server bind error: Address already in use
这是由于,虽然server的应用程序终止了,但TCP协议层的链接并无彻底断开,所以不能再次监听一样的server端口。咱们用netstat命令查看一下:
itcast$ netstat -apn |grep 6666
tcp 1 0 192.168.1.11:38103 192.168.1.11:6666 CLOSE_WAIT 3525/client
tcp 0 0 192.168.1.11:6666 192.168.1.11:38103 FIN_WAIT2 -
server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,可是client并无终止,也没有关闭socket描述符,所以不会发FIN给server,所以server的TCP链接处于FIN_WAIT2状态。
如今用Ctrl-C把client也终止掉,再观察现象:
itcast$ netstat -apn |grep 6666
tcp 0 0 192.168.1.11:6666 192.168.1.11:38104 TIME_WAIT -
itcast$ ./server
bind error: Address already in use
client终止时自动关闭socket描述符,server的TCP链接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭链接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,由于咱们先Ctrl-C终止了server,因此server是主动关闭链接的一方,在TIME_WAIT期间仍然不能再次监听一样的server端口。
MSL在RFC 1122中规定为两分钟,可是各操做系统的实现不一样,在Linux上通常通过半分钟后就能够再次启动server了。至于为何要规定TIME_WAIT的时间,可参考UNP 2.7节。
在server的TCP链接没有彻底断开以前不容许从新监听是不合理的。由于,TCP链接没有彻底断开指的是connfd(127.0.0.1:6666)没有彻底断开,而咱们从新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不一样,connfd对应的是与某个客户端通信的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示容许建立端口号相同但IP地址不一样的多个socket描述符。
有关setsockopt能够设置的其它选项请参考UNP第7章。
在TCP网络通讯中,常常会出现客户端和服务器之间的非正常断开,须要实时检测查询连接状态。经常使用的解决方法就是在程序中加入心跳机制。
Heart-Beat线程
这个是最经常使用的简单方法。在接收和发送数据时我的设计一个守护进程(线程),定时发送Heart-Beat包,客户端/服务器收到该小包后,马上返回相应的包便可检测对方是否实时在线。
该方法的好处是通用,但缺点就是会改变现有的通信协议!你们通常都是使用业务层心跳来处理,主要是灵活可控。
UNIX网络编程不推荐使用SO_KEEPALIVE来作心跳检测,仍是在业务层以心跳包作检测比较好,也方便控制。
SO_KEEPALIVE 保持链接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP链接的输入。设置该选项后,若是2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会致使如下三种状况:对方接收一切正常:以指望的ACK响应。2小时后,TCP将发出另外一个探测分节。对方已崩溃且已从新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口自己则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图获得一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口自己则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并无崩溃,可是不可达,这种状况下待处理错误被置为EHOSTUNREACH。
根据上面的介绍咱们能够知道对端以一种非优雅的方式断开链接的时候,咱们能够设置SO_KEEPALIVE属性使得咱们在2小时之后发现对方的TCP链接是否依然存在。
keepAlive = 1;
setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
1.The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours).
/*开始首次KeepAlive探测前的TCP空闭时间 */
2.The tcp_keepintvl parameter specifies the interval between the nine retriesthat are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keep ntvldefaults to 150 (75 seconds).
/* 两次KeepAlive探测间的时间间隔 */
3.The tcp_keepcnt option specifies the maximum number of keepalive probes tobe sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n s the value of the systemwide tcp_keepcnt parameter.
/* 断定断开前的KeepAlive探测次数*/ int keepIdle = 1000; int keepInterval = 10; int keepCount = 10; Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)); Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval)); Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
SO_KEEPALIVE设置空闲2小时才发送一个“保持存活探测分节”,不能保证明时检测。对于判断网络断开时间太长,对于须要及时响应的程序不太适应。
固然也能够修改时间间隔参数,可是会影响到全部打开此选项的套接口!关联了完成端口的socket可能会忽略掉该套接字选项。
路由器(Router)是链接因特网中各局域网、广域网的设备,它会根据信道的状况自动选择和设定路由,以最佳路径,按先后顺序发送信号的设备。
传统地,路由器工做于OSI七层协议中的第三层,其主要任务是接收来自一个网络接口的数据包,根据其中所含的目的地址,决定转发到下一个目的地址。所以,路由器首先得在转发路由表中查找它的目的地址,若找到了目的地址,就在数据包的帧格前添加下一个MAC地址,同时IP数据包头的TTL(Time To Live)域也开始减数, 并从新计算校验和。当数据包被送到输出端口时,它须要按顺序等待,以便被传送到输出链路上。
路由器在工做时可以按照某种路由通讯协议查找设备中的路由表。若是到某一特定节点有一条以上的路径,则基本预先肯定的路由准则是选择最优(或最经济)的传输路径。因为各类网络段和其相互链接状况可能会因环境变化而变化,所以路由状况的信息通常也按所使用的路由信息协议的规定而定时更新。
网络中,每一个路由器的基本功能都是按照必定的规则来动态地更新它所保持的路由表,以便保持路由信息的有效性。为了便于在网络间传送报文,路由器老是先按照预约的规则把较大的数据分解成适当大小的数据包,再将这些数据包分别经过相同或不一样路径发送出去。当这些数据包按前后秩序到达目的地后,再把分解的数据包按照必定顺序包装成原有的报文形式。路由器的分层寻址功能是路由器的重要功能之一,该功能能够帮助具备不少节点站的网络来存储寻址信息,同时还能在网络间截获发送到远地网段的报文,起转发做用;选择最合理的路由,引导通讯也是路由器基本功能;多协议路由器还能够链接使用不一样通讯协议的网络段,成为不一样通讯协议网络段之间的通讯平台。
路由和交换之间的主要区别就是交换发生在OSI参考模型第二层(数据链路层),而路由发生在第三层,即网络层。这一区别决定了路由和交换在移动信息的过程 中需使用不一样的控制信息,因此二者实现各自功能的方式是不一样的。
以太网交换机是基于以太网传输数据的交换机,以太网采用共享总线型传输媒体方式的局域网。以太网交换机的结构是每一个端口都直接与主机相连,而且通常都工做在全双工方式。交换机能同时连通许多对端口,使每一对相互通讯的主机都能像独占通讯媒体那样,进行无冲突地传输数据。
以太网交换机工做于OSI网络参考模型的第二层(即数据链路层),是一种基于MAC(Media Access Control,介质访问控制)地址识别、完成以太网数据帧转发的网络设备。
集线器实际上就是中继器的一种,其区别仅在于集线器可以提供更多的端口服务,因此集线器又叫多口中继器。
集线器功能是随机选出某一端口的设备,并让它独占所有带宽,与集线器的上联设备(交换机、路由器或服务器等)进行通讯。从Hub的工做方式能够看出,它在网络中只起到信号放大和重发做用,其目的是扩大网络的传输范围,而不具有信号的定向传送能力,是—个标准的共享式设备。其次是Hub只与它的上联设备(如上层Hub、交换机或服务器)进行通讯,同层的各端口之间不会直接进行通讯,而是经过上联设备再将信息广播到全部端口上。 因而可知,即便是在同一Hub的不一样两个端口之间进行通讯,都必需要通过两步操做:
第一步是将信息上传到上联设备;
第二步是上联设备再将该信息广播到全部端口上。
DNS 是域名系统 (Domain Name System) 的缩写,是因特网的一项核心服务,它做为能够将域名和IP地址相互映射的一个分布式数据库,可以令人更方便的访问互联网,而不用去记住可以被机器直接读取的IP地址串。
它是由解析器以及域名服务器组成的。域名服务器是指保存有该网络中全部主机的域名和对应IP地址,并具备将域名转换为IP地址功能的服务器。
wide area network,一种用来实现不一样地区的局域网或城域网的互连,可提供不一样地区、城市和国家之间的计算机通讯的远程计算机网。
覆盖的范围比局域网(LAN)和城域网(MAN)都广。广域网的通讯子网主要使用分组交换技术。
四、完善的通讯服务与网络管理。
如下是一些协议的MTU:
FDDI协议:4352字节 以太网(Ethernet)协议:1500字节 PPPoE(ADSL)协议:1492字节 X.25协议(Dial Up/Modem):576字节 Point-to-Point:4470字节
Socket自己有“插座”的意思,在Linux环境下,用于表示进程间网络通讯的特殊文件类型。本质为内核借助缓冲区造成的伪文件。
既然是文件,那么理所固然的,咱们能够使用文件描述符引用套接字。与管道相似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操做一致。区别是管道主要应用于本地进程间通讯,而套接字多应用于网络进程间数据的传递。
套接字的内核实现较为复杂,不宜在学习初期深刻学习。
在TCP/IP协议中,“IP地址+TCP或UDP端口号”惟一标识网络通信中的一个进程。“IP地址+端口号”就对应一个socket。欲创建链接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就惟一标识一个链接。所以能够用Socket来描述网络链接的一对一关系。
套接字通讯原理以下图所示:
在网络通讯中,套接字必定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。咱们使用同一个文件描述符索发送缓冲区和接收缓冲区。
TCP/IP协议最先在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。本章的主要内容是socket API,主要介绍TCP协议的函数接口,最后介绍UDP协议和UNIX Domain Socket的函数接口。
咱们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流一样有大端小端之分,那么如何定义网络数据流的地址呢?发送主机一般将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,所以,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。例如上一节的UDP段格式,地址0-1是16位的源端口号,若是这个端口号是1000(0x3e8),则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8,这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。可是,若是发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。所以,发送主机把1000填到发送缓冲区以前须要作字节序的转换。一样地,接收主机若是是小端字节序的,接到16位的源端口号也要作字节序的转换。若是主机是大端字节序的,发送和接收都不须要作转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。
为使网络程序具备可移植性,使一样的C代码在大端和小端计算机上编译后都能正常运行,能够调用如下库函数作网络字节序和主机字节序的转换。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
若是主机是小端字节序,这些函数将参数作相应的大小端转换而后返回,若是主机是大端字节序,这些函数不作转换,将参数原封不动地返回。
早期:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in); 只能处理IPv4的ip地址 不可重入函数 注意参数是struct in_addr
如今:
#include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 支持IPv4和IPv6 可重入函数 其中inet_pton和inet_ntop不只能够转换IPv4的in_addr,还能够转换IPv6的in6_addr。 所以函数接口是void *addrptr。
sockaddr数据结构
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
使用 sudo grep -r "struct sockaddr_in {" /usr 命令可查看到struct sockaddr_in结构体的定义。通常其默认的存储位置:/usr/include/linux/in.h 文件中。
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址结构类型 __be16 sin_port; /* Port number */ 端口号 struct in_addr sin_addr; /* Internet address */ IP地址 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { /* Internet address. */ __be32 s_addr; }; struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ }; struct in6_addr { union { __u8 u6_addr8[16]; __be16 u6_addr16[8]; __be32 u6_addr32[4]; } in6_u; #define s6_addr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; #define UNIX_PATH_MAX 108 struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
Pv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sock-addr_un结构体表示。各类socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并非全部UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。IPv四、IPv6和Unix Domain Socket的地址类型分别定义为常数AF_INET、AF_INET六、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不须要知道具体是哪一种类型的sockaddr结构体,就能够根据地址类型字段肯定结构体中的内容。所以,socket API能够接受各类类型的sockaddr结构体指针作参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void 类型以便接受各类类型的指针,可是sock API的实现早于ANSI C标准化,那时尚未void 类型,所以这些函数的参数都用struct sockaddr *类型表示,在传递参数以前要强制类型转换一下,例如:
struct sockaddr_in servaddr; bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* initialize servaddr */
socket函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址 AF_INET6 与上面相似,不过是来用IPv6的地址 AF_UNIX 本地协议,使用在Unix和Linux系统上,通常都是当客户端和服务器在同一台及其上的时候使用 type: SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的链接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。 SOCK_DGRAM 这个协议是无链接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的链接。 SOCK_SEQPACKET该协议是双线路的、可靠的链接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。 SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议) SOCK_RDM 这个类型是不多使用的,在大部分的操做系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序 protocol: 传0 表示使用默认协议。 返回值: 成功:返回指向新建立的socket的文件描述符,失败:返回-1,设置errno
bind函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: socket文件描述符 addr: 构造出IP地址加端口号 addrlen: sizeof(addr)长度 返回值: 成功返回0,失败返回-1, 设置errno
服务器程序所监听的网络地址和端口号一般是固定不变的,客户端程序得知服务器程序的地址和端口号后就能够向服务器发起链接,所以服务器须要调用bind绑定一个固定的网络地址和端口号。
bind()的做用是将参数sockfd和addr绑定在一块儿,使sockfd这个用于网络通信的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上能够接受多种协议的sockaddr结构体,而它们的长度各不相同,因此须要第三个参数addrlen指定结构体的长度。如:
struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666);
首先将整个结构体清零,而后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,由于服务器可能有多个网卡,每一个网卡也可能绑定多个IP地址,这样设置能够在全部的IP地址上监听,直到与某个客户端创建了链接时才肯定下来到底用哪一个IP地址,端口号为6666。
listen函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog); sockfd: socket文件描述符 backlog: 排队创建3次握手队列和刚刚创建3次握手队列的连接数和
查看系统默认backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
典型的服务器程序能够同时服务于多个客户端,当有客户端发起链接时,服务器调用的accept()返回并接受这个链接,若是有大量的客户端发起链接而服务器来不及处理,还没有accept的客户端就处于链接等待状态,listen()声明sockfd处于监听状态,而且最多容许有backlog个客户端处于链接待状态,若是接收到更多的链接请求就忽略。listen()成功返回0,失败返回-1。
accept函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockdf: socket文件描述符 addr: 传出参数,返回连接客户端地址信息,含IP地址和端口号 addrlen: 传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小 返回值: 成功返回一个新的socket文件描述符,用于和客户端通讯,失败返回-1,设置errno
三方握手完成后,服务器调用accept()接受链接,若是服务器调用accept()时尚未客户端的链接请求,就阻塞等待直到有客户端链接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。若是给addr参数传NULL,表示不关心客户端的地址。
咱们的服务器程序结构是这样的:
while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ...... close(connfd); }
整个是一个while死循环,每次循环处理一个客户端链接。因为cliaddr_len是传入传出参数,每次调用accept()以前应该从新赋初值。accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另一个文件描述符connfd,以后与客户端之间就经过这个connfd通信,最后关闭connfd断开链接,而不关闭listenfd,再次回到循环开头listenfd仍然用做accept的参数。accept()成功返回一个文件描述符,出错返回-1。
connect函数
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockdf: socket文件描述符 addr: 传入参数,指定服务器端地址信息,含IP地址和端口号 addrlen: 传入参数,传入sizeof(addr)大小 返回值: 成功返回0,失败返回-1,设置errno
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动做,好比调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,好比从某个阻塞的socket函数返回就代表TCP协议收到了某些段,再好比read()返回0就代表收到了FIN段
下面经过最简单的客户端/服务器程序的实例来学习socket API。
server.c的做用是从客户端读字符,而后将每一个字符转换为大写并回送给客户端。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); write(connfd, buf, n); close(connfd); } return 0; }
client.c的做用是从命令行参数中得到一个字符串发给服务器,而后接收服务器返回的字符串并打印。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; char *str; if (argc != 2) { fputs("usage: ./client message\n", stderr); exit(1); } str = argv[1]; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); write(sockfd, str, strlen(str)); n = read(sockfd, buf, MAXLINE); printf("Response from server:\n"); write(STDOUT_FILENO, buf, n); close(sockfd); return 0; }
因为客户端不须要固定的端口号,所以没必要调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不容许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但若是服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不同,客户端要链接服务器就会遇到麻烦。
客户端和服务器启动后能够使用netstat命令查看连接状况:
netstat -apn|grep 6666
上面的例子不只功能简单,并且简单到几乎没有什么错误处理,咱们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面能够保证程序逻辑正常,另外一方面能够迅速获得故障信息。
为使错误处理的代码不影响主程序的可读性,咱们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,作成一个模块wrap.c:
wrap.c
#include <stdlib.h> #include <errno.h> #include <sys/socket.h> void perr_exit(const char *s) { perror(s); exit(1); } int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ( (n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit("connect error"); return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ( (n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; ptr += nread; } return n - nleft; } ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } ssize_t Readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
wrap.h
#ifndef __WRAP_H_ #define __WRAP_H_ void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); int Bind(int fd, const struct sockaddr *sa, socklen_t salen); int Connect(int fd, const struct sockaddr *sa, socklen_t salen); int Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); int Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 800 void do_sigchild(int num) { while (waitpid(0, NULL, WNOHANG) > 0) ; } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; pid_t pid; struct sigaction newact; newact.sa_handler = do_sigchild; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGCHLD, &newact, NULL); listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); pid = fork(); if (pid == 0) { Close(listenfd); while (1) { n = Read(connfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); Write(connfd, buf, n); } Close(connfd); return 0; } else if (pid > 0) { Close(connfd); } else perr_exit("fork"); } Close(listenfd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 struct s_info { struct sockaddr_in cliaddr; int connfd; }; void *do_work(void *arg) { int n,i; struct s_info *ts = (struct s_info*)arg; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; /* 能够在建立线程前设置线程建立属性,设为分离态,哪一种效率高内? */ pthread_detach(pthread_self()); while (1) { n = Read(ts->connfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); Write(ts->connfd, buf, n); } Close(ts->connfd); } int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; int i = 0; pthread_t tid; struct s_info ts[256]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); ts[i].cliaddr = cliaddr; ts[i].connfd = connfd; /* 达到线程最大数时,pthread_create出错处理, 增长服务器稳定性 */ pthread_create(&tid, NULL, do_work, (void*)&ts[i]); i++; } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
主要使用的方法有三种
二、解决1024如下客户端时使用select是很合适的,但若是连接客户端过多,select采用的是轮询模型,会大大下降服务器响应效率,不该在select上投入更多精力
#include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds: 监控的文件描述符集里最大文件描述符加1,由于此参数会告诉内核检测前多少个文件描述符的状态 readfds: 监控有读数据到达文件描述符集合,传入传出参数 writefds: 监控写数据到达文件描述符集合,传入传出参数 exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数 timeout: 定时阻塞监控时间,3种状况 1.NULL,永远等下去 2.设置timeval,等待固定时间 3.设置timeval里时间均为0,检查描述字后当即返回,轮询 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0 int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1 void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1 void FD_ZERO(fd_set *set); //把文件描述符集合里全部位清0
server
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; /* FD_SETSIZE 默认为 1024 */ ssize_t n; fd_set rset, allset; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; /* #define INET_ADDRSTRLEN 16 */ socklen_t cliaddr_len; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); /* 默认最大128 */ maxfd = listenfd; /* 初始化 */ maxi = -1; /* client[]的下标 */ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; /* 用-1初始化client[] */ FD_ZERO(&allset); FD_SET(listenfd, &allset); /* 构造select监控文件描述符集 */ for ( ; ; ) { rset = allset; /* 每次循环时都重新设置select监控信号集 */ nready = select(maxfd+1, &rset, NULL, NULL, NULL); if (nready < 0) perr_exit("select error"); if (FD_ISSET(listenfd, &rset)) { /* new client connection */ cliaddr_len = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < FD_SETSIZE; i++) { if (client[i] < 0) { client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */ break; } } /* 达到select能监控的文件个数上限 1024 */ if (i == FD_SETSIZE) { fputs("too many clients\n", stderr); exit(1); } FD_SET(connfd, &allset); /* 添加一个新的文件描述符到监控信号集里 */ if (connfd > maxfd) maxfd = connfd; /* select第一个参数须要 */ if (i > maxi) maxi = i; /* 更新client[]最大下标值 */ if (--nready == 0) continue; /* 若是没有更多的就绪文件描述符继续回到上面select阻塞监听, 负责处理未处理完的就绪文件描述符 */ } for (i = 0; i <= maxi; i++) { /* 检测哪一个clients 有数据就绪 */ if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { Close(sockfd); /* 当client关闭连接时,服务器端也关闭对应连接 */ FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */ client[i] = -1; } else { int j; for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Write(sockfd, buf, n); } if (--nready == 0) break; } } } close(listenfd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
pselect原型以下。此模型应用较少,有须要的同窗可参考select模型自行编写C/S
#include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 用sigmask替代当前进程的阻塞信号集,调用返回后还原原有阻塞信号集
poll
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; /* 文件描述符 */ short events; /* 监控的事件 */ short revents; /* 监控事件中知足条件返回的事件 */ }; POLLIN 普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND POLLRDNORM 数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级可读数据 POLLOUT 普通或带外数据可写 POLLWRNORM 数据可写 POLLWRBAND 优先级带数据可写 POLLERR 发生错误 POLLHUP 发生挂起 POLLNVAL 描述字不是一个打开的文件 nfds 监控数组中有多少文件描述符须要被监控 timeout 毫秒级等待 -1:阻塞等,#define INFTIM -1 Linux中没有定义此宏 0:当即返回,不阻塞进程 >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
server
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); client[0].fd = listenfd; client[0].events = POLLRDNORM; /* listenfd监听普通读事件 */ for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */ maxi = 0; /* client[]数组有效元素中最大元素下标 */ for ( ; ; ) { nready = poll(client, maxi+1, -1); /* 阻塞 */ if (client[0].revents & POLLRDNORM) { /* 有客户端连接请求 */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = connfd; /* 找到client[]中空闲的位置,存放accept返回的connfd */ break; } } if (i == OPEN_MAX) perr_exit("too many clients"); client[i].events = POLLRDNORM; /* 设置刚刚返回的connfd,监控读事件 */ if (i > maxi) maxi = i; /* 更新client[]中最大元素下标 */ if (--nready <= 0) continue; /* 没有更多就绪事件时,继续回到poll阻塞 */ } for (i = 1; i <= maxi; i++) { /* 检测client[] */ if ((sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { if ((n = Read(sockfd, buf, MAXLINE)) < 0) { if (errno == ECONNRESET) { /* 当收到 RST标志时 */ /* connection reset by client */ printf("client[%d] aborted connection\n", i); Close(sockfd); client[i].fd = -1; } else { perr_exit("read error"); } } else if (n == 0) { /* connection closed by client */ printf("client[%d] closed connection\n", i); Close(sockfd); client[i].fd = -1; } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } if (--nready <= 0) break; /* no more readable descriptors */ } } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
GNU定义了ppoll(非POSIX标准),能够支持设置信号屏蔽字,你们可参考poll模型自行实现C/S。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <poll.h> int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);
能够使用cat命令查看一个进程能够打开的socket描述符上限。
cat /proc/sys/fs/file-max
若有须要,能够经过修改配置文件的方式修改该上限值。
sudo vi /etc/security/limits.conf 在文件尾部写入如下配置,soft软限制,hard硬限制。以下图所示。 * soft nofile 65536 * hard nofile 100000
一、建立一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。
#include <sys/epoll.h> int epoll_create(int size) size:监听数目
二、控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epfd: 为epoll_creat的句柄 op: 表示动做,用3个宏来表示: EPOLL_CTL_ADD (注册新的fd到epfd), EPOLL_CTL_MOD (修改已经注册的fd的监听事件), EPOLL_CTL_DEL (从epfd删除一个fd); event: 告诉内核须要监听的事件 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; EPOLLIN : 表示对应的文件描述符能够读(包括对端SOCKET正常关闭) EPOLLOUT: 表示对应的文件描述符能够写 EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来) EPOLLERR: 表示对应的文件描述符发生错误 EPOLLHUP: 表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的 EPOLLONESHOT:只监听一次事件,当监听完此次事件以后,若是还须要继续监听这个socket的话,须要再次把这个socket加入到EPOLL队列里
三、等待所监控文件描述符上有事件的产生,相似于select()调用。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events: 用来存内核获得事件的集合, maxevents: 告以内核这个events有多大,这个maxevents的值不能大于建立epoll_create()时的size, timeout: 是超时时间 -1: 阻塞 0: 当即返回,非阻塞 >0: 指定毫秒 返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
server
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready, efd, res; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; int client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; struct epoll_event tep, ep[OPEN_MAX]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); Listen(listenfd, 20); for (i = 0; i < OPEN_MAX; i++) client[i] = -1; maxi = -1; efd = epoll_create(OPEN_MAX); if (efd == -1) perr_exit("epoll_create"); tep.events = EPOLLIN; tep.data.fd = listenfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); if (res == -1) perr_exit("epoll_ctl"); while (1) { nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞监听 */ if (nready == -1) perr_exit("epoll_wait"); for (i = 0; i < nready; i++) { if (!(ep[i].events & EPOLLIN)) continue; if (ep[i].data.fd == listenfd) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (j = 0; j < OPEN_MAX; j++) { if (client[j] < 0) { client[j] = connfd; /* save descriptor */ break; } } if (j == OPEN_MAX) perr_exit("too many clients"); if (j > maxi) maxi = j; /* max index in client[] array */ tep.events = EPOLLIN; tep.data.fd = connfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); if (res == -1) perr_exit("epoll_ctl"); } else { sockfd = ep[i].data.fd; n = Read(sockfd, buf, MAXLINE); if (n == 0) { for (j = 0; j <= maxi; j++) { if (client[j] == sockfd) { client[j] = -1; break; } } res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); if (res == -1) perr_exit("epoll_ctl"); Close(sockfd); printf("client[%d] closed connection\n", j); } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } } } } close(listenfd); close(efd); return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
在这个过程当中,有两种工做模式:
基于管道epoll ET触发模式
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #define MAXLINE 10 int main(int argc, char *argv[]) { int efd, i; int pfd[2]; pid_t pid; char buf[MAXLINE], ch = 'a'; pipe(pfd); pid = fork(); if (pid == 0) { close(pfd[0]); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(pfd[1], buf, sizeof(buf)); sleep(2); } close(pfd[1]); } else if (pid > 0) { struct epoll_event event; struct epoll_event resevent[10]; int res, len; close(pfd[1]); efd = epoll_create(10); /* event.events = EPOLLIN; */ event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */ event.data.fd = pfd[0]; epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == pfd[0]) { len = read(pfd[0], buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } close(pfd[0]); close(efd); } else { perror("fork"); exit(-1); } return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, efd; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); struct epoll_event event; struct epoll_event resevent[10]; int res, len; efd = epoll_create(10); event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */ printf("Accepting connections ...\n"); cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); event.data.fd = connfd; epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == connfd) { len = read(connfd, buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, i; char ch = 'a'; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(sockfd, buf, sizeof(buf)); sleep(10); } Close(sockfd); return 0; }
server
/* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, efd, flag; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); struct epoll_event event; struct epoll_event resevent[10]; int res, len; efd = epoll_create(10); /* event.events = EPOLLIN; */ event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */ printf("Accepting connections ...\n"); cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); flag = fcntl(connfd, F_GETFL); flag |= O_NONBLOCK; fcntl(connfd, F_SETFL, flag); event.data.fd = connfd; epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) { printf("epoll_wait begin\n"); res = epoll_wait(efd, resevent, 10, -1); printf("epoll_wait end res %d\n", res); if (resevent[0].data.fd == connfd) { while ((len = read(connfd, buf, MAXLINE/2)) > 0) write(STDOUT_FILENO, buf, len); } } return 0; }
client
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 8080 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, i; char ch = 'a'; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (1) { for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; write(sockfd, buf, sizeof(buf)); sleep(10); } Close(sockfd); return 0; }
传输层主要应用的协议模型有两种,一种是TCP协议,另一种则是UDP协议。TCP协议在网络通讯中占主导地位,绝大多数的网络通讯借助TCP协议完成数据传输。但UDP也是网络通讯中不可或缺的重要通讯手段。
相较于TCP而言,UDP通讯的形式更像是发短信。不须要在数据传输以前创建、维护链接。只专心获取数据就好。省去了三次握手的过程,通讯速度能够大大提升,但与之伴随的通讯的稳定性和正确率便得不到保证。所以,咱们称UDP为“无链接的不可靠报文传递”。
那么与咱们熟知的TCP相比,UDP有哪些优势和不足呢?因为无需建立链接,因此UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通讯场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。因此,一般状况下,使用UDP协议进行数据传输,为保证数据的正确性,咱们须要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。
2)借助setsockopt函数改变接收缓冲区大小。如:
#include <sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int n = 220x1024 setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
编译运行server,在两个终端里各开一个client与server交互,看看server是否具备并发服务的能力。用Ctrl+C关闭server,而后再运行server,看此时client还可否和server联系上。和前面TCP程序的运行结果相比较,体会无链接的含义。
server
#include <string.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <strings.h> #include <arpa/inet.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int sockfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int i, n; sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); printf("Accepting connections ...\n"); while (1) { cliaddr_len = sizeof(cliaddr); n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len); if (n == -1) perror("recvfrom error"); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); if (n == -1) perror("sendto error"); } close(sockfd); return 0; }
client
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <strings.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int sockfd, n; char buf[MAXLINE]; sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); while (fgets(buf, MAXLINE, stdin) != NULL) { n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); if (n == -1) perror("sendto error"); n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0); if (n == -1) perror("recvfrom error"); write(STDOUT_FILENO, buf, n); } close(sockfd); return 0; }
组播组能够是永久的也能够是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成能够发生变化。永久组播组中成员的数量均可以是任意的,甚至能够为零。那些没有保留下来供永久组播组使用的ip组播地址,能够被临时组播组利用。
224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不作分配,其它地址供路由协议使用; 224.0.1.0~224.0.1.255 是公用组播地址,能够用于Internet;欲使用需申请。 224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效; 239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
可以使用ip ad命令查看网卡编号,如:
itcast$ ip ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000 link/ether 00:0c:29:0a:c4:f4 brd ff:ff:ff:ff:ff:ff inet6 fe80::20c:29ff:fe0a:c4f4/64 scope link valid_lft forever preferred_lft forever
server
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <net/if.h> #define SERVER_PORT 6666 #define CLIENT_PORT 9000 #define MAXLINE 1500 #define GROUP "239.0.0.2" int main(void) { int sockfd, i ; struct sockaddr_in serveraddr, clientaddr; char buf[MAXLINE] = "itcast\n"; char ipstr[INET_ADDRSTRLEN]; /* 16 Bytes */ socklen_t clientlen; ssize_t len; struct ip_mreqn group; /* 构造用于UDP通讯的套接字 */ sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; /* IPv4 */ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 本地任意IP INADDR_ANY = 0 */ serveraddr.sin_port = htons(SERVER_PORT); bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); /*设置组地址*/ inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/ inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 编号 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0"); setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group)); /*构造 client 地址 IP+端口 */ bzero(&clientaddr, sizeof(clientaddr)); clientaddr.sin_family = AF_INET; /* IPv4 */ inet_pton(AF_INET, GROUP, &clientaddr.sin_addr.s_addr); clientaddr.sin_port = htons(CLIENT_PORT); while (1) { //fgets(buf, sizeof(buf), stdin); sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr)); sleep(1); } close(sockfd); return 0; }
client
#include <netinet/in.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <net/if.h> #define SERVER_PORT 6666 #define MAXLINE 4096 #define CLIENT_PORT 9000 #define GROUP "239.0.0.2" int main(int argc, char *argv[]) { struct sockaddr_in serveraddr, localaddr; int confd; ssize_t len; char buf[MAXLINE]; /* 定义组播结构体 */ struct ip_mreqn group; confd = socket(AF_INET, SOCK_DGRAM, 0); //初始化本地端地址 bzero(&localaddr, sizeof(localaddr)); localaddr.sin_family = AF_INET; inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr); localaddr.sin_port = htons(CLIENT_PORT); bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr)); /*设置组地址*/ inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/ inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 编号 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0"); /*设置client 加入多播组 */ setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)); while (1) { len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0); write(STDOUT_FILENO, buf, len); } close(confd); return 0; }
socket API本来是为网络通信设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通信(经过loopback地址127.0.0.1),可是UNIX Domain Socket用于IPC更有效率:不须要通过网络协议栈,不须要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另外一个进程。这是由于,IPC机制本质上是可靠的通信,而网络协议是为不可靠的通信设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,相似于TCP和UDP,可是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最普遍的IPC机制,好比X Window服务器和GUI程序之间就是经过UNIXDomain Socket通信的。
使用UNIX Domain Socket的过程和网络socket十分类似,也要先调用socket()建立一个socket文件描述符,address family指定为AF_UNIX,type能够选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0便可。
UNIX Domain Socket与网络socket编程最明显的不一样在于地址格式不一样,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用建立,若是调用bind()时该文件已存在,则bind()错误返回。
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址结构类型 __be16 sin_port; /* Port number */ 端口号 struct in_addr sin_addr; /* Internet address */ IP地址 }; struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型 char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径) };
如下程序将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); #define offsetof(type, member) ((int)&((type *)0)->MEMBER)
server
#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #define QLEN 10 /* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */ int serv_listen(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); /* in case it already exists */ unlink(name); /* fill in socket address structure */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); /* bind the name to the descriptor */ if (bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */ rval = -3; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); } int serv_accept(int listenfd, uid_t *uidptr) { int clifd, len, err, rval; time_t staletime; struct sockaddr_un un; struct stat statbuf; len = sizeof(un); if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) return(-1); /* often errno=EINTR, if signal caught */ /* obtain the client's uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */ if (stat(un.sun_path, &statbuf) < 0) { rval = -2; goto errout; } if (S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout; } if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ /* we're done with pathname now */ unlink(un.sun_path); return(clifd); errout: err = errno; close(clifd); errno = err; return(rval); } int main(void) { int lfd, cfd, n, i; uid_t cuid; char buf[1024]; lfd = serv_listen("foo.socket"); if (lfd < 0) { switch (lfd) { case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break; } exit(-1); } cfd = serv_accept(lfd, &cuid); if (cfd < 0) { switch (cfd) { case -3:perror("not a socket"); break; case -2:perror("a bad filename"); break; case -1:perror("accept"); break; } exit(-1); } while (1) { r_again: n = read(cfd, buf, 1024); if (n == -1) { if (errno == EINTR) goto r_again; } else if (n == 0) { printf("the other side has been closed.\n"); break; } for (i = 0; i < n; i++) buf[i] = toupper(buf[i]); write(cfd, buf, n); } close(cfd); close(lfd); return 0; }
client
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */ /* * Create a client endpoint and connect to a server. * Returns fd if all OK, <0 on error. */ int cli_conn(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); /* fill socket address structure with our address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); /* in case it already exists */ unlink(un.sun_path); if (bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if (connect(fd, (struct sockaddr *)&un, len) < 0) { rval = -4; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); } int main(void) { int fd, n; char buf[1024]; fd = cli_conn("foo.socket"); if (fd < 0) { switch (fd) { case -4:perror("connect"); break; case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break; } exit(-1); } while (fgets(buf, sizeof(buf), stdin) != NULL) { write(fd, buf, strlen(buf)); n = read(fd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n); } close(fd); return 0; }
过期,仅用于IPv4,且线程不安全。
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno; int main(int argc, char *argv[]) { struct hostent *host; char str[128]; host = gethostbyname(argv[1]); printf("%s\n", host->h_name); while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++); switch (host->h_addrtype) { case AF_INET: while (*(host->h_addr_list) != NULL) printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }
此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno; int main(int argc, char *argv[]) { struct hostent *host; char str[128]; struct in_addr addr; inet_pton(AF_INET, argv[1], &addr); host = gethostbyaddr((char *)&addr, 4, AF_INET); printf("%s\n", host->h_name); while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++); switch (host->h_addrtype) { case AF_INET: while (*(host->h_addr_list) != NULL) printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }