目录
1.计算机网络
2.TCP/IP协议
3.UDP协议
4.RPC框架
1.计算机网络
从资源共享的角度上来说,计算机网络就是以能够相互共享资源的方式互连起来的自治计算机系统的集合。网络建立的主要目的是实现计算机资源的共享。
目前来说,计算机网络分为两大模型:OSI 7层模型 及 TCP/IP 4层模型,相互之间的对应关系如下:
- 应用层:网络服务与最终用户的一个接口
- 表示层:数据的表示、安全、压缩
- 会话层:建立、管理、中止会话
- 传输层:定义传输数据的协议端口号,以及流控和差错校验
- 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择
- 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能
- 物理层:建立、维护、断开物理连接
目前来说,OSI七层模型只是理论上的理想模型,并没有实现;TCP/IP模型是应用更广泛的计算机网络模型。
每一层使用的协议如下:
OSI模型 | TCP/IP模型 | 协议 |
应用层 | 应用层 | HHTP、FTP、SMTP、Telnet、DNS等 |
表示层 | ||
会话层 | ||
传输层 | 传输层 | TCP、UDP |
网络层 | 网络层 | IP、ARP、RARP等 |
数据链路层 | 网络接口层 | IEEE802.1A、802.2等 |
物理层 |
在计算机网络中,我们可以通过IP定位唯一一部主机,java网络编程就是在次基础上,主要依靠的是传输层的协议进行网络通信,使用的TCP/IP模型。
2.TCP/IP协议
TCP :传输控制协议 ,是可靠的协议 (安全性高,速度慢)。
即TCP协议需要通信双方建立连接才可以进行通信;当通信结束后,则关闭连接。
三次握手(建立连接):
- 第一次握手:客户端发送连接请求(SYN报文,将seq置为x),发送完毕后客户端进入SYN_SEND(同步已发送)状态。
- 第二次握手: 服务器收到SYN报文,向客户端发送SYN+ACK报文, seq置为y,ack置为x+1(sea值+1)。发送后客户端进入SYN_RCVD状态
- 第三次握手:客户端收到SYN+ACK报文后,明白两方收发都没有问题。但还需要向服务器发送ACK报文,ack置为y+1,seq置为x+1。
客户端与服务端建立连接后,即可进行数据通信。
四次挥手(关闭连接):
- 第一次挥手:客户端发送连接释放报文,并停止再发送数据,主动关闭TCP连接。 FIN=1,seq=u 发送完后进入FIN-WAIT-1 状态
- 第二次挥手:服务器接收到后发出确认,ACK=1,seq=v,ack=u+1然后进入CLOSE_WAIT状态。
- 第三次挥手:数据传输完后,服务器发出连接释放的报文。FIN=1,ACK=1,seq=w,ack=u+1, 发送完后进入LAST-ACK(最后确认)状态。
- 第四次挥手:客户端收到后,将发出确认,ACK=1,ack=w+1,seq=u+1,发送完后将进入TIME-WAIT状态
TCP/IP只是一个协议栈,就像操作系统的运行机制一样,需要具体的实现,同时还要提供对外的编程接口,这个接口就是Socket 。
java网络编程也是通过Socket类进行实现:
(1)TCP服务端:
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//tcp服务端
class TCPServer {
public static void main(String[] args) {
//创建TCP服务端,开放8080端口供客户端连接
TCPServer server = new TCPServer(8080);
//监听消息
server.getFile();
}
private ServerSocket serverSocket;
//构造方法,建立连接
//阻塞式连接,即运行后不会停止,直至客户端连接调用逻辑后关闭连接
public TCPServer(int port) {
try {
this.serverSocket = new ServerSocket(port);
} catch (Exception e) {
e.printStackTrace();
}
}
//接受客户端发送的字符
public void getMessage() {
Socket accept = null;
InputStream inputStream = null;
ByteOutputStream outputStream = null;
try {
while (true) {
//2.监听消息,等待客户端连接
accept = this.serverSocket.accept();
//3.读取客户端消息
inputStream = accept.getInputStream();
outputStream = new ByteOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
System.out.println("收到消息:" + outputStream.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭流
assert outputStream != null;
outputStream.close();
inputStream.close();
accept.close();
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//接受客户端发送的文件流
public void getFile(){
try {
//2.监听消息,等待客户端连接
Socket accept = this.serverSocket.accept();
InputStream inputStream = accept.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream(new File("accept.jpg"));
byte[] bytes = new byte[1024];
int len;
while ((len=inputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,len);
}
fileOutputStream.close();
inputStream.close();
accept.close();
this.serverSocket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
(2)TCP客户端:
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
//TCP客户端
class TCPClient{
public static void main(String[] args) {
//发送消息
TCPClient client = new TCPClient("127.0.0.1",8080);
client.sendFile();
}
private Socket socket;
public TCPClient(String ip, int port){
try{
//1.获取服务端地址
InetAddress inetAddress = InetAddress.getByName(ip);
//2.创建连接
this.socket = new Socket(inetAddress,port);
}catch (Exception e){
e.printStackTrace();
}
}
public void sendMessage() {
try{
//3.发送消息
OutputStream outputStream = this.socket.getOutputStream();
outputStream.write("测试消息".getBytes(StandardCharsets.UTF_8));
//4.关闭流
outputStream.close();
this.socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
public void sendFile(){
try {
OutputStream os = this.socket.getOutputStream();
FileInputStream fis = new FileInputStream(
new File("你的文件路径"));
byte[] buffer = new byte[1024];
int len;
while ((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//文件传输完,告诉服务端
socket.shutdownOutput();
//关闭流
fis.close();
os.close();
this.socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
3.UDP协议
UDP:用户数据报协议 ,是不可靠的协议(安全性不高,速度快.
UDP是不可靠的传输协议,即双方不需要建立连接,更类似于短信通信。
(1)服务端(接受):
public class TalkReceive implements Runnable{
private DatagramSocket socket = null;
private int port;
private String msgFrom;
public TalkReceive(int port, String msgFrom){
this.port=port;
this.msgFrom=msgFrom;
try {
socket = new DatagramSocket(port);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true){
//接收数据,阻塞式接受
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
System.out.println(msgFrom+":"+
new String(packet.getData(),0, packet.getLength()));
//bye,退出循环
if ("bye".equals(new String(buffer,0, packet.getLength()))){
break;
}
}
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
(2)客户端(发送):
public class TalkSender implements Runnable{
//1.建立socket
private DatagramSocket socket = null;
private final String toIp;
private final int toPort;
public TalkSender(int fromPort, String toIp, int toPort){
this.toIp=toIp;
this.toPort=toPort;
try{
socket=new DatagramSocket(fromPort);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
//2.建立包
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String message = null;
try {
while(true){
message = reader.readLine();
//参数:发送体,发送起始长度,结束长度,目标地址,端口号
DatagramPacket packet = new DatagramPacket(message.getBytes(StandardCharsets.UTF_8),
0, message.getBytes(StandardCharsets.UTF_8).length,
new InetSocketAddress(this.toIp,this.toPort));
//发送
socket.send(packet);
if ("bye".equals(message)){
break;
}
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3)实现通信:
public class teacher {
public static void main(String[] args) {
new Thread(new TalkSender(5555,"127.0.0.1",8888)).start();
new Thread(new TalkReceive(7777,"student")).start();
}
}
public class student {
public static void main(String[] args) {
new Thread(new TalkSender(6666,"127.0.0.1",7777)).start();
new Thread(new TalkReceive(8888,"teacher")).start();
}
}
即可实现双方通信聊天。
4.RPC框架
是什么:RPC(Remote Procedure Call)即远程过程调用。区别于本地调用,RPC是指调用远端机器(或是其他微服务)的方法,且不需要关心底层的调用细节,如网络协议和传输协议等,只需要关注调用方法的参数。
为什么需要:
随着业务的发展,服务功能持续迭代,单体应用出现性能瓶颈,因此需要考虑对服务进行拆分,根据业务功能划分为不同的模块,这就是微服务。
不同的微服务之间不是毫无关系的,微服务整体构成一个系统,所以相互之间需要进行通信完成功能,保证服务的整体性能。一个服务拆分为不同的模块,或者单体应用拆分为多个微服务时,此时便需要RPC出场了,不同模块及不同服务间都需要RPC才能完成通信。
而基于Restful的远程过程调用也可实现以上功能,但由于Restful主要基于HTTP,封装的数据量更多所以数据传输量更大效率比较低,因此RPC被提出了。
可以说RPC是分布式系统架构或者微服务架构必不可少的实现手段。
怎么实现:
实现RPC首先需要服务注册发现:由于调用的是远端对象,因此需要可以定位到调用的服务器地址以及调用的具体方法的进程,常用的服务注册中心有zookeeper、nacos等。
同时,网络传输的是二进制数据,RPC需要支持将函数参数(如基本类型int、long、map以及自定义类型等)等传输至远程方法才可以进行调用,所以需要对其进行序列化及反序列化,保证传输参数及返回结果的可用性及可读性。
最后,RPC的核心在于网络通信,它主要是基于TCP协议进行实现的。
如上图所示,一个完整的RPC架构主要分为三个部分:客户端(调用者)、服务注册中心、服务端(被调用者),一次调用的过程一般如下:
- 服务端通过服务注册中心进行注册,暴露自身。
- 客户端发起远程调用,通过服务发现到服务注册中心找到对应的调用服务地址,然后根据RPC协议创立TCP连接,发起远程调用。
- 调用成功后,得到返回结果,关闭连接,调用结束。
所以,一个完整的商用 RPC 框架最核心的基本就是三个:服务寻址、数据编码、网络传输。
常用的RPC框架:
(1)gRPC
gRPC是一个高性能、通用的开源RPC框架,其由Google2015年主要面向移动应用开发并基于HTTP/2标准协议而设计,基于ProtoBuf序列化协议开发,且支持众多语言开发。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链式功能,从而有助于节省带宽、降低TCP连接次数、节省CPU使用、电池寿命。
(2)Dubbo
Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。 Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,Dubbo自2011年开源后,已被许多非阿里系公司使用,目前Dubbo已经交给Apache基金会维护。