服务化基石之远程通信系列一:通信协议之传输层

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

通信协议之传输层

在服务化概念流行以前,单体式应用程序使用同一进程内的本地调用为主,本地方法的调用使得性能损耗可以忽略不计。在服务化概念开始流行以后,服务提供者与服务消费者之间采用远程通信,网络使得服务间调用的延时增大,带来了额外的性能损耗;并且,由于网络的不稳定性与不确定性,分布式系统间调用失败和超时的风险也随之增加。


高效、安全和便捷的实现远程通信是服务化的重要组成部分。另外,由于各种服务大多由异构语言所组成,因此,如何能将跨语言调用的成本降至最低,也受到越来越多的关注。远程通信的技术重点是通信方式、序列化协议和透明化RPC框架。

 

OSI是Open System Interconnection的缩写,中文翻译是开放式系统互联。国际标准化组织(ISO)制定了OSI模型,它定义了不同计算机之间实现互联的标准,是网络通信的基本框架。OSI模型将网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。


由于复杂度过高,OSI模型并没有TCP/IP模型应用广泛。TCP/IP是Transmission Control Protocol/Internet Protocol的简写,它可以被理解为OSI模型的浓缩版本,它将原OSI七层网络模型抽象为了四层:原OSI七层网络模型中的物理层和数据链路层对应为网络接口层;原OSI七层网络模型中的网络层和传输层仍然保留;原OSI七层网络模型中的会话层、表示层和应用层则合并为统一的应用层。


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层


传输层的作用是使源端和目的端的计算机的以对等的方式进行会话,实现端到端的传输。位于传输层的通信协议有TCP和UDP。采用TCP作为应用程序间的远程通信的传输方案十分常见,UDP也有其特定的使用场景。


TCP

TCP是Transmission Control Protocol的缩写,中文名称为传输控制协议。它是一种面向连接的协议,提供可靠的双向字节流。

服务化基石之远程通信系列一:通信协议之传输层

概述


      TCP通过三次握手的连接创建机制确保连接的可靠性。

01

SYN

      客户端发送包含同步序列号的SYN报文,并且同时传递一个随机数作为顺序号。为了方便描述,我们将该顺序号设为x。


02

SYN-ACK

服务端在接收到请求之后,返回SYN-ACK报文作为应答。并且同时传递一个值为x+1的应答号以及另一个随机数作为服务端的序列号。同样,为了方便描述,我们将应答号设为x+1,服务端序列号设为y。


03

ACK

客户端接收到服务端的应答后,分别将y+1与x+1作为应答号和序列号再次发送至客户端。


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层


当三次握手的流程以及序列号与应答号都校验无误后,才会完成连接的创建并发送数据。因此TCP的连接创建过程是较为昂贵的。


TCP通过显示的方式确认连接的创建和终止,因此称之为面向连接的协议。由于通信时必须创建连接的开销,TCP的开销高于UDP,性能也低于UDP。但TCP可以保证数据的正确性、顺序性和不可重复性。对于业务应用间的通信,TCP是更合适的选择。


服务化基石之远程通信系列一:通信协议之传输层

Java中使用Socket开发TCP


Socket是用于连通应用层与传输层之间的抽象层接口。Socket翻译为套接字,这个翻译并不有助于理解,因此下文还是统一称其为Socket。它采用IP地址和端口的方式确定一个网络环境中唯一的通信句柄,应用通过句柄向网络中的其他服务发送请求或应答并处理所接收的请求。


Java的网络编程基础是从Socket的TCP编程开始的,TCP是C/S模式,即客户端/服务器模式,这与服务化中的消费者/提供者模式概念等同,只不过服务化中的应用可以既是服务消费者,也可以同时是其它服务的提供者。Java的Socket编程API封装了TCP的三次握手等复杂交互。


通过以下4个步骤可以简单的实现一个基于TCP的Socket编程的正常处理流程:

1.   服务端程序绑定一个未占用的端口用于监听客户端程序的连接请求。


2.   客户端程序向服务端发起连接请求,请求过程中附带自身的主机IP地址和通信端口号。


3.   服务端接受客户端的连接请求。


4.   客户端与服务端通过Socket进行IO通信。


5.   建立通信管道后,可以考虑使用多线程的机制增加服务端的吞吐量。


6.   完成通信,客户端断开与服务端的连接。


服务化基石之远程通信系列一:通信协议之传输层

以下代码是基于TCP的服务端的Java语言的Socket代码示例。由两个类组成,TCPServer用于启动服务器进程和消息分发。TCPServerHandler用于处理消息和业务逻辑。


首先看一下TCPServer的代码示例:


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层

注释位置的相关解释如下:


1.   使用一个变量控制服务端进程的停止。使用volatile关键字修改此变量以保证它在多线程中的可见性。


2.   定义线程池来控制服务端的线程数量。代码示例中使用运行服务器的CPU核数 * 2定义线程池允许运行的最大线程数量。


3.   创建ServerSocket对象并使用8899端口监听客户端的连接请求。


4.   服务端使用一个永久循环来维持运行状态,在处理完一个客户端连接后阻塞,直到下一个客户端的连接到达。这里一个volatile的布尔变量,来控制服务端的优雅关闭。


5.   接受客户端的请求,并完成连接的创建以及获取其输入流和输出流。为了简单起见,示例代码中并未采取多线程的方式处理请求。可以将获取连接之后的处理都放入一个新的线程,让主线程立刻返回,以便加快响应下一个客户端的连接请求。


6.   使用TCPServerHandler类处理业务逻辑。TCPServerHandler是一个实现了Runnable的线程类,将其提交至线程池,以多线程的方式执行IO和具体业务逻辑。使得当前方法可以立刻返回,可以快速接受下个客户端的连接请求。



7.   独立定义一个stop方法。通过改变控制服务端停止循环的标志变量的值来停止服务端进程的运行。


服务化基石之远程通信系列一:通信协议之传输层

      接下来在看一下TCPServerHandler代码示例


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层

注释位置的相关解释如下:


1.   覆盖Runnable的run方法接口,用于实现多线程里面的业务逻辑。


2.   sayHello方法的业务逻辑是读取客户端传输来的消息,并在其消息之前插入”Hello, ”的问候话语。


3.   通过try with resource机制自动释放资源。这里会在try代码块结束之后自动关闭socket、socket的输入流和socket的输出流这3个对象。


4.   读取客户端发送的消息。这里使用了Java的IO API,先使用read(byte [] b)方法,读取消息,并将结果放入buffer的字节数组中。这里定义的字节数组大小是1024个字节。如果传输的消息大于这个buffer的字节数组,则需要反复读取,并在每次读取时将中间结果放入buffer。在read的返回值等于-1时,即表示客户端传输的消息已经结束,可以结束读取。如果read的返回值不为-1,则表示上次读取的字节数。因此在循环读取时,需要使用read(byte [] b, int offset, int length)方法,以避免最后一次读取消息时,并未使用全部的buffer字节数组,导致字节数组中length之后的数据仍然是上次读取的脏数据。


5.  将结果信息发送至客户端。


6.   关闭输出流。由于read方法是阻塞的,在返回结果不是-1时,会阻塞当前线程,继续等待读取数据。只有调用了shutdownOutput方法之后,socket即会关闭输出流,read的返回值才是-1,用于结束读取阻塞。


服务化基石之远程通信系列一:通信协议之传输层

以下代码是基于TCP的客户端的Java语言的Socket代码示例:


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层

注释位置的相关解释如下:


1.   使用TCPServerHandler类处理业务逻辑。TCPServerHandler是一个实现了Runnable的线程类,将其提交至线程池,以多线程的方式执行IO和具体业务逻辑。使得当前方法可以立刻返回,可以快速接受下个客户端的连接请求。


2.   调用业务方法getSayHelloMessage,向服务端发送信息,并将获取到返回结果打印到控制台。


3.   在getSayHelloMessage业务方法中,发送名字到服务端。


4.   关闭输出流,以示意服务端结束读取阻塞。


5.   读取服务端返回的消息。由于服务端在返回消息时同样调用了shutdownOutput方法,因此客户端在读取消息结束后解除阻塞。


服务化基石之远程通信系列一:通信协议之传输层

      运行代码来看一下效果


先运行TCPServer类,程序启动后,在控制台没有任何输出。再运行TCPClient类,可以看到控制台上打印出“Hello, Terry”的字符串之后,TCPClient类的进程即结束。而TCPServer进程仍然在继续运行,可以反复启动TCPClient类,多次向TCPServer发送请求。


使用Java进行开发,屏蔽了三次握手等众多协议上的细节,简化了TCP的开发难度。


UDP

UDP是User Datagram Protocol的缩写,中文名称是用户数据报文协议。它是一种无连接的面向数据报文的协议。


服务化基石之远程通信系列一:通信协议之传输层

概述


UDP是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。UDP不要求通信时保持一个连接,也没有接收方确认收到的报文传输而带来的开销。

虽然UDP可能产生网络丢包,并且无法保证传输的原有顺序,但在性能方面更占优势。它更加适合于允许丢失的业务场景,如系统调用追踪日志、视频会议流等数据可以部分丢弃的场景。


服务化基石之远程通信系列一:通信协议之传输层

Java中使用Socket开发UDP


Java的Socket编程API同样封装了UDP,使其发送端和接收端的开发也变得简单,除了接口不同,处理流程没有区别。

服务化基石之远程通信系列一:通信协议之传输层

以下代码是使用UDP做服务端的Java语言的Socket代码示例:


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层

注释位置的相关解释如下:


1.   与之前的TCPServer示例相同,使用一个变量控制服务端进程的停止。


2.   创建数据报文类socket对象并使用3888端口接收数据。使用try with resource自动释放socket资源。


3.   定义接收数据的DatagramPacket类。缓冲区为1024字节。客户端一次性发送超过1024字节的额外部分将被丢弃。


4.   与之前的TCPServer示例相同,服务端使用一个永久循环来维持运行状态。


5.   接收客户端发送来的数据,在接收到数据之前阻塞。


6.   实现业务逻辑。示例中的业务逻辑是在接收到的数据之前加上“Hello, ”的前缀。


7.   定义发送数据的DatagramPacket类,使用3999端口发送数据。


8.  将数据发送至客户端。


9.   独立定义一个stop方法。通过改变控制服务端停止循环的标志变量的值来停止服务端进程的运行。


服务化基石之远程通信系列一:通信协议之传输层

下面再看一下使用UDP做客户端的Java语言的Socket代码示例:


服务化基石之远程通信系列一:通信协议之传输层
服务化基石之远程通信系列一:通信协议之传输层

客户端代码与服务端代码类似,因此相似的代码就不再解释了。注释位置的相关解释如下:


1.   设置socket的连接超时时间。示例中设置为5000毫秒。


2.   由于UDP的不确定性,不一定每次发送和接收数据都可以成功。因此示例中使用了重试的方式,在接收数据时,等待超时之后即重新发送。接收数据成功则不再重试。示例中的重试次数为3次。


3.   将接收到的数据打印至控制台。


服务化基石之远程通信系列一:通信协议之传输层

      运行代码来看一下效果


与运行之前的TCP代码示例步骤类似。先运行UDPServer类,程序启动后,在控制台没有任何输出。再运行UDPClient类,可以看到控制台上打印出“Hello, Terry”的字符串之后,UDPClient类的进程即结束。而UDPServer进程仍然在继续运行,可以反复启动UDPClient类,多次向UDPServer发送请求。


相比于TCP,UDP更加简单,socket的接收端和发送端以对等的形式存在,无需建立连接。但正如示例所示,开发一个健壮的UDP交互程序,需要考虑报文无法及时发送的场景。


以上内容节选自《云原生分布式架构》一书

 内容简介

互联网架构不断演化,经历了从集中式架构到分布式架构,再到云原生架构的过程。云原生因能解决传统应用升级缓慢、架构臃肿、不能快速迭代等问题而成为未来云端应用的目标。本书首先介绍了架构演化及云原生的概念,让读者对基础概念有一个准确的了解。接着阐述容器调度、服务化、分布式等体系的原理,讲解分布式中间件设计方法。最后辅以实战,以中心化和平台化角度切入,深度揭秘两大开源项目Elastic-Job和Sharding-JDBC的实现

服务化基石之远程通信系列一:通信协议之传输层

尽请期待

服务化基石之远程通信系列一:通信协议之传输层

《云原生分布式架构》2018年  将与您见面

书名尚未完全确定,欢迎您宝贵建议。


感谢大家关注“点亮架构”,欢迎对公众号文章的内容批评指正,如果有其他想要了解的技术问题,也可以留言提出。

‘点亮架构’的火炬,燃烧云原生‘

服务化基石之远程通信系列一:通信协议之传输层

欢迎转发

赞(0) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » 服务化基石之远程通信系列一:通信协议之传输层
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏