Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

时间:2022-04-11 19:36:25

不必说碧绿的菜畦,光滑的石井栏,高大的皂荚树,紫红的桑葚;也不必说鸣蝉在树叶里长吟,肥胖的黄蜂伏在菜花上,轻捷的叫天子(云雀)忽然从草间直窜向云霄里去了。

—鲁迅《从百草味到三味书屋》


Java网络编程

1 URL 类

URL统一资源定位符
一个URL对象通常包含最基本的3部分信息:协议,地址,资源。

示例

用字节流读取流输入流中的数据时,由于汉字是两个字节,如果读取的长度不合适,就会有乱码,可以指定编码格式解决,增加一次读取的数据也可以解决, 用字符流也可以
中文乱码问题http://blog.csdn.net/huangbiao86/article/details/7293062
返回百度网站的源代码

URLTest.java

//package com.net;

import java.net.MalformedURLException;
import java.net.URL;

public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://www.baidu.com");
MyThread mythread = new MyThread(url);
Thread readUrl = new Thread(mythread);
readUrl.start();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}

MyThread.java

//package com.net;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class MyThread implements Runnable{
URL url;

public MyThread(URL url) {
super();
this.url = url;
}

@Override
public void run() {
try {
InputStream in = url.openStream();
byte [] b = new byte[100];
int n = -1;
while((n=in.read(b))!=-1){
//String str = new String(b,0,n);
String str = new String(b,"utf-8");
System.out.println(str);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charse
t=utf-8>
<meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link re
l=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css>
<title>百度
一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=h
.
.

2 InetAddress类

域名的表示

  • 域名
    例如: www.baidu.com

  • IP 地址
    例如: 202.108.35.210

java.net包中的InetAddress 类的对象含有一个Internet主机地址的域名和IP地址:

www.baidu.com/202.108.35.210

在连接网络时输入一个域名后,域名服务器(DNS)负责将域名转换为IP地址,这样才能和主机建立连接。

获取地址

  • 获取Internet上的主机的地址
getByName(String host)

返回格式

www.baidu.com/111.13.100.91

其他实例方法:

public String getHostName() 获取InetAdress 对象所含的域名

public String getAddress() 获取InetAdress 对象所含的IP地址
  • 获取本机地址
getLocalHost()

示例

//package com.MyInetAdress;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class Main {
public static void main(String[] args) {
try {
InetAddress address = InetAddress.getByName("www.baidu.com");
System.out.println(address.toString());
InetAddress address2 = InetAddress.getByName("111.13.129.31");// 这个不成功
System.out.println(address2.toString());
System.out.println( InetAddress.getLocalHost());
} catch (UnknownHostException e) {
System.out.println("无法找到地址");
}
}
}
www.baidu.com/111.13.100.91
/111.13.129.31
DESKTOP-QSFD0OC/192.168.194.2

3 套接字

  源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
  它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

 网络通信使用IP地址标识Internet上的计算机,使用端口号标识服务器上的进程(程序)。也就是说,如果服务器上的一个程序不占用一个端口,用户程序就无法找到它,就无法和该程序交互信息。端口号被规定为一个16位的0~65535之间的整数,其中,0~1023被预先定义的服务通信占用(如telnet占用端口
23,http占用端口80等),除非我们需要访问这些特定服务,否则,就应该使用1024~65535这些端口中的某一个进行通信,以免发生端口冲突。
  当两个程序进行通信时,可以通过Socket 类建立套接字对象并连接在一起(端口号与IP地址的组合得出一个网络套接字)。

常用的TCP/IP协议的3种套接字

常用的TCP/IP协议的3种套接字类型如下所示

流套接字(SOCK_STREAM):

流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The
Transmission Control Protocol)协议。

数据包套接字(SOCK_DGRAM):

数据包套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据包套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据包套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):

原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、
ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW
SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW
SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW

原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据包套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据包套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

现在使用的是第一种—-流套接字。

客户端套接字

客户端的程序使用Scoket类建立负责连接到服务器的套接字。

Socket (String host,int port) 

如:

Socket mysocket = new Socket("http://192.168.0.78",2010);
mysocket.getInputStream() 获取输入流,输入流的源和服务器端的一个输出流的目的地刚好相同

mysocket.getOutputStream() 获取输出流,输出流的目的地和服务器端的一个输入流的源刚好相同

ServerSocket对象与服务器端套接字

客户端发送请求—–套接字对象1,服务器端接收请求—–套接字对象2

服务器必须建立一个ServerSocket对象,把套接字1和套接字2连接起来,从而达到连接的目的。

ServerSocket构造方法:

ServerSocket(int port) port必须和客户端呼叫的端口号相同
try {
ServerSocket serverForClient = new ServerSocket(2010); //端口已经被占用,就会引发IO异常
} catch (IOEcxeption e){}

当服务器端的ServerSocket 对象 ServerForClient 建立后,就可以使用方法 accept()将客户的套接字和服务器端的套接字连接起来

try {
Socket sc = serverForClietn.accept();
} catch(IOException){}

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

示例 客户端和服务器端通信

两个java文件,运行在不同的地方,可以开启两个cmd运行,也可以一个在eclipse运行,一个在cmd里运行。

Client.java

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

public class Client {
public static void main(String[] args) {
String[] mess = { "1+1什么情况下不等于2", "轻轻的我来了", "什么东西能看、能吃、能坐" };
Socket myscoket;
DataInputStream in = null;
DataOutputStream out = null;
try {
myscoket = new Socket("127.0.0.1", 2010);
in = new DataInputStream(myscoket.getInputStream());// 得到服务器端发来的输入流
out = new DataOutputStream(myscoket.getOutputStream());
for (int i = 0; i < mess.length; i++) {
out.writeUTF(mess[i]);
String s = in.readUTF(); // in读取信息,堵塞状态
System.out.println("客户端收到服务器的回答:" + s);
Thread.sleep(500);
}
myscoket.close();
} catch (Exception e) {
System.out.println("服务器断开" + e);
}
}
}

Server.java

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) {
String[] answer = { "在算错的情况下", "轻轻的我走了", "电视、面包、沙发" };
ServerSocket serverForClient = null;// 接受客户端请求的套接字
Socket socketOnServer = null;
/**
*socketOnServer可以setSoTimeout(int timeout),accept方法堵塞超时会报SocketTimeoutException
*/

DataOutputStream out = null;
DataInputStream in = null;
try {
serverForClient = new ServerSocket(2010); //端口号与客户呼叫端口对应

} catch (IOException e) {
e.printStackTrace();
}
try {
System.out.println("等待客户呼叫:");
/**
*accept返回一个与客户端Socket对象相连接的Socket对象socketServer
*/

socketOnServer = serverForClient.accept(); // 堵塞状态,除非有客户呼叫
out = new DataOutputStream(socketOnServer.getOutputStream());
in = new DataInputStream(socketOnServer.getInputStream());// 得到客户端发来的输入流
for (int i = 0; i < answer.length; i++) {
String s = in.readUTF(); // in读取信息,堵塞状态
System.out.println("服务器收到客户的提问:" + s);
out.writeUTF(answer[i]); //写信息 输出流发出
Thread.sleep(500);
}
socketOnServer.close();
} catch (Exception e) {
System.out.println("客户端已断开:" + e);
}
}
}

结果:
Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

使用多线程技术

从套接字连接中读取数据与从文件中读取数据有着很大的不同。尽管二者都是输入流,但从文件中读取数据时,所有的数据都已经在文件中了,而使用套接字连接时,可能在另一端数据发送出来之前,就已经开始试着读取了,这时,就会堵塞本线程,直到该读取方法成功读取到信息,本线程才继续执行后续的操作。因此,服务器端收到一个客户的套接字后,就应该启动一个专门为该客户服务的线程。
Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

客户端和服务器端建立连接 ,使用套接字对象调用

public void connect (SocketAddress endpoint) throws IOException

请求和参数SocketAddress 指定地址的服务器端的套接字建立连接。为了使用connect()方法,使用InetSocketAddress(继承自SocketAddress) 对象,其构造方法是

public InetSocketAddress (InetAddress addr, int port)

示例

客户端输入圆的半径并发给服务器,服务器把计算出的圆的面积返回给客户。

客户端流程: 用户输入IP地址、端口和圆的半径,客户端把半径写到输出流*服务器读取,输入流等待获取服务器端发来的圆的面积数据

服务器端流程:通过输入流得到客户端发到输出流中的半径数据,计算面积后,把面积数据写到输出流*客户端读取

Server2.java

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server2 {
public static void main(String[] args) {
ServerSocket server = null;
ServerThread thread;
Socket you = null;
while (true) {
try {
server = new ServerSocket(2010);
} catch (IOException e) {
System.out.println("正在监听");// ServerSocket 对象不能重复创建
}

try {
System.out.println(" 等待客户呼叫:");
you = server.accept();
System.out.println("客户的地址:" + you.getInetAddress());
} catch (IOException e) {
System.out.println("正在等待客户");
}
if (you != null) {
new ServerThread(you).start();// 为每个客户启动一个专门的线程
}
}
}
}

class ServerThread extends Thread {
Socket socket;
DataInputStream in = null;
DataOutputStream out = null;
String s = null;

public ServerThread(Socket t) {
socket = t;

try {
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
}
}

@Override
public void run() {
while (true) {
double r;
try {
r = in.readDouble();
double area = Math.PI * r * r;
out.writeDouble(area);
} catch (IOException e) {
System.out.println("客户离开");
return;
}
}
}
}

Client2.java

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.InputMismatchException;
import java.util.Scanner;

public class Client2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Socket mysocket = null;
DataInputStream in = null;
DataOutputStream out = null;
Thread readData;
Read read = null;
try {
mysocket = new Socket();
read = new Read();
readData = new Thread(read);
System.out.println("输入服务器的IP:");
String IP = scanner.next();
System.out.println("输入端口号:");
int port = scanner.nextInt();
if (mysocket.isConnected()) {
} else {
InetAddress address = InetAddress.getByName(IP);
InetSocketAddress socketAddress = new InetSocketAddress(address, port);
mysocket.connect(socketAddress);
in = new DataInputStream(mysocket.getInputStream());
out = new DataOutputStream(mysocket.getOutputStream());
read.setInDataInputStream(in);
readData.start();// 客户端的线程开启
}

} catch (Exception e) {
System.out.println("服务器已断开" + e);
}
System.out.print("输入圆的半径(放弃输入N):");
while (scanner.hasNext()) { //循环读取输入的半径
double radius = 0;
try {
radius = scanner.nextDouble();
} catch (InputMismatchException e) {
System.exit(0);
}

try {
out.writeDouble(radius);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Read.java

import java.io.DataInputStream;

public class Read implements Runnable{
DataInputStream in;

public void setInDataInputStream(DataInputStream in) {
this.in = in;
}

@Override
public void run() {
double result = 0;
while(true){ // 循环读取面积
try {
result = in.readDouble();
System.out.println("圆的面积:"+result);
System.out.print("输入圆的半径(放弃请输入N):");
} catch (Exception e) {
System.out.println("与服务器断开连接"+e);
}
}
}
}

服务器端显示

等待客户呼叫:
客户的地址:/127.0.0.1
正在监听
等待客户呼叫:
客户离开

客户端显示

D:\javafile\net>java Client
输入服务器的IP:127.0.0.1
输入端口号:2010
输入圆的半径(放弃请输入N):32
圆的面积:3216.990877275948
输入园的半径(放弃请输入N):12
圆的面积:452.3893421169302
输入园的半径(放弃请输入N):10
圆的面积:314.1592653589793
输入园的半径(放弃请输入N):N

D:\javafile\net>

4 UDP数据包

相比基于TCP协议的套接字通信面向连接、可靠的数据传输服务。UPD数据包套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据包套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据包套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

套接字基于TCP的网络通信 —–就像生活中使用电话进行信息交互
套接字基于UDP(用户数据报协议)—–就像生活中的邮寄信件,信息传递更快。

既然UDP是一种不可靠的协议,为什么还要使用它呢?如果要求数据必须绝对准确的到达目的地,显然不能选择UDP来通信。但有时候人们需要较快速地传输信息,并能容忍小的错误,就可以考虑使用UDP。

基于UDP通信的基本模式是:

  • 将数据打包,称为数据包(好比将信件装入信封一样),然后将数据包发完目的地。
  • 接收别人发来的数据包(好比接受信封一样),然后查看数据包中的数据。

发送数据包

(1)用DatagramPacket类来将数据打包,DatagramPacket类创建的一个对象,称为数据包。

DatagramPacket(byte data[] ,int length,InetAddress address, int port);

DatagramPacket(byte data[] ,int offset,int length,InetAddress address, int port);
  • data 指定的数组数据
  • address 目标地址
  • port 端口号
  • offset 从数组中的offset开始取length字节长度的数据
byte [] data = "26号新生进行结束了".getBytes();
InetAddress address = InetAddress.getByName("www.baidu.com");
DatagramPacket data_packet = new DatagramPacket(data,data.length,address,2017);

这样就创建了用于发送的数据包: data_packet,它可以调用下面的方法得到相应的数据

(int)  data_packet.getPort() 获取数据包目标端口
(InetAddress) data_packet.getAddress 获取数据包的目标地址
(byte[]) data_packet.getData() 返回数据包中的字节数组

(2) 发送数据包,DatagramSocket()创建的对象

  DatagramSocket mail_out = new DatagramSocket();
mail_out.send(data_packet);

接收数据包

DatagramSocket(int port)创建一个对象,port必须与待接收的数据包的端口号相同。

DatagramSocket mail_in = new DatagramSocket(5566);

然后对象mail_in使用方法receive(DatagramSocket pack)接收数据包。
预备一个数据包以便接收数据包。

byte[] data = new byte[8192];
DatagramPacket pack = new DatagramPacket(data,data.length);
mail_in.receive(pack);

数据包pack将接收长度是data.length的数据存入data中

receive方法可能会堵塞,知道收到数据包
数据包数据的长度不要超过8192KB

示例

张三和李四使用用户数据报相互发送和接收数据包。

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

LiSi.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class LiSi {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Thread readData;
ReceiveLetterForLi receiver = new ReceiveLetterForLi();
try {
readData = new Thread(receiver);
readData.start(); // 负责接收信息的线程
byte[] buffer = new byte[1];
InetAddress address = InetAddress.getByName("127.0.0.1");
DatagramPacket dataPack = new DatagramPacket(buffer, buffer.length, address, 888);
DatagramSocket postman = new DatagramSocket();
System.out.print("输入发给张三的信息:");
while (scanner.hasNext()) {
String mess = scanner.nextLine();
buffer = mess.getBytes();
if (mess.length() == 0){
System.out.println("不要输入空内容");
continue; // 不接着循环 空内容不发到数据包中
}
//buffer = mess.getBytes();
dataPack.setData(buffer);
postman.send(dataPack);
System.out.print("继续输入发给张三的信息:");
}

} catch (Exception e) {
System.out.println(e);
}

}
}

class ReceiveLetterForLi implements Runnable {

@Override
public void run() {
DatagramPacket pack = null;
DatagramSocket postman = null;
byte[] data = new byte[8192];
try {
pack = new DatagramPacket(data, data.length);
postman = new DatagramSocket(666);

} catch (Exception e) {
}
while (true) {
if (postman == null){
System.out.println("没有连接到张三");
break;
}
else
try {
postman.receive(pack);
String message = new String(pack.getData(), 0, pack.getLength());
System.out.printf("%25s\n", "收到:"+ message);
} catch (Exception e) {
}
}

}

}

ZhangSan.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class ZhangSan {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Thread readData;
ReceiveLetterForZhang receiver = new ReceiveLetterForZhang();
try {
readData = new Thread(receiver);
readData.start(); // 负责接收信息的线程
byte[] buffer = new byte[1];
InetAddress address = InetAddress.getByName("127.0.0.1");
DatagramPacket dataPack = new DatagramPacket(buffer, buffer.length, address, 666);
DatagramSocket postman = new DatagramSocket();
System.out.print("输入发给李四的信息:");
while (scanner.hasNext()) { // Enter键作为一个结束符,输入内容按Enter会一次发多个消息
String mess = scanner.nextLine().trim();
buffer = mess.getBytes();
if (mess.length() == 0){
System.out.println("不要输入空内容");
continue; // 不接着循环 空内容不发到数据包中
}

//buffer = mess.getBytes();
dataPack.setData(buffer);
postman.send(dataPack);
System.out.print("继续输入发给李四的信息:");
}
} catch (Exception e) {
System.out.println(e);
}
}
}

class ReceiveLetterForZhang implements Runnable {

@Override
public void run() {
DatagramPacket pack = null;
DatagramSocket postman = null;
byte[] data = new byte[8192];
try {
pack = new DatagramPacket(data, data.length);
postman = new DatagramSocket(888);
} catch (Exception e) {
}
while (true) {
if (postman == null){
System.out.println("没有连接到李四");
break;
}
else
try {
postman.receive(pack);
String message = new String(pack.getData(), 0, pack.getLength());
System.out.printf("%25s\n", "收到:" + message);
} catch (Exception e) {

}
}
}

}

5 广播数据包

我们很多人都使用过收音机,也熟悉广播电台的一些基本术语,例如,当一个电台在某个波段和频率上进行广播时,接收者调到电台指定的波段、频率就可以听到广播的内容。

计算机发送广播需要用到IP地址和端口。
IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址,D、E类为特殊地址。

A类 10.0.0.0–10.255.255.255
B类 172.16.0.0–172.31.255.255
C类 192.168.0.0–192.168.255.255

D类IP地址
D类IP地址被叫做多播地址(multicast address),即组播地址。在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点。多播地址的最高位必须是“1110”,范围从224.0.0.0到239.255.255.255。

要广播或接受广播的主机都必须加入到同一个D类地址。一个D类地址也称做一个组播地址,D类地址并不代表某个特定主机的位置,一个具有A、B或C类地址的主机要广播数据或接收广播,都必须加入到同一个D类地址。

示例

一个主机不断地重复广播放假天数,加入到同一组的主机都可以随时接收广播的信息。下面的代码分一个广播端BroadCast和一个接收端Receiver。

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

BroadCast.java

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class BroadCast {
String s = "国庆放假时间是10天";
int port = 5858; // 组播的端口
InetAddress group = null; // 组播的地址
MulticastSocket socket = null; // 多点广播的套接字

BroadCast() {
try {
group = InetAddress.getByName("239.255.8.0"); // 设置广播组的地址为239.255.8.0
socket = new MulticastSocket(port); // 多点广播套接字将在port端口广播
socket.setTimeToLive(1); // 多点广播套接字发送数据范围为本地网络
socket.joinGroup(group); // 加入group后,socket发送的数据报group中的成员收到

} catch (Exception e) {
System.out.println("Error:" + e);
}
}

public void paly() {
while (true) {
try {
DatagramPacket packet = null;// 待广播的数据包
byte[] data = s.getBytes();
packet = new DatagramPacket(data, data.length, group, port);
System.out.println(new String(data));
socket.send(packet); // 广播数据包
Thread.sleep(2000);

} catch (Exception e) {
System.out.println("Error:" + e);
}
}
}

public static void main(String[] args) {
new BroadCast().paly();
}
}

Receiver.java

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class Receiver {
public static void main(String[] args) {
int port = 5858;// 组播的端口
InetAddress group = null; // 组播的地址
MulticastSocket socket = null; // 多点广播套接字
try {
group = InetAddress.getByName("239.255.8.0"); // 设置广播组的地址为239.255.8.0
socket = new MulticastSocket(port); // 多点广播套接字将在port端口广播
socket.joinGroup(group); // 加入group
} catch (Exception e) {
}
while (true) {
byte[] data = new byte[8192];
DatagramPacket packet = null;
packet = new DatagramPacket(data, data.length, group, port); // 待接收的数据包
try {
socket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("接收的内容:\n" + message);
} catch (Exception e) {
}
}
}
}

6 Java远程调用

Java远程调用技术(Remote Method Invocatio,RMI)是一种分布式技术,实用RMI可以让一个虚拟机上的应用程序请求调用位于网络上的另一处的虚拟机上的对象方法。习惯上称发出调用请求的虚拟机为(本地) 客户机,称接受并执行的虚拟机为(远程)服务器。

远程对象及其代理

驻留在(远程)服务器上的对象是客户要请求的对象,称作远程对象,及客户端程序请求远程对象调用方法,然后远程对象调用并返回必要的结果。

代理和存根(Stub)

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

用户实际上实在和远程对象的代理直接打交道。
RMI会帮我们生成一个存根(Stub):一种特殊的字节码,并让这个存根产生的对象作为远程对象的代理。代理需要驻留在客户端,也就是说要把RMI生成的存根(Stub)复制或下载到客户端
代理的特点:它与远程对象实现了相同的接口,也就是说它与远程对象向用户公开了相同的方法,当用户请求代理调用这样的方法时,如果代理确认远程对象能调用相同的方法时,就把实际的方法调用委派给远程对象。

Remote接口

RMI为了标识一个对象是远程对象,要求其必须实现java.rmi 包中的 Remote 接口,Remote 接口中没有方法,仅仅起到一个标识的作用。所以,你要扩展Remote 接口,以便规定远程对象的哪些方法是客户可以请求的方法,用户程序不必编写和远程代理的有关代码,只需要知道远程对象和远程对象实现了相同的接口。

RMI的设计细节

示例-本地客户机和远程服务器的通信

我们假设本地客户及存放有关类的目录是D:\...\Client(自己放的位置);远程服务器的IP是127.0.0.1,存放有关类的目录是D:\...\Server

代码文件结构:

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

1 扩展Remote接口

定义一个接口扩展Remote接口,增加计算某种几何图形的面积。

RemoteSubject.java

import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RemoteSubject extends Remote {
public void setHeigth(double height) throws RemoteException;

public void setWidth(double width) throws RemoteException;

public double getArea() throws RemoteException;
}

这个文件放在D:\...\Server目录下,并编译生成的RemoteSubject.class文件,并且复制一份到D:\...\Client

2 远程对象

远程对象需要实现Remote接口,但是原生的Remote接口没有方法,刚好自己刚刚已经定义了RemoteSubject接口,实现这个接口就可以了。
RMI为了让一个对象称为远程对象,还要继承UnicastRemoteObject完成一些必要的初始化操作。

RemoteConcreteSubject.java

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteConcreteSubject extends UnicastRemoteObject implements RemoteSubject {
double width;
double height;

protected RemoteConcreteSubject() throws RemoteException {
}

@Override
public void setHeigth(double height) throws RemoteException {
this.height = height;
}

@Override
public void setWidth(double width) throws RemoteException {
this.width = width;
}

@Override
public double getArea() throws RemoteException {
return width * height;
}
}

编译生成RemoteConcreteSubject.class文件

3 存根(Stub)与代理

生成存根,后缀为_Stub的.class文件
在cmd中输入下面的命令:

D:\...\Server> rmic RemoteConcreteSubject

会有警告提示过时,但也能用
警告: 为 JRMP 生成和使用骨架及静态存根
已过时。骨架不再必要, 而静态存根
已由动态生成的存根取代。建议用户
不再使用rmic来生成骨架和静态存根。
请参阅 java.rmi.server.UnicastRemoteObject 的文档。

在Server目录下会有RemoteConcreteSubject_Stub.class文件,并且复制一份到D:\...\Client

4 启动注册: rmiregistry

在cmd中的输入下面的命令(转到D:\...\Server目录下):

D:\...\Server> rmiregistry

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

5 启动远程对象服务

远程服务启动注册rmiregistry后,远程服务器就可以启动远程对象服务了,这时,还要绑定这个远程对象到rmiregistry所管理的注册表中。
下面的程序启动了一个远程对象服务,即该应用程序可以让用户访问它注册的远程对象。

BindRemoteObject.java

import java.rmi.Naming;

public class BindRemoteObject {
public static void main(String[] args) {
try {
RemoteConcreteSubject remoteObject = new RemoteConcreteSubject();
Naming.rebind("rmi://127.0.0.1/rect", remoteObject);
System.out.println(" be ready for client server.....");
} catch (Exception e) {
System.out.println(e);
}
}
}

编译文件,在Server目录下会有BindRemoteObject.class文件
Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

6 运行客户端程序

远程服务器启动元处对象服务后,客户端就可以运行有关程序了,访问使用远程对象。

ClientApplication.java

import java.rmi.Naming;
import java.rmi.Remote;

import com.Java远程调用.Server.RemoteSubject;

public class ClientApplication {
public static void main(String[] args) {
try {
Remote remoteObject =Naming.lookup("rmi://127.0.0.1/rect"); //必须和远程对象注册的相同
RemoteSubject remoteSubject = (RemoteSubject) remoteObject;
remoteSubject.setHeigth(528);
remoteSubject.setWidth(129);
double area = remoteSubject.getArea();
System.out.println("面积:"+area);
} catch (Exception e) {
System.out.println(e.toString());
}
}
}

这个文件保存到D:\...\Server\Client目录下

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

注意

上面程序涉及的几次注册和启动远程对象服务都要一直在后台开启,我是用了几个cmd程序分别运行(注意不要有中文路径)。

关闭相应的cmd程序会报下面的异常

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

这是我的运行方式

Java笔记(9)-网络编程、URL、InetAddress、套接字、Socket、ServerSocket、多线程、UDP、广播数据包、远程调用

参考

1.耿祥义、张跃平的《Java程序设计实用教程》