linux网络编程手记_第1页
linux网络编程手记_第2页
linux网络编程手记_第3页
已阅读5页,还剩6页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、做 linux 下的网络编程有一段时间了,中间遇到过很多问题,其中不少是因为自己对网络编程和网络协议 的一些基本概念搞不清楚,趁着今天没心情干活就把自己在网络编程方面的理解和一些经验总结一下, Request For Comments。在诸多的网络协议中接触的最多也最紧密的无疑是 TCP 和 UDP ,SCTP 之前因为项目原因也研究过, 不过 最终由于方案修改给抛弃了, TCP 年代已经很久远,在网上的资料也非常多,而且我感觉它是一种非常复 杂的协议,感觉要把编好基于 TCP 的程序光简单地了解几个 socket API 是不够的,刚开始接触网络编程 的时候自己确实也吃了不少苦头,后来我还专

2、门拿时间出来阅读了一下RFC,再加上长时间的实践总算也对TCP有所了解,把自己的一些经验和教训都总结一下。首先说一下 TCP 的状态转移图,这个应该是很重要的,了解 TCP 运行周期的各种状态才能更好地运用 netstat 之类的应用程序去对程序进行调试,我这里收藏了一张图,是 TCP 的状态图,记不清是从哪里找 来的,也不知道直接版权该给谁,但这张图应该最终是出自于 UNP 第一卷的,那 copyright 就是 UNP 了 吧。1. TCP 连接状态TCP状态转换图呂Yh RCVD XA XLISTEVhSlABUSHED氏乳、N,A( K 冋附打开CLOSH):一担1 JI':“

3、 UNCl.OS NG11111ACKACK11<x>1111F馮1_FFN WAIT I'fJME' WAI1农环F竺览送=磁2MSLWR1 -1 :idI AS! AC K"川:I clcweI MN连接建立的几个状态没什么可说的,TCP的三次握手众所周知,更重要的是TCP连接中止的几个状态,应该可以说是连接中止需要四次握手吧。当Client调用close函数主动关闭socket时,连接状态被标记为 FIN_WAIT_1 ,Server在收到FIN之后 read函数会返回0,这里server知道Client已经关闭连接,回复 ACK,这里client连

4、接状态被标记为 FIN_WAIT_2 ,接下来Server调用close函数关闭连接,这时候Server向client发送FIN,Client收到之 后将状态标记为TIME_WAIT,并回复ACK。TIME_WAIT 这个状态存在的意义在于 Client 回复的 ACK 未必会被 Server 收到,可能在传输过程中导致 包的丢失,而这里 Server 未收到 ACK 之后会重新向 Client 发送 FIN ,如果 client 未将状态标记为 TIME_WAIT而是直接标记为 CLOSED ,_则Server发送的FIN会直接收到RST,导致Server端的发送错 误,因此 Client

5、需要保证有一个 TIME_WAIT 状态,而这个状态会持续两位的 MSL( 最大段生命周期 ),从 而保证Server成功发送FIN并发送ACK,为了保证两个数据段传输的最大时间,因此TIME_WAIT持续的时间为两倍的 MSL。Server 在收到第一个 FIN 之后会将状态标记为 CLOSE_WAIT ,此时是 client 主动关闭连接,这里 Server 也需要调用Close给Client发送FIN (如上所述),之后 Server的状态标记为LAST_ACK,表示Server 正在等待Client发送的最后一个 ACK,当Server收到最后一个 ACK便会将连接标记为 CLOSED

6、,这时 连接结束。 TIME_WAIT 这个状态和套接字的 SO_REUSEADDR 选项是有关系的,这个留做后面讨论。2. TCP 连接异常情况TCP 连接异常分为很多种情况,无论是客户端程序还是服务器端程序都需要考虑周全的。Server 在连接的过程中程序崩溃或者 CTRL+C 中止程序, 或者 kill 接 Server 进程。这时会导致 Server 立 即发送一个 FIN 数据包给 Client , Client 如果此时正在调用 recv 函数,则 recv 函数返回 0,表示服务器 已关闭连接,如果 Client调用send函数继续向Server发送数据,Server在收到后会回

7、复 RST,而此时 send 方法会触发 SIGPIPE 信号,表示通信管道已断开,在程序中如果对该信号不做处理则会导致程序的 崩溃,一般在程序开始时会忽略此信号, 则在这种情况下 send 函数会返回 -1,表示发送失败, 处理 SIGPIPE 的代码如下:前几天实验室这个破项目非要加上什么流媒体的功能,简单起见使用了VLC 来实现,客户端这边就得需要把相关的播放界面整合到现有的界面里面来,之前的客户端 UI 我都是用 GTK 实现的,没办法, GTK 用得 比较多,相对熟练一些就用 GTK 来做了,没想到要把 VLC 整到 GTK 里面来那么麻烦,原生的 libvlc 是不 支持 GTK

8、的,需要加一层 libvlc-gtk ,从网上好不容易下载到了 libvlc-gtk 的源码,从哪里下的也记不清 了,反正就是零散地几个文件,没有 README 甚至连 Makefile 都没有,没办法首先得先写个 Makefile 把它编译一下, libvlc-gtk 一共有八个文件, Makefile 如下:struct sigaction sa ;sa.sa_handler = SIG_IGN ;sigaction (SIGPIPE, &sa, 0 );另外在这种情况下 select 函数也会立即返回, socket 描述符会被设置,而试图从该 socket 中 recv 数据,

9、 则会返回 -1 。另外一种情况是 Server 系统崩溃或者网络直接异常或断开, 这时候 Server 不可能再给 Client 发送 FIN 包, 而 Client 调用 send 函数后会导致数据包一直重传直接超时后返回 -1 ,而 recv 函数也会一直阻塞直接超时 后返回-1 。这种情况就很难判断是 Server 端进程关闭还是网络异常, 这种情况一般会用 TCP 的 KEEP ALIVE 机制,每隔一定的时间向对方发送一个只有一字节数据内容的数据包,对端收到后会返回一个ACK ,以此来确保连接正常,如果未收到 ACK ,会尝试重传,直到重试规定次数后可以将与对端的连接标记为断开,

10、send 和 recv 将会返回 -1 。 KEEP ALIVE 的使用方法如下:int tcp_keep_alive (int socketfd )int keepAlive = 1;/* 开始发送 KEEP ALIVE 数据包之前经历/* KEEP ALIVE 数据包之前间隔的时间 */* 重试的最大次数 */int keepIdle = 10; 的时间 */int keepInterval = 10;int keepCount = 10;if (setsockopt (socketfd , SOL_SOCKET , SO_KEEPALIVE,(void *)& keepAlive

11、 ,sizeof (keepAlive ) = -1)debug_info ("set SO_KEEPALIVE failed n "); return -1;if (setsockopt (socketfd , SOL_TCP , TCP_KEEPIDLE,(void * )& keepIdle ,sizeof (keepIdle ) = debug_info ("set TCP_KEEPIDEL failed n ");return -1;if (setsockopt (socketfd , SOL_TCP , TCP_KEEPINTVL,(

12、void* )& keepInterval ,sizeof(keepInterval ) = -1)debug_info ("set TCP_KEEPINTVL failed n ");return -1;if (setsockopt (socketfd , SOL_TCP , TCP_KEEPCNT,(void * )& keepCount ,sizeof (keepCount )= -1)debug_info ("set TCP_KEEPCNT failed n ");return -1;return 1;上面这个函数只针对 Linu

13、x ,昨天有网友告知在 Mac OS 上 TCP_KEEPIDLE ,TCP_KEEPINTVL, TCP_KEEPCNT 这些宏将未定义。另外对于这些参数的设置也是需要注意的,很多系统中它们的设置并不 是对单个 socket 描述符起作用的,而是该机器上的所有 socket 描述符起作用的,所以这个需要注意(这 个是从 UNP 里面看到的)。3. 关于字节顺序Linux 的主机字节顺序是采用 little-endian 字节顺序,而网络字节顺序是采用 big-endian 字节顺序,字 节顺序转换是必需的。写了一个小程序来检测字节顺序,不知道对不对, Request For Comment.

14、#include int main (int argc ,char * argv)short s = 0x0102 ;if (* (unsigned char *)&s) = 2)printf ("little endian n ");else if(*(unsigned char*)&s) = 1)printf ("big endian n ");elseprintf ("unknown endian n ");return 0;3. 关于 send 和 recv写过 socket 程序的人肯定都会知道 send 和

15、 recv 函数并不会总是返回要求发送或读取的字节数,如:int ret = recv (sk, buf , 2096 ,0);这句话并不总是读取到完整地 2096 个字节,相反地,大多数情况下都不能将 buf 读满, recv 只能返回当 前可以读取到的字节数,如果协议规定本次读取肯定会读取到 N 个字节,那我一般的做法会写一个这样的 函数来确保读取到固定的字节数:int buf_recv (int sock , void *buf , size_t len ,int flags )int n, ret ;if (len = 0) return 0;for (n=0;n!=len &

16、& (ret = recv (sock , buf +n, len-n, flags ) != -1&& ret; n += ret );return (n!= len )? -1:n;关于这两个函数还有很重要的一点是应该尽可能大地一次发送或接收更多地数据,当然前提是缓冲区中有 这些数据的话,原因很简单,当通信链路很好的时候数据可能会填满系统缓冲区,而 recv 便是从缓冲区中 读取数据, 这时候一次读取更多地字节就意味着可以少调用几次 recv 函数, 而这些函数通常都是调用了系 统调用,需要进行内核态和用户态上下文的切换,也就意味着多调用几次 recv 会带来额外的

17、开销,之前写 的一个代理服务器的程序数据传输速度一直很低,后来修改了 recv 和 send 的缓冲区大小后速率提高了近 一倍。4. 关于非阻塞模式一般应用的时候都是使用阻塞式 IO ,至少我在大多数情况下都用的阻塞式IO ,非阻塞很少应用,但存在便我价值,我用到的非阻塞 IO 的情况一般是用来进行超时 connect ,首先将 socket 设为非阻塞模式, connect立即返回-1,此时已向对端发送 FIN,而并未来得及收到任何 ACK ,于是直接返回-1,但并不代表 连接失败, errno 会被置为 EINPROGRESS , 表示连接正在进行中,然后通过 select 来设置 soc

18、ket 可写的 超时时间,如果规定时间内可写,且 socket 并无出错,则表示连接成功, socket 出错则表示连接失败, 或规定时间内不可写则表示连接超时,简单地写了如下代码:#include#include#include#include#include#include#include int main (int argc ,char *argv )intsk;intflags ;interr = 0intret;socklen_tlen;struct sockaddr_in addr ;fd_setfd_writestruct timevaltv;if( (sk = socket (

19、AF_INET, SOCK_STREAM , 0) = -1 ) perror ("socket" );return 1;if( (flags = fcntl (sk, F_GETFL, 0) = -1 )perror ("fcntl GET flags failed" );return 1;if (fcntl (sk, F_SETFL, flags | O_NONBLOCK ) = -1) perror ("fcntl SET flags failed" );return 1;memset (& addr , 0, size

20、of (addr );addr. sin_family = AF_INET ;addr. sin_addr .s_addr = inet_addr addr. sin_port = htons (808 );if (connect (sk, (struct sockaddr *)& addr , sizeof (addr ) = -1 ) if (errno != EINPROGRESS) perror ("connect" ); return 1;FD_ZERO(& fd_write );FD_SET(sk, & fd_write );tv. tv

21、_sec = 5;tv. tv_usec = 0; ret = select (sk + 1, (fd_set * )0, & fd_write , (fd_set *)0, &tv); if (ret > 0)if(FD_ISSET(sk,&fd_write ) len = sizeof (int );if (getsockopt (sk, SOL_SOCKET,SO_ERROR, & err , & len ) = 0) if (err = 0) printf ("connectsuccess n "); return 0; else fprintf (stderr ,"connect:%s n ", strerror (err ); return 1; else fprintf (stderr ,"getsockopt:%s n ", strerror (err );return 1;else fprintf (stderr , "connect(FD_ISSET)failed n ");return 1;else if ( ret = 0) fprintf (stderr , "connect timeout n "

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论