Linux套接字编程5大陷阱

原文链接: http://www.cnblogs.com/hanhuilee/archive/2013/01/10/5221388.html

Linux套接字编程5大陷阱

-  在异构环境开发可靠的网络应用

M. Tim Jones


摘要(summary这里翻译为摘要):

套接字API是网络应用开发事实上的标准API。尽管这些API很简单,但缺乏经验的开发人员仍旧会遇到一些常见的问题。这篇文章将识别那些最常见的问题并介绍如何克服它们。


套接字API首先被引入到4.2 BSD UNIX操作系统,且是现在所有操作系统的标准特性。事实上,几乎所有现代编程语言都支持套接字API。套接字API相对简单,但是缺乏经验的编程人员不得不小心行事,以避免落入陷阱。

这篇文章将识别那些最常见的问题并介绍如何克服它们。


陷阱一:忽略返回状态

第一个陷阱很明显,但是它是缺乏经验的编程人员常常忽略的。如何你忽略函数调用的返回状态,那么你就可能注意不到它们失败了或者只是成功了一半。结果就是这个错误一直传递下去,以至于问题出现时已经很难定位问题到底出现在哪里了?

因此,不要忽略返回状态,而是要捕获并检测每一个返回值。看一下列表1中使用send函数的例子。


列表1. 忽略API调用返回状态


  1. int status, sock, mode;

  2. /* Create a new stream (TCP) socket */
  3. sock = socket( AF_INET, SOCK_STREAM, 0 );

  4. ...

  5. status = send( sock, buffer, buflen, MSG_DONTWAIT );

  6. if (status == -1) {

  7.   /* send failed */
  8.   printf( "send failed: %s\n", strerror(errno) );

  9. } else {

  10.   /* send succeeded -- or did it? */

  11. }

列表1展示了一个执行套接字函数send(通过一个套接字发送数据)的函数片段。在这个例子中,虽然函数的错误状态被捕获与检测,但是我们却忽略了send在非阻塞模式(通过MSG_DONTWAIT标记使能)时的特性。

send函数可能返回三类值:

如果内核将数据添加到发送队列以备发送,返回0;

如果调用过程中发送错误,返回-1(具体错误值通过errno变量标示,errno是线程本地数据)

如果只处理了部分数据,那么返回值标示最终发送了多少数据


由于send具有由MSG_DONTWAIT激活的非阻塞性特征,所以函数调用返回时可能已经发送了全部数据,或者只发送了部分甚至没有发送数据。在这个例子中,忽略返回状态可能导致不完整的数据发送以及其引起的数据丢失。


陷阱二. 对端套接字关闭

在UNIX系统中,几乎所有一切都可作为文件来处理。文件本身,文件夹,管道,外设,以及套接字都可当作文件。这种抽象意味着一系列设备类型可以使用一组相同的API来管理。

考虑函数read,它用来从文件读取以字节为最小单位的数据。read函数返回读取的字节数,发生错误时返回-1,读到文件尾时返回0。

如果你从一个文件读取数据直到文件结束(通过返回0标示),你应当关闭文件并认为大功告成。这同样适用于套接字,但语意稍有区别。如果你对一个套接字执行read并得到返回值0,那么这说明套接字的对端已经执行了close函数。这同读文件时一样,读取这个文件描述符不能得到更多的数据。


列表2. read函数正确使用方式


  1. int sock, status;

  2. sock = socket( AF_INET, SOCK_STREAM, 0 );

  3. ...

  4. status = read( sock, buffer, buflen );

  5. if (status > 0) {

  6.   /* Data read from the socket */

  7. } else if (status == -1) {

  8.   /* Error, check errno, take action... */

  9. } else if (status == 0) {

  10.   /* Peer closed the socket, finish the close */
  11.   close( sock );

  12.   /* Further processing... */

  13. }

使用write函数也可以到探测对端socket是否关闭。这种情况下,你将收到一个SIGPIPE信号(如果不响应(忽略或其他处理方式)这个信号,那么应用程序将直接退出),或者如果这个信号被阻塞了,那么write函数将返回-1并将errno设置为EPIPE。


陷阱三. 地址冲突错误

你可能使用bind函数绑定一个地址(接口加端口)到套接字。你可能在服务器端使用这个函数以限制使用哪个接口允许外部连接。你也可以在客户端使用这个函数以限制使用哪个接口发起连接。bind的常用方式是为服务器关联一个端口并使用任意地址(所有网卡上的IP地址),这样客户端可连接任一服务器接口(IP)。

使用bind函数的常见问题是试图绑定已经使用的端口。这个陷阱是说不许可将处于禁止状态的端口绑定给激活的套接字,这是由TCP套接字的TIME_WAIT状态导致的。这一状态将在套接字关闭后保持2到4分钟的时间。TIME_WAIT状态退出后,套接字将会被删除,端口就可以重新使用了。

等待TIME_WAIT状态结束并不 让人愉悦,尤其是当你正在开发套接字服务器并且需要停止它,以便修改些什么再重新启动时。幸运的是,有办法应对TIME_WAIT状态。你可以为套接字设置SO_REUSEADDR选项,以便端口可以即时重新使用。

考虑列表3中的例子。我们在绑定地址前,调用setsocketopt设置SO_REUSEADDR选项。为了激活地址重用,设置其中的整数参数为1(或者,设置其为0去使能地址重用)。


列表3. 使用SO_REUSEADDR套接字选项避免出现“地址冲突”错误


  1. int sock, ret, on;
  2. struct sockaddr_in servaddr;

  3. /* Create a new stream (TCP) socket */
  4. sock = socket( AF_INET, SOCK_STREAM, 0 ):

  5. /* Enable address reuse */
  6. on = 1;
  7. ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

  8. /* Allow connections to port 8080 from any available interface */
  9. memset( &servaddr, 0, sizeof(servaddr) );
  10. servaddr.sin_family = AF_INET;
  11. servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
  12. servaddr.sin_port = htons( 45000 );

  13. /* Bind to the address (interface/port) */
  14. ret = bind( sock, (struct sockaddr *)&servaddr, sizeof(servaddr) );

应用SO_REUSEADDR套接字选项后,bind函数将始终许可端口的即时重用。


陷阱四. 发送结构化数据

这里介绍字节序转换,请参考[翻译]字节与比特序,或者直接阅读本文的原文


陷阱五. TCP组帧假设

TCP没有提供组帧,这使得它非常适合于面向字节流的协议。这是TCP和UDP的关键不同,UDP是面向消息的协议,它将保留发送者和接收者交互的各个消息之间的界限。TCP是基于流的协议,被发送的数据将会是非结构化的。如图1所示,


图1 UDP的组帧能力对比TCP无组帧特性


图1上给出了一个UDP客户端和服务器。左面的peer执行了两次套接字写操作,每次发送100字节。协议栈的UDP层跟踪写操作的数据量并保证右面的peer通过套接字获取数据,每次获取的数据也是100字节。换句话说,发送者给出的消息的边界也保留到了接收端。


现在看图1下。这次是两次同样的写操作,每次100字节,但是传输层是TCP(流套接字)。在这种情况下,流套接字的接收端通过一次读获取到200字节。协议栈的TCP层对两次写数据进行了组包。组包可能发生在TCP/IP协议栈的发送和接收任何一方。需要注意的是,也许根本就不会发生组包,TCP只保证数据的有序交付(有序发送给应用层,协议栈间有可能重传数据)。


这一问题让大多数开发人员不知所措。因为我们需要的是TCP的可靠性以及UDP的组帧特征。如果不能转向其他的传输层协议(如流传输控制协议(SCTP)),那么应用层开发人员需要自己实现数据缓存及分段功能。


[1] http://www.ibm.com/developerworks/linux/library/l-sockpit/index.html

[2] [翻译]字节与比特序








转载于:https://www.cnblogs.com/hanhuilee/archive/2013/01/10/5221388.html

猜你喜欢

转载自blog.csdn.net/weixin_30294295/article/details/94791172