LinuxSocket编程中的隐患1.忽略返回状态第一个隐患很明显,但却是新手最容易犯的错误。如果您忽略函数的返回状态,当它们失败或部分成功时您可能会迷失方向。反过来,这会传播错误,从而难以找到问题的根源。捕获并检查每个退货状态,而不是忽略它们。考虑清单1中所示的示例,一个套接字发送函数。忽略API函数返回状态intstatus,sock,mode;/*Createanewstream(TCP)socket*/sock=socket(AF_INET,SOCK_STREAM,0);...status=send(sock,buffer,buflen,MSG_DONTWAIT);if(status==-1){/*sendfailed*/printf("sendfailed:%s\n",strerror(errno));}else{/*sendsucceeded--ordidit?*/}探索完成套接字的函数片段字发送操作(通过套接字发送数据)。函数的错误状态被捕获和测试,但这个例子忽略了一个以非阻塞模式发送的特性(由MSG_DONTWAIT标志启用)。发送API函数具有三个可能的返回值:如果数据已成功排队传输,则为0。如果排队失败,则返回-1(使用errno变量找出失败的原因)。如果在函数调用时并非所有字符都可以排队,则最终返回值是发送的字符数。由于send的MSG_DONTWAIT变量的非阻塞特性,函数调用在所有数据发送完毕、部分数据发送完毕或未发送数据后返回。忽略此处的返回状态将导致发送不完整和随后的数据丢失。陷阱2.点对点套接字闭包UNIX有趣的一面是您几乎可以将任何东西视为文件。文件本身、目录、管道、设备和套接字都被视为文件。这是一种新颖的抽象,这意味着一整套API可用于广泛的设备类型。考虑读取API函数,它从文件中读取一定数量的字节。read函数返回读取的字节数(不超过您指定的最大值);或-1,表示错误;或0,如果已到达文件末尾。如果读取操作在套接字上完成并获得返回值0,则表示远程套接字端的对等方调用了关闭API方法。该指示与文件读取相同-无法通过描述符读取额外数据。适当处理读取API函数intsock、status的返回值;sock=socket(AF_INET,SOCK_STREAM,0);...status=read(sock,buffer,buflen);if(status>0){/*Datareadfromthesocket*/}elseif(status==-1){/*错误,checkerrno,takeaction...*/}elseif(status==0){/*Peerclosedthesocket,finishtheclose*/close(sock);/*Furtherprocessing...*/}同样可以探测到peersocket的关闭与写API功能。在这种情况下,写入函数返回-1并在收到SIGPIPE信号时将errno设置为EPIPE,或者如果该信号阻塞。隐患3、地址使用错误(EADDRINUSE)可以使用bindAPI函数将地址(接口和端口)绑定到socket端点。此功能可用于服务器设置以限制可能来自连接的接口。此功能也可用于客户端设置,以限制应提供连接的接口。bind最常见的用途是将端口号与服务器相关联,并使用通配符地址(INADDR_ANY),这允许任何接口用于传入连接。bind的一个常见问题是尝试绑定一个已在使用的端口。陷阱是可能不存在活动套接字,但仍然禁止绑定端口(绑定返回EADDRINUSE),这是由TCP套接字状态TIME_WAIT引起的。套接字关闭后,此状态会持续大约2到4分钟。在TIME_WAIT状态退出后,套接字被删除,这样地址就可以毫无问题地重新绑定。等待TIME_WAIT结束可能会很烦人,特别是如果您正在开发需要停止以进行某些更改然后重新启动的套接字服务器。幸运的是,有一些方法可以避免TIME_WAIT状态。可以将SO_REUSEADDR套接字选项应用于套接字,以便可以立即重用该端口。考虑以下示例。在绑定地址之前,我使用SO_REUSEADDR选项调用setsockopt。为了允许地址重用,我将整数参数(on)设置为1(否则,可以将其设置为0以禁用地址重用)。使用SO_REUSEADDR套接字选项避免地址使用错误(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));/*Allowconnectionstoport8080fromanyavailableinterface*/memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=htonl(INADDR_ANY);servaddr.sin_port=htons(45000);/*Bindtotheadaddress(interface/port)*/ret=bind(sock,(structsockaddr*)&servaddr,sizeof(servaddr));应用SO_REUSEADDR选项后,绑定API函数将允许立即重用。隐患4.发送结构化数据套接字是发送非结构化二进制字节流或ASCII数据流(例如通过HTTP传输HTTP页面,或通过SMTP传输电子邮件)的完美工具。但是,如果您尝试通过套接字发送二进制数据,事情就会变得更加复杂。假设您想发送一个整数:您能确定接收方会以相同的方式解释该整数吗?运行在同一架构上的应用程序可以依靠它们的通用平台来对那类数据做同样的解释。但是,如果运行在大端IBMPowerPC上的客户端向小端Intelx86发送一个32位整数,会发生什么?字节对齐会导致不正确的解释。字节交换与否?字节顺序是指字节在内存中的排列顺序。BigEndian按最高有效字节在前排序,而LittleEndian按最低有效字节在先排序。大端架构(例如PowerPC?)优于小端架构(例如Intel?Pentium?系列,其网络字节顺序是大端架构)。这意味着对于大端机器,控制数据在TCP/IP中自然排序。Littleendian体系结构需要字节交换——这是网络应用程序的一个轻微性能弱点。通过套接字发送C结构怎么样?在这里,您也会遇到麻烦,因为并非所有编译器都以相同的方式排列结构的元素。结构也可以被压缩以最小化浪费的空间,这进一步错位了结构中的元素。幸运的是,这个问题有一些解决方案可以保证两端数据的解释一致。过去,远程过程调用(RemoteProcedureCall,RPC)封装工具提供了所谓的外部数据表示(ExternalDataRepresentation,XDR)。XDR定义了数据的标准表示,以支持异构网络应用程序通信的发展。现在,有两个提供类似功能的新协议。可扩展标记语言/远程过程调用(XML/RPC)以XML格式安排通过HTTP的过程调用。数据和元数据以XML编码并作为字符串传输,主机模式将值与其物理表示分开。SOAP遵循XML-RPC,以更好的特性和功能扩展其思想。隐患5.TCP中的帧同步假定TCP不提供帧同步,这使得它非常适合面向字节流的协议。这是TCP和UDP(UserDatagramProtocol,用户数据报协议)的重要区别。UDP是一种面向消息的协议,它保留发送方和接收方之间的消息边界。TCP是一种面向流的协议,它假设正在通信的数据是非结构化的,如图1所示。UDP的帧同步能力和TCP缺乏帧同步图的上半部分说明了UDP客户端和服务器。左侧的对等方完成两次套接字写入,每次100字节。协议栈的UDP层跟踪写入次数,并确保当右侧的接收方通过套接字获取数据时,它以相同的字节数到达。换句话说,为读者保留了作者提供的消息边界。现在,看看图表的底部。它演示了TCP层的相同粒度的写操作。两个单独的写操作(每个100字节)被写入流套接字。但在这个例子中,流套接字读取器获得200个字节。协议栈的TCP层聚合了这两个写操作。这种聚合可以发生在TCP/IP协议栈的发送方或接收方。重要的是要注意聚合可能不会发生——TCP只保证数据的有序传递。对于大多数开发人员来说,这个陷阱会造成混淆。您需要TCP的可靠性和UDP的帧同步。除非使用其他传输协议,例如流传输控制协议(STCP),否则应用层开发人员需要实现缓冲和分段功能。查看网络子系统的详细信息netstat工具提供了查看GNU/Linux网络子系统的能力。使用netstat,您可以查看当前活动的连接(每个单独的协议)、查看处于特定状态的连接(例如处于侦听状态的服务器套接字)以及许多其他信息。netstat提供的一些选项及其启用的功能。netstat实用程序的使用模式查看当前活动的所有TCPsockets$netstat--tcpViewallUDPsockets$netstat--udpViewallTCPsocketsinthelisteningstate$netstat--listeningViewthemulticastgroupmembershipinformation$netstat--groupsDisplaythelistofmasqueradedconnections$netstat--masqueradeViewstatisticsforeachprotocol$netstat--statistics和其他标准的GNU/Linux工具。监控流量GNU/Linux可以使用多种工具来检查网络上的低级流量。tcpdump工具是一个较旧的工具,它从网络中“嗅探”网络数据包,将它们打印到标准输出或将它们记录到文件中。此功能允许查看应用程序生成的流量和TCP生成的低级流量控制机制。补充tcpdump的是一个名为tcpflow的新工具,它提供协议流分析和一种适当地重建数据流的方法,而不管数据包顺序或重传。tcpdump的两种使用模式。cpdump工具的用法模式Displayalltrafficontheeth0interfaceforthelocalhost$tcpdump-l-ieth0Showalltrafficonthenetworkcomingfromorgoingtohostplato$tcpdumphostplatoShowallHTTPtrafficforhostcamus$tcpdumphostcamusand(porthttp)ViewtrafficcomingfromorgoingtoTCPport45000onthelocalhost$tcpdumptcpport45000tcpdump和tcpflow工具有大量的选项,包括创建复杂过滤表达式的能力。Bothtcpdumpandtcpflowaretext-basedcommand-line工具。如果您更喜欢图形用户界面(GUI),有一个开源工具Ethereal可能会满足您的需要。Ethereal是一款专业的协议分析软件,可以帮助调试应用层协议。它的插件架构可以分解协议,例如HTTP和您能想到的任何协议(在撰写本文时为637)。
