分布式专题(2)- 分布式 Java通信

Java实现分布式系统间通信的方式

本篇一句话总结:Java实现分布式通信,可以基于Java API、开源框架和远程通信技术三种方式实现。


正文开始:

        通过上一篇文章《分布式专题(1)- 计算机网络》我们知道了计算机之间之所以能够进行通信的原理。如果对计算机网络还不是很清楚的同学可以点过去看一下。不过小兵写上一篇时写得比较细,得要花上一点时间才能看完,如果已经知道大概内容了的小伙伴,小兵这里推荐另一篇博客用于快速复习。传送门:《计算机之间是如何进行通信的?》

        咱们中国武学讲究内功心法和招式变术。招式变术是千变万化的,而内功心法则稳定而绵延不绝。内功心法的深度决定了可以学习的招式变术的上限高度。学习编程亦如此道:具体的技术是招式变术,而计算机原理和操作系统就是内功心法。习得内功心法,才能更好地掌握各种高阶招式。看我们的虚竹同学,一开始时是什么招术都不会的,就因为有无崖子七十年的内力,所以在天山童姥稍微指点下,就能把追杀他的人全部反杀,一跃成为江湖顶尖高手之一,可见内功的重要性。我们做开发的知道,技术更新的频率是如此之快,上半年用的东西可能下半年就有新的技术取代了,我们永远不可能一直走在技术的最前沿。但如果我们的内功足够深厚,只要稍加观摩,那基本上也能把它的原理猜得七七八八,掌握它自然就水到渠成了。

Java实现系统间的通信概览

        好了,题外话不多说,正文开始。我们知道,所谓分布式,无非就是“将一个系统拆分成多个子系统并散布到不同设备”的过程而已。在微服务的大潮之中, 我们把系统拆分成了多个服务,根据需要部署在多个机器上,这些服务非常灵活,可以随着访问量弹性扩展。本质上而言,实现一个分布式系统,最核心的部分无非有两点:

  1. 如何拆分——可以有很多方式,核心依据一是业务需求,二是成本限制。这是实践中构建分布式系统时最主要的设计依据。
  2. 如何连接——光把系统拆开成各个子系统还不够,关键是拆开后的各个子系统之间还要能通信,因此涉及通信协议设计的问题,需要考虑的因素很多,好消息是这部分其实成熟方案很多。

       分布式系统并非灵丹妙药,解决问题的关键还是看你对问题本身的了解。通常我们需要使用分布式的常见理由是:

  • 为了性能扩展——系统负载高,单台机器无法承载,希望通过使用多台机器来提高系统的负载能力。
  • 为了增强可靠性——软件不是完美的,网络不是完美的,甚至机器本身也不可能是完美的,随时可能会出错,为了避免故障,需要将业务分散开保留一定的冗余度。

        本篇要讲的是分布式应用中解决“如何连接”的问题,即Java是如何实现系统间的通信的。先上一张总图:

Java实现系统间的通信

上图中,我们看到图片左边的【网络通信】,是由协议和网络IO组成。协议如TCP/IP等在上一篇文章中已经介绍过,多出的Multicast(组播)此处也不再延伸介绍,有需要的同学另外自行了解即可。上一篇文章在介绍传输层的TCP协议时,已经提到了“TCP提供全双工通信,会话双方都可以同时接收和发送数据。都设有接收缓存和发送缓存,用来临时存放双向通信的数据”。发送缓存也就是写缓存,接收缓存也就是读缓存。在客户端与服务器经过三次握手建立连接后,在二者之间就相当于打开了一条可以互相传送数据的道路,道路的两端就是各自的读写缓存和我们所说的套接字Socket,每一个socket都有一个输出流和一个输入流。这种跨越网络的数据IO流,就是我们说的网络IO。然后可以看到网络IO还分为了BIO、NIO和AIO,这个我们可以先不管,后面我会再细说。所以TCP连接差不多就是下图这个样子。

TCP协议通信过程

在了解了Socket和网络IO的含义之后,我们看回第一张图的右边,可以看到Java实现系统间的通信方式有基于Java API、基于开源框架、基于远程通信技术下面,我们用Java代码来一起实现一下这几种方式。

Socket:socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)。可以看做是对TCP/IP协议的封装,它把复杂的TCP/IP协议族隐藏在Socket接口后面,它的出现只是使得程序员更方便地使用TCP/IP协议栈而已。

基于Java API

java.net 包中的 API 包含有网络编程相关的类和接口。java.net 包中能够找到对TCP协议、UDP协议、Multicast协议的支持。我们仍以基于TCP协议的网络编程为例。

在编程开始前,我们再次简单回顾一下计算机网络中的传输层和TCP协议。

  • 传输层为应用进程之间提供端口到端口的通信
  • TCP提供全双工通信,会话双方都可以同时接收和发送数据。

(在看API的具体实现之前,思考一个有意思的问题:如果是交给你去实现客户端与服务器的通信,你会设计多少个对象?如何设计它们的关系?如何做到面向对象设计?多看,多想,多换位思考,如果是你的话,你怎么处理,这是对提高自己水平很有裨益的事,无论是做人还是做事。)

官方文档提到:以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
  • 连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

      上述流程有空就多看几遍,我们后面讲的所有通信都是基于上述流程。各种技术和框架不过是对这些流程不断封装、抽象、扩展而已,但是主流程仍是不变的。

现在,打开我们的IDEA或者Eclipse,按照API中的实现步骤,一起来实现下面的小目标。

(终于要回到我们熟悉的代码部分了,Code Time Begin !)

小目标:对基于Java API的网络编程有初步的了解。具体需求如下:

1)从客户端把“Hello, I am xxx. Here is Client.”这条消息传送给服务端;

2)从服务端读取该消息,并给客户端返回响应消息:“Hello, xxx, nice to meet you! Here is Server.”

我们可以按照以下步骤实现上述需求:

第一步:建项目

我们先新建一个项目distributed,再建一个名为mysocket的包。为了以后方便添加Jar包,我们建的是maven项目。

第二步:建服务端类

然后建一个服务端类:HelloServer,代码如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloServer {
	// 选择一个端口作为服务端端口
	private static int port = 8888;

	public static void main(String[] args) throws Exception {
		// 创建ServerSocket对象,相当于在服务端(本机)打开一个监听
		ServerSocket serverSocket = new ServerSocket(port);
		System.out.println("开始监听端口:" + port + "...");
		// accept方法阻塞等待客户端连接,连接成功后得到socket对象
		Socket socket = serverSocket.accept();
		// 获取服务端socket的输入流,客户端通过这个输入流给服务端传递消息
		DataInputStream in = new DataInputStream(socket.getInputStream());
		// 通过服务端socket的输入流,输出客户端发送过来的消息
		System.out.println("客户端消息:"+in.readUTF());
		// 获取服务端socket的输出流,服务端端通过这个输出流给客户端传递消息
		DataOutputStream out = new DataOutputStream(socket.getOutputStream());
		// 通过服务端socket的输出流,给客户端发送消息
		out.writeUTF("Hello,jvxb, nice to meet you!Here is Server。");
		// 关闭服务端socket。
		socket.close();
		// 关闭监听
		serverSocket.close();
	}

}

第三步:建客户端类

然后建一个客户端类:HelloClient,代码如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloClient {

	// 需连接的服务端IP或域名,此例中本机即为服务端。一般都是通过配置文件来设置。
	private static String serverName = "127.0.0.1";
	// 需连接的服务端端口
	private static int port = 8888;

	public static void main(String[] args) throws Exception {
		// 通过指定服务端IP、服务端端口,连接到服务端,连接成功后获得客户端socket.
		Socket clientSocket = new Socket(serverName, port);
		// 通过客户端socket,获得客户端输出流。
		DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
		// 通过客户端输出流,向服务端发送消息。
		out.writeUTF("Hello,I am jvxb! Here is Client.");
		// 通过客户端输出流,读取服务端发送过来的消息。
		DataInputStream in = new DataInputStream(clientSocket.getInputStream());
		// 输出服务端发送过来的消息
		System.out.println("服务器响应: " + in.readUTF());
		// 关闭客户端socket
		clientSocket.close();
	}
}

第四步:测试

1)运行服务端类

2)运行客户端类

3)查看输出结果

可以看到结果如下:

通过以上的例子我们可以看到,只需要简单的几行Java代码,通过Java API我们就能够实现基于TCP协议的客户端/服务端通信。同理,通过DatagramSocket对象也能很快速地实现基于UDP协议的客户端/服务端通信,此处不再展开。当然,我们上面举的例子只是最基础的。一般来说服务端不会只与一个客户端连接,服务端需要监听多个客户端连接的话,就得让accept()方法在while中持续循环,所以服务端的代码一般都是配合多线程来使用,传统做法是一个客户端连接过来就开一个线程去单独处理,这种处理是比较简单容易实现,但很明显客户端连接一多,性能方面就跟不上了,因为光是线程的切换开销就挺大的,更不用说每个线程都会占用挺大的资源。那要怎么解决性能的问题呢?功力深厚者,可以自己去设计出自己的一套东西去解决,像小兵我这种水平未到家的,我觉得用人家东西也挺不错的。。比如我们可以直接使用Netty框架。

思考:如何利用socket传输对象?

基于开源框架

可以看到有两个比较著名的网络通讯框架Mina和Netty。这两个开源框架都能提供高性能IO,两个框架也都是韩国人Trustin Lee的大作,Netty比Mina新,且性能更好,Mina据说已经停止维护了,所以咱们还是重点关注Netty。Netty是目前最流行的Java开源NIO框架,连咱们淘宝的Dubbo框架都用到了Netty,能抗得住亿万级流量的冲击,证明这个框架底色还是很足的,所以我们还是有必要学习一下Netty。看看同样是基于TCP协议、网络IO,相比核心 Java API它怎么就有着更好的吞吐量、较低的延时,资源消耗居然还更少,为什么Netty同学就能做到如此优秀?

在学习Netty之前,我们首先要了解几个概念,即BIO、NIO、AIO,及序列化

阻塞、非阻塞、同步、异步

同步:如果有多个任务或者事件要发生,这些任务或者事件必须逐个地进行,一个事件或者任务的执行会导致整个流程的暂时等待,这些事件没有办法并发地执行;

异步:如果有多个任务或者事件发生,这些事件可以并发地执行,一个事件或者任务的执行不会导致整个流程的暂时等待。

举个简单的例子,假如有一个任务包括两个子任务A和B,对于同步来说,当A在执行的过程中,B只有等待,直至A执行完毕,B才能执行;而对于异步就是A和B可以并发地执行,B不必等待A执行完毕之后再执行,这样就不会由于A的执行导致整个任务的暂时等待。
        
阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值