概述
通过通信线路(有线或无线)可以把不同地理位置且相互独立的计算机连同其外部设备连接起来,组成计算机网络。在操作系统、网络管理软件及网络通信协议的管理和协调下,可以实现计算机之间的资源共享和信息的传递。
网络编程是指用来实现网络互联的不同计算机上运行的程序间可以进行数据交换。对我们来说即如何用编程语言 java 实现计算机网络中不同计算机之间的通信。
1.网络通信三要素
IP地址
网络中计算机的唯一标识;
32bit(4 字节),一般用“点分十进制”表示,如 192.168.1.158; IP 地址=网络地址+主机地址 可分类:
A 类:第 1 个 8 位表示网络地址。剩下的 3 个 8 位表示主机地址 (主要留给ZF或者大型企业)
B 类:前 2 个 8 位表示网络地址。剩下的 2 个 8 位表示主机地址 (主要分配给中等规模的公司)
C 类:前 3 个 8 位表示网络地址。剩下的 1 个 8 位表示主机地址 (分配给小公司或者个人)
D 类地址用于在 IP 网络中的组播
E 类地址保留作研究之用。
Java 编程中可使用 InetAddress 类来操纵 IP 地址
public static void main(String[] args) throws UnknownHostException {
InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost.getHostAddress()); System.out.println(localHost.getHostName());
}
端口号
用于标识进程的逻辑地址,不同进程的标识;
有效端口:0-65535,其中 0-1024 系统使用或保留端口。
传输协议
通讯的规则
常见协议: UDP(用户数据报协议)、TCP(传输控制协议)
2. 网络模型
计算机网络之间以何种规则进行通信,就是网络模型所研究的问题。 网络模型一般是指 OSI 七层参考模型和 TCP/IP 五层参考模型。
每一层实现各自的功能和协议,并且都为上一层提供业务功能。为了提供这种业务功能,下一层将上一层中的数据并入到本层的数据域中,然后通过加入报头或报尾来实现该层业务功能,该过程叫做数据封装。用户的数据要经过一次次包装,最后转化成可以在网络上传输的信号,发送到网络上。当到达目标计算机后,再执行相反的数据拆包过程。
物理层:
主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。
主要作用是将数据最终编码为用 0、1 标识的比特流,通过物理介质传输。 这一层的数据叫做比特。
数据链路层:
主要将接收到的数据进行 MAC 地址(网卡地址)的封装与解封装。 常把这一层的数据叫做帧。这一层常工作的设备是交换机。
网络层:
主要将接收到的数据进行 IP 地址的封装与解封装。
常把这一层的数据叫做数据包。这一层设备是路由器。
传输层:
定义了一些数据传输的协议和端口号。
主要将接收的数据进行分段和传输,到达目的地址后在进行重组。 常把这一层的数据叫做段。
会话层:
通过传输层建立数据传输的通路。
主要在系统之间发起会话或者接收会话请求。
表示层:
主要进行对接收数据的解释、加密与解密、压缩与解压缩。
确保一个系统的应用层发送的数据能被另一个系统的应用层识别。
应用层:
主要是为一些终端应用程序提供服务。直接面对着用户的
3.Socket 机制
3.1. Socket 概述
Socket,又称为套接字,用于描述 IP 地址和端口。应用程序通常通过 socket向网络发出请求或者应答网络请求。Socket 就是为网络编程提供的一种机制:
通信两端都有 socket;
网络通信其实就是 socket 之间的通信;
数据在两个 socket 之间通过 IO 传输。
网络编程也称作为 Socket 编程,套接字编程。
Socket 通信是 Client/Server 模型。
3.2. 基于UDP 协议的Socket 通信
核心类:DatagramSocket
发送端:
// 创建发送端 Socket 服务对象
DatagramSocket dSocket = new DatagramSocket();
// 创建数据,打包数据
String message = "hello ,are u UDP ?";
byte[] bys = message.getBytes();
int length = bys.length;
InetAddress address = InetAddress.getByName("localhost");
int port = 12621;
DatagramPacket dPacket = new DatagramPacket(bys, length, address, port);
// 发送数据
dSocket.send(dPacket);
// 资源释放
dSocket.close();
接收端:
//创建接收端Socket 服务对象
DatagramSocket dSocket = new DatagramSocket(12621);
//创建数据包(接收容器)
byte[] bys = new byte[1024];
DatagramPacket dPacket = new DatagramPacket(bys, bys.length);
//调用接收方法
dSocket.receive(dPacket);
//数据包解析
InetAddress address = dPacket.getAddress();
String hostAddress = address.getHostAddress();
byte[] data = dPacket.getData();
String message = new String(data);
System.out.println(hostAddress+"*********:"+message);
//资源释放
dSocket.close();
3.3. 基于TCP 协议的Socket 通信
服务端
核心 API:ServerSocket
流程:
创建 ServerSocket 服务,然后绑定在服务器的 IP 地址和端口
监听连接请求
接受请求,建立了 TCP 连接
获取输入流读取数据,并显示
释放资源
//建立服务端socket 服务,并且监听一个端口
ServerSocket ss = new ServerSocket(13131);
//监听连接
Socket s = ss.accept();
//获取输入流,读取数据
InputStream inputStream = s.getInputStream();
byte[] bys = new byte[1024];
int len = inputStream.read(bys);
System.out.println(new String(bys, 0, len));
//关闭客户端
s.close();
//关闭服务端,一般服务端不关闭 ss.close();
客户端
核心 API:Socket
流程:
创建客户端 socket 对象
向服务端请求建立 tcp 连接
从 tcp 连接中获取输出流,写数据 释放资源
//创建客户端的socket 服务,指定目的主机和端口
Socket s = new Socket("127.0.0.1", 13131);
//通过socket 获取输出流,写数据
OutputStream outputStream = s.getOutputStream(); outputStream.write("hello ,this is tcp?".getBytes());
//释放资源
s.close();
4. IO 通信模型
网络通信的本质是网络间的数据 IO。只要有 IO,就会有阻塞或非阻塞的问题,无论这个 IO 是网络的,还是硬盘的。原因在于程序是运行在系统之上的,任何形式的IO 操作发起都需要系统内核的支持。
4.1. BIO(阻塞模式)
BIO 即 blocking IO,是一种阻塞式的 IO。
jdk1.4 版本之前 Socket 即 BIO 模式。
BIO 的问题在于 accept()、read()的操作点都是被阻塞的。
服务器线程发起一个 accept 动作,询问操作系统是否有新的 socket 信息从端口 X 发送过来。注意,是询问操作系统。如果操作系统没有发现有 socket 从指定的端口X 来,那么操作系统就会等待。这样 serverSocket.accept()方法就会一直等待。这就是为什么 accept()方法为什么会阻塞。
如果想让 BIO 同时处理多个客户端请求,就必须使用多线程,即每次 accept阻塞等待来自客户端请求,一旦收到连接请求就建立通信,同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续 accept 等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理。
4.2. NIO(非阻塞模式)
NIO 即 non-blocking IO,是一种非阻塞式的 IO。jdk1.4 之后提供。
NIO 三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。 Buffer:容器对象,包含一些要写入或者读出的数据。在 NIO 库,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。
Channel:通道对象,对数据的读取和写入要通过 Channel,它就像水管一样。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。 Channel不会直接处理字节数据,而是通过 Buffer 对象来处理数据。
Selector:多路复用器,选择器。提供选择已经就绪的任务的能力。Selector会不断轮询注册在其上的 Channel,如果某个 Channel 上面发生读或者写事件,这个Channel 就处于就绪状态,会被 Selector 轮询出来,进行后续的 I/O 操作。这样服务器只需要一两个线程就可以进行多客户端通信。
4.3. 阻塞/非阻塞、同步/非同步
阻塞 IO 和非阻塞 IO 这两个概念是程序级别的。主要描述的是程序请求操作系统IO 操作后,如果 IO 资源没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行(并且使用线程一直轮询,直到有 IO 资源准备好了)。
同步 IO 和非同步 IO,这两个概念是操作系统级别的。主要描述的是操作系统在收到程序请求 IO 操作后,如果 IO 资源没有准备好,该如何响应程序的问题:前者不响应,直到 IO 资源准备好以后;后者返回一个标记(好让程序和自己知道以后的数据往哪里通知),当 IO 资源准备好以后,再用事件机制返回给程序。
5. RPC
5.1. 什么是RPC
RPC(Remote Procedure Call Protocol)远程过程调用协议。
通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个过程或函数,就像调用本地应用程序中的一样。
正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
5.2. RPC 主要特质
RPC 是协议:协议意味着规范。目前典型的 RPC 实现包括:Dubbo、Thrift、 Hetty等。但这些实现往往都会附加其他重要功能,例如 Dubbo 还包括了服务管理、访问权限管理等功能。
网络协议和网络 IO 模型对其透明:既然 RPC 的客户端认为自己是在调用本地对象。那么传输层使用的是 TCP/UDP 还是 HTTP 协议,又或者是一些其他的网络协议它就不需要关心了。既然网络协议对其透明,那么调用过程中,使用的是哪一种网络IO 模型调用者也不需要关心。
信息格式对其透明:远程调用过程中,需要传递一些参数,并且会返回一个调用结果。至于这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
跨语言能力:对于调用方来说,不知道也无需知道远程的程序使用的是什么语言运行的,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。
5.3. RPC 原理
实现 RPC 的程序包括 5 个部分:User、User-stub、RPCRuntime、Server-stub、Server。
user 就是发起 RPC 调用的 client ,当 user 想发起一个远程调用时,它实际是通过本地调用 user-stub。 user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例。远端RPCRuntim 实例收到请求后交给 server-stub 进行解码后发起本地端调用,调用结果再返回给 user 端。
stub:为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象。