Java实现系统间的通信概览
我们知道,所谓分布式,无非就是“将一个系统拆分成多个子系统并散布到不同设备”的过程而已。在微服务的大潮之中, 我们把系统拆分成了多个服务,根据需要部署在多个机器上,这些服务非常灵活,可以随着访问量弹性扩展。本质上而言,实现一个分布式系统,最核心的部分无非有两点:
如何拆分——可以有很多方式,核心依据一是业务需求,二是成本限制。这是实践中构建分布式系统时最主要的设计依据。
如何连接——光把系统拆开成各个子系统还不够,关键是拆开后的各个子系统之间还要能通信,因此涉及通信协议设计的问题,需要考虑的因素很多,好消息是这部分其实成熟方案很多。
分布式系统并非灵丹妙药,解决问题的关键还是看你对问题本身的了解。通常我们需要使用分布式的常见理由是:
为了性能扩展——系统负载高,单台机器无法承载,希望通过使用多台机器来提高系统的负载能力。
为了增强可靠性——软件不是完美的,网络不是完美的,甚至机器本身也不可能是完美的,随时可能会出错,为了避免故障,需要将业务分散开保留一定的冗余度。
本篇要讲的是分布式应用中解决“如何连接”的问题,即Java是如何实现系统间的通信的。先上一张总图:
上图中,我们看到图片左边的【网络通信】,是由协议和网络IO组成。协议如TCP/IP等在上一篇文章中已经介绍过,多出的Multicast(组播)此处也不再延伸介绍,有需要的同学另外自行了解即可。上一篇文章在介绍传输层的TCP协议时,已经提到了“TCP提供全双工通信,会话双方都可以同时接收和发送数据。都设有接收缓存和发送缓存,用来临时存放双向通信的数据”。发送缓存也就是写缓存,接收缓存也就是读缓存。在客户端与服务器经过三次握手建立连接后,在二者之间就相当于打开了一条可以互相传送数据的道路,道路的两端就是各自的读写缓存和我们所说的套接字Socket,每一个socket都有一个输出流和一个输入流。这种跨越网络的数据IO流,就是我们说的网络IO。然后可以看到网络IO还分为了BIO、NIO和AIO,这个我们可以先不管,后面我会再细说。所以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框架。
网友评论