摘要:本文学习了网络编程的相关知识,包括TCP网络编程和UDP网络编程,以及如何使用RMI远程调用。
环境
Windows 10 企业版 LTSC 21H2 Java 1.8
1 基础 1.1 定义 网络编程就是在两个或两个以上的设备之间传输数据。程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据,这个就是狭义的网络编程范畴。
网络编程的基本模型就是客户机到服务器模型,简单的说就是两个进程之间相互通讯,然后其中一个提供固定位置,而另一个则需要知道这个固定位置,就能建立两者之间的联系,然后完成数据的通讯就可以了。提供固定位置的通常称为服务器,建立联系的通常称为客户端。
Java从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序,联网的底层细节被隐藏在安装系统里,由JVM进行控制。
1.2 网络通信 网络通信中的两个要素
IP地址和端口号:用于在网络上找到主机地址和主机上的特定应用。
网络协议,用于可靠高效地进行数据传输,有两套参考模型:
OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广。
TCP/IP参考模型(或TCP/IP协议):事实上的国际标准,还可以细分为四层和五层。
1.3.1 IP地址 为了解决如何在网络上找到主机地址的问题,引入了IP地址和域名(Domain Name)的概念。
使用IP地址能够在网络上唯一标识网络设备,但由于IP地址不容易记忆,又创造用于映射IP地址的域名,一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。
在实际传输数据前需要将域名转换为IP地址,实现这种功能的服务器称为DNS服务器,也就是域名解析。
1.3.2 端口号 为了解决如何在主机上找到特定应用的问题,引入了端口(Port)的概念。
使用端口能够在主机中唯一标识应用,在主机上可以通过端口区分发送给每个应用的数据,实现了多个网络程序在共同的主机上运行,并且不会互相干扰。
1.3.3 网络协议 有了IP地址和端口号以后,在进行网络通讯交换时,就可以通过IP地址查找到主机,然后通过端口标识程序,这样就可以进行网络数据的交换了。
为了解决如何保证数据交换的安全可靠,引入了网络协议(Protocol)的概念。
网络协议用于在实际进行数据交换时规定数据的格式,避免格式不同导致的数据识别错误等问题。
常见的网络模型对比如下:
OSI七层网络模型
TCP/IP四层网络模型
TCP/IP五层网络模型
网络协议
工作设备
应用层
应用层
应用层
HTTP HTTPS FTP SMTP POP3
计算机及应用
表示层
会话层
传输层
传输层
传输层
TCP UDP
四层交换机 四层路由器
网络层
网络层
网络层
IP ICMP ARP RARP
三层交换机 路由器 网关
数据链路层
网络接口层
数据链路层
Ethernet PPP
交换机 网桥
物理层
物理层
USB
中继器 集线器
TCP和UDP比较:
特性
TCP
UDP
是否连接
面向连接。
无连接。
是否可靠
可靠传输,使用流量控制和拥塞控制。
不可靠传输,不使用流量控制和拥塞控制。
连接对象个数
只能是一对一通信。
支持一对一,一对多,多对一和多对多交互通信。
传输方式
面向字节流。
面向报文。
首部开销
首部最小20字节,最大60字节。
首部开销小,仅8字节。
适用场景
适用于要求可靠传输的应用,例如文件传输。
适用于实时应用,例如IP电话、视频会议。
1.3 统一资源定位符 统一资源定位符(URL,Uniform Resource Locator),表示网络上某一资源的地址,通过URL可以访问网络上的各种资源。
URL主要由协议和资组成,语法:
java
1.3.1 URL 构造方法:
java 1 2 3 4 public URL (String spec) ;public URL (String protocol, String host, int port, String file) ;
常用方法:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public String getProtocol () ;public String getHost () ;public int getPort () ;public String getFile () ;public String getRef () ;public String getPath () ;public final InputStream openStream () ;public URLConnection openConnection () ;
示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) { try { URL url = new URL ("http://www.gamelan.com:80/Gamelan/network.html#BOTTOM" ); System.out.println(url.getProtocol()); System.out.println(url.getHost()); System.out.println(url.getPort()); System.out.println(url.getFile()); System.out.println(url.getRef()); System.out.println(url.getPath()); } catch (MalformedURLException e) { e.printStackTrace(); } }
结果:
java 1 2 3 4 5 6 http www.gamelan.com 80 /Gamelan/network.html BOTTOM /Gamelan/network.html
1.3.2 URLConnection URLConnection表示到URL所引用的远程对象的连接。当与URL建立连接时,首先要生成对应的URLConnection对象。如果连接过程失败,将产生IOException。
获取方法:
java 1 2 public URLConnection openConnection () ;
常用方法:
java 1 2 3 4 public InputStream getInputStream () ;public OutputStream getOutputStream () ;
2 TCP网络编程 2.1 常用类 2.1.1 InetAddress 此类表示互联网协议(IP)地址。
常用方法:
java 1 2 3 4 5 6 7 8 public static InetAddress getByName (String host) ;public static InetAddress getLocalHost () ;public String getHostName () ;public String getHostAddress () ;
示例:
java 1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { try { InetAddress baidu = InetAddress.getByName("www.baidu.com" ); InetAddress local = InetAddress.getLocalHost(); System.out.println(baidu.getHostName()); System.out.println(local.getHostAddress()); } catch (UnknownHostException e) { e.printStackTrace(); } }
结果:
java 1 2 www.baidu.com 192.168 .1 .109
2.1.2 InetSocketAddress 此类实现IP套接字地址(IP地址和端口号)。
构造方法:
java 1 2 3 4 5 6 public InetSocketAddress (int port) ;public InetSocketAddress (InetAddress addr, int port) ;public InetSocketAddress (String hostname, int port) ;
常用方法:
java 1 2 3 4 5 6 7 8 public final InetAddress getAddress () ;public final String getHostName () ;public final int getPort () ;public String toString () ;
示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) { try { InetSocketAddress local = new InetSocketAddress (InetAddress.getLocalHost(), 80 ); InetSocketAddress baidu = new InetSocketAddress ("www.baidu.com" , 80 ); System.out.println(baidu); System.out.println(baidu.getHostName()); System.out.println(local.getAddress().getHostAddress()); System.out.println(local.getPort()); } catch (UnknownHostException e) { e.printStackTrace(); } }
结果:
java 1 2 3 4 www.baidu.com/182.61 .200 .7 :80 www.baidu.com 192.168 .1 .109 80
2.1.3 Socket 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
构造方法:
java 1 2 3 4 public Socket (InetAddress address, int port) ;public Socket (String host, int port) ;
常用方法:
java 1 2 3 4 5 6 public OutputStream getOutputStream () ;public InputStream getInputStream () ;public void close () ;
2.1.4 ServerSocket 此类实现服务器套接字。服务器套接字等待请求通过网络传入。
构造方法:
java 1 2 public ServerSocket (int port) ;
常用方法:
java 1 2 3 4 public Socket accept () ;public void close () ;
2.2 客户端 2.2.1 建立连接 在客户端网络编程中,首先需要建立连接。
示例:
java 1 Socket s = new Socket ("192.168.1.103" , 8800 );
使用服务端8800号端口建立连接,如果建立连接时本机网络不通,或服务器端程序未开启,会抛出异常。
2.2.2 数据交换 建立连接后,需要获得输入流和输出流与服务器端进行通信。使用输出流发送数据到服务器,使用输入流接收服务器发送的数据。
示例:
java 1 2 OutputStream os = s.getOutputStream();InputStream is = s.getInputStream();
2.2.3 关闭连接 当数据交换完成以后,关闭网络连接,释放网络连接占用的系统端口和内存等资源。
示例:
java
2.3 服务端 2.3.1 监听端口 在服务器端网络编程中,由于服务器端实现的是被动等待连接,所以首先要监听是否有客户端连接请求。
示例:
java 1 ServerSocket ss = new ServerSocket (8800 );
如果服务端8800号端口已经被别的程序占用,那么将抛出异常。
2.3.2 获得连接 当有客户端发起连接请求时建立连接。
示例:
java
2.3.3 数据交换 获得连接后,需要获得输入流和输出流与客户端端进行通信。使用输入流读取客户端发送的数据,使用输出流发送数据到客户端,服务端交换数据的顺序和客户端刚好相反。
示例:
java 1 2 OutputStream os = s.getOutputStream();InputStream is = s.getInputStream();
2.3.4 关闭连接 当数据交换完成以后,关闭网络连接,同时关闭监听。
示例:
java
2.4 发送消息 客户端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static void main (String[] args) { Socket s = null ; OutputStream os = null ; try { System.out.println("客户端已启动" ); s = new Socket (InetAddress.getLocalHost(), 8800 ); os = s.getOutputStream(); os.write("你好,我是客户端" .getBytes()); System.out.println("客户端成功发送数据" ); } catch (IOException e) { e.printStackTrace(); } finally { if (os != null ) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } } }
服务端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public static void main (String[] args) { ServerSocket ss = null ; Socket s = null ; InputStream is = null ; ByteArrayOutputStream baos = null ; try { System.out.println("服务端已启动" ); ss = new ServerSocket (8800 ); s = ss.accept(); is = s.getInputStream(); byte [] temp = new byte [1024 ]; int len; baos = new ByteArrayOutputStream (); while ((len = is.read(temp)) != -1 ) { baos.write(temp, 0 , len); } System.out.println("服务端成功接收数据:" + baos.toString()); } catch (IOException e) { e.printStackTrace(); } finally { if (baos != null ) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null ) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } if (ss != null ) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } }
客户端结果:
java
服务端结果:
java 1 2 服务端已启动 服务端成功接收数据:你好,我是客户端
2.5 响应消息 客户端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public static void main (String[] args) { Socket s = null ; OutputStream os = null ; InputStream is = null ; ByteArrayOutputStream baos = null ; try { System.out.println("客户端已启动" ); s = new Socket (InetAddress.getLocalHost(), 8800 ); os = s.getOutputStream(); os.write("你好,我是客户端" .getBytes()); System.out.println("客户端成功发送数据" ); s.shutdownOutput(); is = s.getInputStream(); byte [] temp = new byte [1024 ]; int len; baos = new ByteArrayOutputStream (); while ((len = is.read(temp)) != -1 ) { baos.write(temp, 0 , len); } System.out.println("客户端成功接收服务端回传数据:" + baos.toString()); } catch (IOException e) { e.printStackTrace(); } finally { if (os != null ) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } if (baos != null ) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null ) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
服务端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public static void main (String[] args) { ServerSocket ss = null ; Socket s = null ; InputStream is = null ; ByteArrayOutputStream baos = null ; OutputStream os = null ; try { System.out.println("服务端已启动" ); ss = new ServerSocket (8800 ); s = ss.accept(); is = s.getInputStream(); byte [] temp = new byte [1024 ]; int len; baos = new ByteArrayOutputStream (); while ((len = is.read(temp)) != -1 ) { baos.write(temp, 0 , len); } System.out.println("服务端成功接收数据:" + baos.toString()); os = s.getOutputStream(); os.write("你好,我是服务端" .getBytes()); System.out.println("服务端成功发送回传数据" ); } catch (IOException e) { e.printStackTrace(); } finally { if (baos != null ) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null ) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } if (ss != null ) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } if (os != null ) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
客户端结果:
java 1 2 3 客户端已启动 客户端成功发送数据 客户端成功接收服务端回传数据:你好,我是服务端
服务端结果:
java 1 2 3 服务端已启动 服务端成功接收数据:你好,我是客户端 服务端成功发送回传数据
3 UDP网络编程 3.1 常用类 3.1.1 DatagramSocket 此类表示用来发送和接收数据报包的套接字。
构造方法:
java 1 2 3 4 5 6 public DatagramSocket () ;public DatagramSocket (int port) ;public DatagramSocket (int port, InetAddress laddr) ;
常用方法:
java 1 2 3 4 5 6 public void send (DatagramPacket p) ;public synchronized void receive (DatagramPacket p) ;public void close () ;
3.1.2 DatagramPacket 此类表示数据报包。
构造方法:
java 1 2 3 4 5 6 7 8 public DatagramPacket (byte buf[], int length) ;public DatagramPacket (byte buf[], int offset, int length) ;public DatagramPacket (byte buf[], int length, InetAddress address, int port) ;public DatagramPacket (byte buf[], int offset, int length, InetAddress address, int port) ;
常用方法:
java 1 2 3 4 public synchronized InetAddress getAddress () ;public synchronized int getPort () ;
3.2 客户端 3.2.1 建立连接 与TCP建立连接不同,使用UDP建立连接不需要指定服务器的IP和端口号。
示例:
java 1 DatagramSocket ds = new DatagramSocket ();
可以指定客户端连接使用的端口号。
示例:
java 1 DatagramSocket ds = new DatagramSocket (8811 );
使用客户端8811号端口建立连接,一般在建立客户端连接时没有必要指定端口号。
3.2.2 发送数据 在UDP网络编程中,不需要使用IO流,将数据内容以及服务器的IP地址和端口号封装发送即可。
示例:
java 1 2 3 byte [] buffer = "" .getBytes();DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length, InetAddress.getLocalHost(), 8800 );ds.send(dp);
按照UDP协议的约定,不保证数据一定被正确传输,如果数据在传输过程中丢失,那就丢失了。
3.2.3 接收数据 在UDP网络编程中,不需要使用IO流,将数据内容封装接收即可。
示例:
java 1 2 3 byte [] buffer = new byte [1024 ];DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length);ds.receive(dp);
3.2.4 关闭连接 当数据交换完成以后,关闭网络连接。
示例:
java
需要说明的是,和TCP建立连接的方式不同,在UDP建立连接时没有固定的IP地址和端口号,可以将数据包发送到不同的IP地址和端口号,这点是TCP无法做到的。
3.3 服务端 3.3.1 建立连接 与UDP建立连接类似,但是需要指定服务端连接使用的端口号。
示例:
java 1 DatagramSocket ds = new DatagramSocket (8800 );
使用服务端8800号端口建立连接,由于服务器端的端口需要固定,所以一般在建立服务器端连接时都指定端口号。
3.3.2 接收数据 将数据内容封装接收即可。
示例:
java 1 2 3 byte [] buffer = new byte [1024 ];DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length);ds.receive(dp);
3.3.3 发送数据 将数据内容以及客户端的IP地址和端口号封装发送即可。
示例:
java 1 2 3 byte [] buffer = "" .getBytes();DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length, InetAddress.getLocalHost(), 8811 );ds.send(dp);
建议通过前面接收数据的数据报包获取客户端的IP地址和端口号发送数据。
示例:
java 1 2 InetAddress address = dp.getAddress();int port = dp.getPort();
3.3.4 关闭连接 当数据交换完成以后,关闭网络连接。
示例:
java
3.4 发送消息 客户端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) { try { System.out.println("客户端已启动" ); DatagramSocket ds = new DatagramSocket (); byte [] buffer = "我是客户端" .getBytes(); DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length, InetAddress.getLocalHost(), 8800 ); ds.send(dp); System.out.println("客户端成功发送数据" ); ds.close(); } catch (IOException e) { e.printStackTrace(); } }
服务端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) { try { System.out.println("服务端已启动" ); DatagramSocket ds = new DatagramSocket (8800 ); byte [] buffer = new byte [1024 ]; DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length); ds.receive(dp); System.out.println("服务端成功接收数据:" + new String (dp.getData(), 0 , dp.getLength())); ds.close(); } catch (IOException e) { e.printStackTrace(); } }
客户端结果:
java
服务端结果:
java
3.5 响应消息 客户端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) { try { System.out.println("顾客已上线" ); Scanner scan = new Scanner (System.in); DatagramSocket ds = new DatagramSocket (8811 ); while (true ) { String message = scan.next(); byte [] bufMes = message.getBytes(); DatagramPacket dpMes = new DatagramPacket (bufMes, 0 , bufMes.length, InetAddress.getLocalHost(), 8800 ); ds.send(dpMes); byte [] bufRec = new byte [1024 ]; DatagramPacket dpRec = new DatagramPacket (bufRec, 0 , bufRec.length); ds.receive(dpRec); String receive = new String (dpRec.getData(), 0 , dpRec.getLength()); System.out.println("客服说:" + receive); if (message.equals("bye" )) { break ; } } scan.close(); ds.close(); } catch (IOException e) { e.printStackTrace(); } }
服务端示例:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) { try { System.out.println("客服已上线" ); Scanner scan = new Scanner (System.in); DatagramSocket ds = new DatagramSocket (8800 ); while (true ) { byte [] bufRec = new byte [1024 ]; DatagramPacket dpRec = new DatagramPacket (bufRec, 0 , bufRec.length); ds.receive(dpRec); String receive = new String (dpRec.getData(), 0 , dpRec.getLength()); System.out.println("顾客说:" + receive); String message = scan.next(); byte [] bufMes = message.getBytes(); DatagramPacket dpMes = new DatagramPacket (bufMes, 0 , bufMes.length, InetAddress.getLocalHost(), 8811 ); ds.send(dpMes); if (message.equals("bye" )) { break ; } } scan.close(); ds.close(); } catch (IOException e) { e.printStackTrace(); } }
客户端结果:
java 1 2 3 4 5 6 7 8 9 10 11 顾客已上线 客服您好! 客服说:您好,请问有什么事情吗? 我要退货 客服说:好的,请填写退货单号 退货单号已经填好了 客服说:退货成功!请问还有其他事情吗? 没有了,再见! 客服说:好的,再见! bye 客服说:bye
服务端结果:
java 1 2 3 4 5 6 7 8 9 10 11 客服已上线 顾客说:客服您好! 您好,请问有什么事情吗? 顾客说:我要退货 好的,请填写退货单号 顾客说:退货单号已经填好了 退货成功!请问还有其他事情吗? 顾客说:没有了,再见! 好的,再见! 顾客说:bye bye
4 RMI远程调用 1.1 通信方式 常见的通信方式分为两种:
基于RPC远程调用的同步方式。
基于中间件代理的异步方式。
1.1.1 RPC远程调用 基于RPC远程调用的同步方式:
RPC(Remote Procedure Call,远程过程调用)是一种服务通信的调用方式,并不是具体的协议。
在RPC远程调用方式下,不同系统之间直接调用通信,每个请求直接从调用方发送到被调用方,然后要求被调用方返回响应结果给调用方,以确定本次调用结果是否成功。
此处的同步并不代表调用方式,RPC远程调用也可以有异步非阻塞的调用方式,但本质上仍然是需要在指定时间内得到被调用方的直接响应。
RPC的实现有很多,比如最早的CORBA和RMI,以及最近的WebService和Dubbo,甚至也可以将RESTful API看做是RPC的实现。
1.1.2 中间件代理 基于中间件代理的异步方式:
在中间件代理方式下,各子系统之间无需强耦合直接连接,调用方只需要将请求转化成异步事件(通常为异步消息)发送给中间代理,发送成功即可认为该异步链路调用完成,剩下的工作会由中间代理负责将事件可靠通知到下游的调用系统,确保任务执行完成。
中间件代理一般使用消息中间件,比如老牌的ActiveMQ和RabbitMQ,炙手可热的Kafka,以及阿里巴巴自主研发的RocketMQ。
1.2 定义 RMI(Remote Method Invocation,远程方法调用)是Java中的一种远程通信协议,允许程序利用序列化机制远程调用方法,可以看做是RPC的一种实现。
RMI与Socket的区别:
Socket独立于开发语言,客户端和服务端可以使用不同的开发语言。RMI和Java语言绑定,客户端和服务端都必须使用Java语言开发。
Socket属于传输层协议,使用TCP协议和UDP协议进行通信。RMI属于应用层协议,传输层使用Java远程消息交换协议(JRMP,Java Remote Messaging Protocol)进行通信。
Socket更灵活,可以控制序列化机制。RMI更方便,在Socket的基础上增加了对象序列化机制。
Socket占用的带宽更少,适合需要传输大量数据的场景。RMI占用的宽带较多,适合处理需要逻辑计算的场景。
1.3 使用 1.3.1 公共接口 编写公共接口,暴露服务:
java 1 2 3 4 5 6 7 8 9 package base;import java.rmi.Remote;import java.rmi.RemoteException;public interface BaseService extends Remote { String hello () throws RemoteException; }
1.3.2 客户端 编写客户端启动类:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package client;import base.BaseService;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class MainApp { public static void main (String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("localhost" , 8888 ); BaseService baseService = (BaseService) registry.lookup("BaseService" ); String hello = baseService.hello(); System.out.println(hello); } }
1.3.3 服务端 编写服务端实现类:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package server;import base.BaseService;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class ServerServiceImpl extends UnicastRemoteObject implements BaseService { protected ServerServiceImpl () throws RemoteException { super (); } @Override public String hello () throws RemoteException { System.out.println("call hello()" ); return "hello" ; } }
编写服务端启动类:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package server;import base.BaseService;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class MainApp { public static void main (String[] args) throws Exception { Registry registry = LocateRegistry.createRegistry(8888 ); BaseService baseService = new ServerServiceImpl (); registry.rebind("BaseService" , baseService); } }
1.3.4 运行 先启动服务端启动类,然后启动客户端启动类。
1.4 原理 流程如下:
概念说明:
Stub:客户端的代理,对开发人员屏蔽了远程方法调用的细节。
Skeleton:服务端的代理,对开发人员屏蔽了远程方法调用的细节。
条