




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、做linux下的网络编程有一段时间了,中间遇到过很多问题,其中不少是因为自己对网络编程和网络协议的一些基本概念搞不清楚,趁着今天没心情干活就把自己在网络编程方面的理解和一些经验总结一下,Request For Comments。在诸多的网络协议中接触的最多也最紧密的无疑是TCP和UDP,SCTP之前因为项目原因也研究过,不过最终由于方案修改给抛弃了,TCP年代已经很久远,在网上的资料也非常多,而且我感觉它是一种非常复杂的协议,感觉要把编好基于TCP的程序光简单地了解几个socket API是不够的,刚开始接触网络编程的时候自己确实也吃了不少苦头,后来我还专门拿时间出来阅读了一下RFC,再加上长
2、时间的实践总算也对TCP有所了解,把自己的一些经验和教训都总结一下。首先说一下TCP的状态转移图,这个应该是很重要的,了解TCP运行周期的各种状态才能更好地运用netstat之类的应用程序去对程序进行调试,我这里收藏了一张图,是TCP的状态图,记不清是从哪里找来的,也不知道直接版权该给谁,但这张图应该最终是出自于UNP第一卷的,那copyright就是UNP了吧。1.TCP连接状态1 / 11连接建立的几个状态没什么可说的,TCP的三次握手众所周知,更重要的是TCP连接中止的几个状态,应该可以说是连接中止需要四次握手吧。当Client调用close函数主动关闭socket时,连接状态被标记为F
3、IN_WAIT_1,Server在收到FIN之后read函数会返回0,这里server知道Client已经关闭连接,回复ACK,这里client连接状态被标记为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,
4、则Server发送的FIN会直接收到RST,导致Server端的发送错误,因此Client需要保证有一个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
5、收到最后一个ACK便会将连接标记为CLOSED,这时连接结束。TIME_WAIT这个状态和套接字的SO_REUSEADDR选项是有关系的,这个留做后面讨论。2.TCP连接异常情况TCP连接异常分为很多种情况,无论是客户端程序还是服务器端程序都需要考虑周全的。Server在连接的过程中程序崩溃或者CTRL+C中止程序,或者kill接Server进程。这时会导致Server立即发送一个FIN数据包给Client,Client如果此时正在调用recv函数,则recv函数返回0,表示服务器已关闭连接,如果Client调用send函数继续向Server发送数据,Server在收到后会回复RST,而此时s
6、end方法会触发SIGPIPE信号,表示通信管道已断开,在程序中如果对该信号不做处理则会导致程序的崩溃,一般在程序开始时会忽略此信号,则在这种情况下send函数会返回-1,表示发送失败,处理SIGPIPE的代码如下:前几天实验室这个破项目非要加上什么流媒体的功能,简单起见使用了VLC来实现,客户端这边就得需要把相关的播放界面整合到现有的界面里面来,之前的客户端UI我都是用GTK实现的,没办法,GTK用得比较多,相对熟练一些就用GTK来做了,没想到要把VLC整到GTK里面来那么麻烦,原生的libvlc是不支持GTK的,需要加一层libvlc-gtk,从网上好不容易下载到了libvlc-gtk的源
7、码,从哪里下的也记不清了,反正就是零散地几个文件,没有README甚至连Makefile都没有,没办法首先得先写个Makefile把它编译一下,libvlc-gtk一共有八个文件,Makefile如下:struct sigaction sa;sa.sa_handler = SIG_IGN;sigaction(SIGPIPE, &sa, 0 );另外在这种情况下select函数也会立即返回,socket描述符会被设置,而试图从该socket中recv数据,则会返回-1。另外一种情况是Server系统崩溃或者网络直接异常或断开,这时候Server不可能再给Client发送FIN包,而Client调
8、用send函数后会导致数据包一直重传直接超时后返回-1,而recv函数也会一直阻塞直接超时后返回-1。这种情况就很难判断是Server端进程关闭还是网络异常,这种情况一般会用TCP的KEEP ALIVE机制,每隔一定的时间向对方发送一个只有一字节数据内容的数据包,对端收到后会返回一个ACK,以此来确保连接正常,如果未收到ACK,会尝试重传,直到重试规定次数后可以将与对端的连接标记为断开,send和recv将会返回-1。KEEP ALIVE的使用方法如下:int tcp_keep_alive(int socketfd)int keepAlive = 1;int keepIdle = 10; /*
9、 开始发送KEEP ALIVE数据包之前经历的时间 */int keepInterval = 10; /* KEEP ALIVE数据包之前间隔的时间 */int keepCount = 10; /* 重试的最大次数 */if(setsockopt(socketfd , SOL_SOCKET , SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive) = -1)debug_info(set SO_KEEPALIVE failedn);return -1;if(setsockopt(socketfd , SOL_TCP , TCP_KEEPIDLE,(vo
10、id *)&keepIdle,sizeof(keepIdle) = -1)debug_info(set TCP_KEEPIDEL failedn);return -1;if(setsockopt(socketfd , SOL_TCP , TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval) = -1)debug_info(set TCP_KEEPINTVL failedn);return -1;if(setsockopt(socketfd , SOL_TCP , TCP_KEEPCNT,(void *)&keepCount,sizeo
11、f(keepCount) = -1)debug_info(set TCP_KEEPCNT failedn);return -1;return 1;上面这个函数只针对Linux,昨天有网友告知在Mac OS上TCP_KEEPIDLE ,TCP_KEEPINTVL, TCP_KEEPCNT这些宏将未定义。另外对于这些参数的设置也是需要注意的,很多系统中它们的设置并不是对单个socket描述符起作用的,而是该机器上的所有socket描述符起作用的,所以这个需要注意(这个是从UNP里面看到的)。3.关于字节顺序Linux的主机字节顺序是采用little-endian字节顺序,而网络字节顺序是采用big
12、-endian字节顺序,字节顺序转换是必需的。写了一个小程序来检测字节顺序,不知道对不对,Request For Comment.#include int main(int argc, char *argv)short s = 0x0102;if(*(unsigned char*)&s) = 2)printf(little endiann);else if(*(unsigned char*)&s) = 1)printf(big endiann);elseprintf(unknown endiann);return 0;3.关于send和recv写过socket程序的人肯定都会知道send和re
13、cv函数并不会总是返回要求发送或读取的字节数,如: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 &(ret = recv(sock, buf+n, le
14、n-n, flags) != -1 &ret; n += ret);return (n!=len)? -1:n;关于这两个函数还有很重要的一点是应该尽可能大地一次发送或接收更多地数据,当然前提是缓冲区中有这些数据的话,原因很简单,当通信链路很好的时候数据可能会填满系统缓冲区,而recv便是从缓冲区中读取数据,这时候一次读取更多地字节就意味着可以少调用几次recv函数,而这些函数通常都是调用了系统调用,需要进行内核态和用户态上下文的切换,也就意味着多调用几次recv会带来额外的开销,之前写的一个代理服务器的程序数据传输速度一直很低,后来修改了recv和send的缓冲区大小后速率提高了近一倍。4.
15、关于非阻塞模式一般应用的时候都是使用阻塞式IO,至少我在大多数情况下都用的阻塞式IO,非阻塞很少应用,但存在便我价值,我用到的非阻塞IO的情况一般是用来进行超时connect,首先将socket设为非阻塞模式,connect立即返回-1,此时已向对端发送FIN,而并未来得及收到任何ACK,于是直接返回-1,但并不代表连接失败,errno会被置为EINPROGRESS ,表示连接正在进行中,然后通过select来设置socket可写的超时时间,如果规定时间内可写,且socket并无出错,则表示连接成功,socket出错则表示连接失败,或规定时间内不可写则表示连接超时,简单地写了如下代码:#inc
16、lude#include#include#include#include#include#include int main(int argc, char *argv)int sk;int flags;int err = 0;int ret;socklen_t len;struct sockaddr_in addr;fd_set fd_write;struct timeval tv;if( (sk = socket(AF_INET, SOCK_STREAM, 0) = -1 ) perror(socket);return 1;if( (flags = fcntl(sk, F_GETFL, 0)
17、= -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, sizeof(addr);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(59.64.129.169);addr.sin_port = htons(808);if(connect(sk, (struct sockaddr*)&a
18、ddr, sizeof(addr) = -1) if(errno != EINPROGRESS) perror(connect);return 1;FD_ZERO(&fd_write);FD_SET(sk, &fd_write);tv.tv_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, &
19、err, &len) = 0) if(err = 0) printf(connect successn); return 0; else fprintf(stderr, connect:%sn, strerror(err); return 1; elsefprintf(stderr, getsockopt:%sn, strerror(err);return 1;elsefprintf(stderr, connect(FD_ISSET) failedn);return 1;else if(ret = 0) fprintf(stderr, connect timeoutn);return 1;else fprintf(std
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 定点复印合同范本
- 2025年IC卡售气系统合作协议书
- 跨境销售合同范本
- 股权归属合同范本
- 二零二五年度办事处市场销售代理服务合同
- 液态废油罐车回收合同模板
- 2025年度办事处跨境电商运营与市场开发协议
- 智慧家居用地居间合同样本
- 二零二五年度办事处学术合作项目进度管理与调整协议
- 2025-2030年中国特缆行业市场现状分析规划研究报告
- 肠内营养患者的血糖管理
- 儿科新生儿脐炎培训课件
- 2024年苏州市职业大学单招职业适应性测试题库及答案解析
- 《纺织服装材料》课件-4纱线的结构与性能
- 2024过敏性休克抢救指南(2024)课件干货分享
- GB/T 44122-2024工业互联网平台工业机理模型开发指南
- DL-T-5759-2017配电系统电气装置安装工程施工及验收规范
- 城市更新模式探讨
- SY∕T 7087-2016 石油天然气工业 钻井和采油设备 液氮泵送设备
- 1.1时代为我搭舞台(课件)-【中职专用】中职思想政治《心理健康与职业生涯》(高教版2023·基础模块)
- 下肢静脉曲张危险因素
评论
0/150
提交评论