通常写一个跨计算机网络的通信程序时,首先要考虑到双方协议,也就是交换消息的合约是什么。这也就是常说的通信协议。目前随着通信技术的发展,通信协议是非常多,特别是移动通信网,从原来的GSM网络中SS7、GPRS等到现在LTE中各种通信协议。这些协议作为规范可能单就文档就有上千页,由此可见协议的复杂性。另一个方面协议也非常重要,对一个产品来说,协议可控制产品的私密性,比喻说QQ,它发展了自己的一套通信协议,这个协议定义了客户端与服务端交互数据的格式,因此别人想定制一个客户端就非常难。目前部分网游的一些外挂其实现原理也是通过破解这些通信协议格式,向服务端送去一些特定的消息,以便修改游戏中人物的相关属性值。我们目前使用的INTENT,它是以基于TCP/IP协议簇发展起来的,因此现在绝大部分操作系统都是采用基于TCP/IP协议的内部实现。TCP/IP协议虽然最早是基于BSD4.1开始提供,但是因为它是使用Opensource license,后续的UNIX可以直接在其上进行改进。但基本上还是兼容原来实现。但对LINUX和WINDOWS来说,它都是完全重新自己实现了TCP/IP协议。如下图所示:从操作系统与用户层来说,TCP/IP以下重点关注是通信实现,而TCP/IP以上则侧重于业务需求。因此,操作系统向上对客户应用来说只是一些系统调用,这些调用抽象成SOCKET API,通过这个API,应用可以不用关注底层有关通讯的一些实现细节,交到内核实现就可以了。

Linux下C编程(4)_休闲

目前在WINDOWS和LINUX上实现TCP/IP协议簇大部分应用,这些应用有些是通过TCP/UDP/SCTP进行,有些是直接对IP层操作,还有一些能够直接进行DATALINK操作。这些操作从应用的角度来说都是使用SOCKET API系统调用,也就是说使用SOCKETAPI它实际上不仅仅可以控制TCP/UDP之上的数据,还可以控制基于DATALINK/IP之上的数据。如下图所示:

Linux下C编程(4)_职场_02

因为本学习不针对协议过分强调,但一些常见的概念作一些简要介绍,如下:

1)TCP的三次握手和四次挥手机制

TCP同UDP不一样,是面向连接的,具有一定的可靠性,当然这个可靠性也不是100%,但是它提供了一种机制,这种机制就是收发端要进行双向同步确认机制。如下图所示,在建立阶段,发起方或称客户端发送一SYN数据包,请求对端ACK,当中使用SEQ 数据作为校验标识,服务端在获取客户端SYN数据包的SEQ值加上1作为向客户端发出ACK标识的值,如下图中J+1,附带在服务端发出的SYN数据包中,客户端接收到这个SYN包时需要检验ACK标识,同时给服务端一个ACK数据包,这个包中的ACK值使用服务端发过来的SYN中序列值加1.如下图中K+1

Linux下C编程(4)_编程_03

在关闭阶段,因为关闭分成两种情况,一种是由客户端主动关闭,在这种关闭的情况,客户端首先发出FIN数据包,服务端在收到FIN数据包后,将服务端接收客户端数据的FD关闭,这样服务端不再读取数据,同时给客户端发出一个ACK数据包确认连接CLOSE,进入FIN_WAIT_1状态,客户端进入CLOSE_WAIT。另一种是当服务端写发送数据的FD进行关闭时,服务端发出FIN数据包,客户端回确认包,这时候服务端不能再写入数据。对应的就是客户端不能读取数据。

Linux下C编程(4)_编程_04

2)socket pair组成(连接的两个端点由4个值来决定,本地IP地址,本地端口,对端IP地址,对端端口),如下图所示,通常对一个TCP服务端来说,都有多个客户端,如何唯一标识这种连接呢,实际上使用的是socketpair,四个值来确定。

Linux下C编程(4)_职场_05

3)缓冲机制及数据报文大小限制。不管是TCP还UDP其依赖的都是IP数据包,不管是IPV4还是IPV6最大的值都是65535字节,这当中还包括IPV4/IPV6的头。因此在硬件层给这个报文传输单元大小设置了一个一个参数叫MTU,通常Ethernet MTU是1500bytes。当然可以通过ethtool工具进行调整。对IPV4来说,如果超过MTU值的包进来会进行拆分,记录序列号,然后在对方进行组装。对IPV6来说,有些路由器就不进行拆分,直接回错。当然这里面对Socket还有一个选项MSS可以进行设置。另外从开发的角度来说,SOCKET数据是用户空间向内核空间进行拷贝,这里TCP/UDP有一个机制是不同的。TCP还有一个内核空间缓存大小。如下图所示,可以通过SO_SNDBUF设置这个缓冲的大小。而UDP则没有,直接是数据链路层的一个固定大小。

Linux下C编程(4)_编程_06

Linux下C编程(4)_职场_07

4)僵尸进程(zombie process),我们知道,通常在TCPSOCKET服务端处理多个客户端连接时都会通过fork函数创建子线程来单独处理外来连接,这时会有一个问题的产生,就是当客户端连接关闭时,相应的服务端的子进程也需要关闭,如果只是简单的exit退出,实际上子线程并没有真正销毁掉。通过使用“ps aux|awk '{print $8 " " $2 }'|grep -w Z”可以查出zombie process进程。 在TCP/IP服务端开发时,如果在服务端使用fork出子线程来处理连接socket时,需要使用在父进程中使用signal处理SIGCHLD信号量。

Linux下C编程(4)_职场_08