第十三章 JavaJava网络编程
13.1 URL(Uniform Resource Location-系统资源定义符) 类
- 介绍:URL是一个有关网络的一个类在
java.net
下,可以获取一些关于网址的信息和网址的内容。 - 类中的重要方法
- 常用构造方法
public URL(String spec)throws MalformedURLException
- 常用方法
- 建立输入流
public final InputStream openStream()throws IOException
- 建立输入流
- 常用构造方法
- 例子(从网页获取网页文件)
Example13_1.java
import java.net.*;
import java.io.*;
import java.util.*;
public class Example13_1 {
public static void main(String args[]) {
Scanner scanner;
//定义URL对象
URL url;
//定义一个线程
Thread readURL;
//定义访问网站的类
Look look = new Look();
System.out.println("输入URL资源:");
//传入网站
scanner = new Scanner(System.in);
String source = scanner.nextLine();
try { url = new URL(source);
look.setURL(url);
readURL = new Thread(look);
readURL.start();
}
catch(Exception exp){
System.out.println(exp);
}
}
}
Look.java
import java.net.*;
import java.io.*;
public class Look implements Runnable {
//松耦合,定义url
URL url;
public void setURL(URL url) {
this.url=url;
}
public void run() {
try {
//定义一个流用于传输数据
InputStream in = url.openStream();
byte [] b = new byte[1024];
int n=-1;
while((n=in.read(b))!=-1) {
/*读取字节到数组b中,如果数组长度为0,则返回值为0,
如果数组长度不为0且没有的东西可以读取则返回值为-1,
其他情况则返回值是实际读取的字节数*/
String str = new String(b,0,n,"utf-8");
/*将数组b中的字节从第0个开始编码以utf-8的格式
*/
System.out.print(str);
}
}
catch(IOException exp){}
}
}
- 程序简易流程图
13.2 InetAddress 类
- 介绍:此类获取主机的IP地址和域名
- 杂:一个IP可以对应几个域名,一个域名只能对应一个IP
import java.net.*;
public class Example13_2 {
public static void main(String args[]) {
try{ InetAddress address=InetAddress.getByName("www.sina.com.cn");
System.out.println(address.toString());
address=InetAddress.getByName("221.180.220.34");
System.out.println(address.toString());
address=InetAddress.getLocalHost();
System.out.println(address.toString());
}
catch(UnknownHostException e) {
System.out.println(""+e);
}
}
}
结果
www.sina.com.cn/218.61.192.109
/221.180.220.34
DESKTOP-RVBPOVA/10.0.0.1
13.3 套接字
13.3.1 套接字概述
- 介绍:套接字简单理解就是IP+端口,有了套接字之后就可以把客户机和服务器相互连接进行数据传送。
13.3.2 客户套接字的建立
- 客户套接字的构造方法:
Socket(String host,int port)
host
是IPport
是端口号
- 使用例子
try{ Socket clientSocket = new Socket("192.168.0.78",2010); }catch(IOException e){}
- 在建立套接字之后就可以使用
getInputStream()
,getOutputStream()
方法与服务器建立输入输出流。- 注意:客户端就是服务器的输出流的目的地,同时服务器就是客户端的输出流的目的地。
13.3.3 服务器套接字的建立
- 为了能使客户成功连接到服务器服务器必须建立一个
ServerSocket
对象 - ServerSocket的构造方法:
ServerSocket(int port)
port是端口号 - 例子
try{ ServerSocket serverForClient = ServerSocket(2010); }catch(IOException e){}
13.3.4 服务器与客户机的连接
- 问题:服务器的ServerSocket对象为什么不能和Socket对象一样直接建立输入输出流,而是要通过ServerSocket对象建立Socket对象从而建立输入输出流。
- 建立连接的原理:经过客户端套接字和服务器套接字的建立由于客户端定位了服务器的IP和端口,而服务器的端口又和客户机寻找的端口一致就为连接客户机和服务器的连接构成了条件,所以可以通过
accept()
方法建立Socket对象,从而通过Socket对象建立输入输出流。通信完毕后要close()
。 - 套接字连接读取数据和从文件中读取数据的区别
- 读取文件中的数据:文件中的数据已经存在,直接读取就好了。
- 套接字:客户机中的数据没存在而服务器就开始读取了,所以在服务器读取不到客户数据的这段时间就是阻塞状态。(就像银行和客人一样:银行的人员始终等待着客户但是客户可能来可能不来给你数据)
- 例子
Client.java
import java.io.*;
import java.net.*;
public class Client {
public static void main(String args[]) {
//设定3个问题
String [] mess ={"珠穆朗玛峰的高度是多少?","亚洲有多少个国家?","西宁是哪个省的省会?"};
/*
定义一些对象
1. 套接字对象
2. 数据输入流
3. 数据输出流
*/
Socket mysocket;
DataInputStream in=null;
DataOutputStream out=null;
try{
//使用直接设定服务器的IP和端口的构造方法,在服务器没有故障的情况下就此和服务器建立联系
mysocket=new Socket("127.0.0.1",2010);
//调用Socket对象的方法创立输入字符输入流和输出流作为数据流的底层流
in=new DataInputStream(mysocket.getInputStream());
out=new DataOutputStream(mysocket.getOutputStream());
for(int i=0;i<mess.length;i++) {
out.writeUTF(mess[i]);
String s=in.readUTF(); //in读取信息,堵塞状态
System.out.println("客户收到服务器的回答:"+s);
Thread.sleep(1000);
}
}
catch(Exception e) {
System.out.println("服务器已断开"+e);
}
}
}
Server.java
import java.io.*;
import java.net.*;
public class Server {
public static void main(String args[]) {
//设定答案
String [] answer ={"顶岩石面海拔8844.43米","48个","青海省"};
/*1. 建立服务套接字对象,
2. 建立以后用于通信的套接字
3. 建立数据输入输出流
*/
ServerSocket serverForClient=null;
Socket socketOnServer=null;
DataOutputStream out=null;
DataInputStream in=null;
//用服务器套接字设定接口
try { serverForClient = new ServerSocket(2010);
}
catch(IOException e1) {
//如果端口号已经被占用,将触发异常
System.out.println(e1);
}
try{
System.out.println("等待客户呼叫");
//设定Socket对象,就此用完accept方法之后,如果客户有要求就此和客户连接起来
socketOnServer = serverForClient.accept();
//服务器套接字的介个方法获得客户的地址
System.out.println("客户的地址:"+socketOnServer.getInetAddress());
System.out.println("客户的端口:"+socketOnServer.getPort());
//定义数据流
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(1000);
}
}
catch(Exception e) {
System.out.println("客户已断开"+e);
}
}
}
13.3.4 使用多线程技术
- 例子
Client.java
import java.io.*;//DataInputStream DataOutputStream
import java.net.*;//Socket InetAddress InetSocketAddress
import java.util.*;//Scanner
public class Client {
public static void main(String args[]) {
/*定义1. 调用用户的键盘
2. 套接字
3. 数据输入流
4. 数据输出流
5. 定义一个线程
6. 创建一个read对象
*/
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
read = new Read();
readData = new Thread(read);
//如果没有连接上,输入服务器的IP和接口
System.out.print("输入服务器的IP:");
String IP = scanner.nextLine();
System.out.print("输入端口号:");
int port = scanner.nextInt();
if(mysocket.isConnected()){}//如果连接就没有什么命令
else{
/*由于创建套接字的时候没有给定服务器的IP,
没有办法连接到服务器所以要用connect方法来连接服务器
使用connect方法的时候需要在参数InetSocketAddress中的构造方法中的
InetAddress中设置服务器的IP*/
InetAddress address=InetAddress.getByName(IP);//创立address对象,设置IP
InetSocketAddress socketAddress=new InetSocketAddress(address,port);//设置IP和接口
mysocket.connect(socketAddress); //连接服务器
in =new DataInputStream(mysocket.getInputStream());//设置输入流
out = new DataOutputStream(mysocket.getOutputStream());//设置输出流
read.setDataInputStream(in);//在线程中设置输入流
readData.start();//开启线程
}
}
catch(Exception e) {
System.out.println("服务器已断开"+e);
}
System.out.print("输入园的半径(放弃请输入N):");
while(scanner.hasNext()) {
double radius=0;
//这次的try-catch是用于防止输入非double类的数字
try {
radius = scanner.nextDouble();
}
catch(InputMismatchException exp){
System.exit(0);
}
//这次的try-catch是用于防止没有连接上服务器
try {
out.writeDouble(radius);
}
catch(Exception e) {}
}
}
}
Read.java
import java.io.*;
public class Read implements Runnable {
//设置数据输入流,松耦合
DataInputStream in;
public void setDataInputStream(DataInputStream in) {
this.in = in;
}
public void run() {
double result=0;
while(true) {
try{ result=in.readDouble();
System.out.println("圆的面积:"+result);
System.out.print("输入园的半径(放弃请输入N):");
}
catch(IOException e) {
System.out.println("与服务器已断开:"+e);
break;
}
}
}
}
Server.java
import java.io.*;
import java.net.*;
import java.util.*;
public class Server {
public static void main(String args[]) {
//建立服务器套接字
ServerSocket server=null;
ServerThread thread;
Socket you=null;
//创立服务器接口
while(true) {
try{ server=new ServerSocket(2010);
}
catch(IOException e1) {
//ServerSocket对象不能重复创建,除非更换端口号
System.out.println("正在监听");
}
try{ System.out.println("等待客户呼叫");
you=server.accept();
System.out.println("客户的地址:"+you.getInetAddress());
}
catch (IOException e) {
System.out.println("正在等待客户");
}
if(you!=null) {
/*将you这个Socket对象传给另一个线程,当多个用户同时访问服务器时因为new过所以不会扰乱线程
每一次new过的地址不会被系统清理所以线程还在继续*/
new ServerThread(you).start(); //为每个客户启动一个专门的线程
}
}
}
}
class ServerThread extends Thread {
//就此开启线程
/*
1. 创立Socket对象
2. 数据输入输出流
*/
Socket socket;
DataOutputStream out=null;
DataInputStream in=null;
String s=null;
//重写构造方法
ServerThread(Socket t) {
socket=t;
//实体化数据流
try { out=new DataOutputStream(socket.getOutputStream());
in=new DataInputStream(socket.getInputStream());
}
catch (IOException e){}
}
public void run() {
while(true) {
try{
//计算圆面积
double r=in.readDouble();//堵塞状态,除非读取到信息
double area=Math.PI*r*r;
out.writeDouble(area);
}
catch (IOException e) {
System.out.println("客户离开");
return;
}
}
}
}
13.4 UDP 数据报
- 介绍:不是点对点连接传输数据,速度快,可靠性低,
13.4.1 发送数据包
- 用到的类:
DatagramPacket
(此类创建的对象称为数据包) - 构造方法
DatagramPacket(byte data[],int length,IntAddress address,int port)
(这里的address是IntAddress类的对象,port是端口)DatagramPack(byte data[],int offset,int length,InetAddress address,int port)
DatagramSocket()
不带参数只有传输数据的功能。eg:-
DatagramSocket mail_out = new DatagramSocket(); mail_out.send(data_pack);
-
- 其他方法
- 获取数据包的目标端口:
public int getPort()
- 获取数据包的目标地址:
public InetAddress getAddress()
- 获取数据包的目标字节数组:
public byte[] getData()
- 获取数据包的目标端口:
13.4.2 接收数据包
- 用到的类:
DatagramSocket
- 构造方法:
DatagramSocket(int port)
- 用法
- 创建方法的参数:用一个新的构造方法创建DatagramPack对象
DatagramPack(byte data[],int length)
(data是用于接收的字节数组,length是每次接收的长度) - 使用receive方法接收数据
receive(DatagramPacket pack)
- 创建方法的参数:用一个新的构造方法创建DatagramPack对象
- 注意
- receive()方法会阻塞,直到收到数据包
- 数据包中的数据长度不要超过8192KB
- 接收数据包的例子
DatagramSocket mail_in = new DatagramSocket(5666); byte data[] = new byte[100]; int length = 90; DatagramPacket pack = new DatagramPacket(data,length); mail_in.receive(pack);
- 例子
ZhangSan.java
import java.util.*;
public class ZhangSan {
public static void main(String args[]) {
Scanner scanner = new Scanner(System.in);
//用自己写的类定义的发送数据的包,创建对象
SendDataPacket sendDataPacket=new SendDataPacket();
//定义目标IP
sendDataPacket.setIP("127.0.0.1");
//定义目标端口
sendDataPacket.setPort(666);
//自己定义的结合搜数据的包,创建对象
ReceiveDatagramPacket receiveDatagramPacket = new ReceiveDatagramPacket();
//设置端口
receiveDatagramPacket.setPort(888);
//启动线程
receiveDatagramPacket.receiveMess();
System.out.print("输入发送给李四的信息:");
while(scanner.hasNext()) {//判断是否还有字符串
//将字符串赋值给
String mess = scanner.nextLine();
//如果字符串长度为0则报错
if(mess.length()==0)
System.exit(0);
//将字符串转换为字节存放在字节数组中
byte buffer[] = mess.getBytes();
//发送信息
sendDataPacket.sendMess(buffer);
System.out.print("继续输入发送给李四的信息:");
}
}
}
LiSi.java
import java.util.*;
public class LiSi {
public static void main(String args[]) {
Scanner scanner = new Scanner(System.in);
SendDataPacket sendDataPacket=new SendDataPacket(); //负责发送数据包
sendDataPacket.setIP("127.0.0.1");
sendDataPacket.setPort(888);
ReceiveDatagramPacket receiveDatagramPacket =
new ReceiveDatagramPacket();
receiveDatagramPacket.setPort(666);
receiveDatagramPacket.receiveMess();//负责接受数据包
System.out.print("输入发送给张三的信息:");
while(scanner.hasNext()) {
String mess = scanner.nextLine();
if(mess.length()==0)
System.exit(0);
byte buffer[] = mess.getBytes();
sendDataPacket.sendMess(buffer);
System.out.print("继续输入发送给张三的信息:");
}
}
}
ReceiveDatagramPacket.java
import java.net.*;
public class ReceiveDatagramPacket implements Runnable {
//松耦合创建线程
Thread thread;
//设置接口变量
public int port;
//初始化线程
public ReceiveDatagramPacket(){
thread = new Thread(this);
}
//设置接口方法
public void setPort(int port){
this.port = port;
}
//启动线程方法
public void receiveMess(){
thread.start();
}
public void run() {
//创建发送数据对象-pack
DatagramPacket pack=null;
//创建接受数据对象-datagramSocket
DatagramSocket datagramSocket=null;
//创建最大数据量的字节数组
byte data[]=new byte[8192];
try{
//初始化发送接收数据的对象
pack=new DatagramPacket(data,data.length);
datagramSocket=new DatagramSocket(port);
}
catch(Exception e){}
if(datagramSocket==null) return;
while(true) {
try{
//就此使用receive方法开始接受包中的数据
datagramSocket.receive(pack);
String message=new String(pack.getData(),0,pack.getLength());
System.out.printf("%25s\n","收到:"+message);
}
catch(Exception e){}
}
}
}
SendDataPacket.java
import java.net.*;
public class SendDataPacket {
public byte messBySend []; //存放要发送的数据
public String IP; //目标IP地址
public int port; //目标端口
//定义目标端口方法
public void setPort(int port){
this.port = port;
}
//定义目标IP方法
public void setIP(String IP){
this.IP = IP;
}
//定义发送信息的方法
public void sendMess(byte messBySend []){
try{
//定义InetAddress作为下面方法的参数
InetAddress address=InetAddress.getByName(IP);
DatagramPacket dataPack=new DatagramPacket(messBySend,messBySend.length,address,port);
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.send(dataPack);
}
catch(Exception e){}
}
}
- 注意
- 接收的Java文件用多线程的原因是可能发生阻塞
13.5 广播数据报
- 预备知识
-
IP
- 不同体系中的网络模型分层和对应的协议。
- 不同体系中的网络模型分层和对应的协议。
-
IP地址分类(IP协议)
- IPv4地址:由段组成,每一段是一个字节,最大值时255(转换为10进制之后)。
- A类地址:(1.0.0.1-126.255.255.254)一般用于大型网络。
- B类地址:(128.1.0.1-191.254.255.254)一般用于中型网络。
- C类地址:(192.0.1.1-223.255.254.254)一般用于小型网络。
- D类地址:(224.0.0.1-239.255.255.254)用于广播
- E类地址:(240.0.0.0—255.255.255.254) 保留地址
- 一些特殊的地址(书中的例子三的IP就是127.0.0.1指向本机)
- IPv4地址:由段组成,每一段是一个字节,最大值时255(转换为10进制之后)。
-
IPV6地址:现在不常用,IPv6产生原因是IPv4的地址不够用所以产生了IPv6。
-
- 创建多点广播
- 介绍:创建广播分为单点广播和多点广播。他们的共同点是IP地址都需要是广播地址(IPv4-D类,IPv6-FF0::1),而且用到的协议都是UDP。只是由服务器给用户传输数据没有用户给服务器传输数据的过程。
- 单点广播:把上边的UDP的例子的IP改为D类地址就行。(DatagramSocket类)
- 多点广播:所用的类是MulticastSocket,是继承DatagramSocket类在此类上增加了支持多点传播数据的方法,并且需要加入多点广播组。
- 具体的使用方法
- 准备组播的端口、地址、多点广播套接字,建立group
int port=5858; //这个时候定义IP,建立group InetAddress group==InetAddress.getByName("239.255.8.0"); //这个时候定义端口 MulticastSocket socket=new MulticastSocket(port);
- 定义广播网络范围加入group
//定义网络范围参数是1就是本地网路,其中参代表可以经过的路由器数量,每经过一个路由器参数就减1 socket.setTimeToLive(1); //jdk14以后新定义joinGroup的方法 InetSocketAddress socketAddress = new InetSocketAddress(group,port); NetworkInterface networkInterface = NetworkInterface.getByInetAddress(group); socket.joinGroup(sockedAddress,networkInterface); //加入group后,socket发送的数据报被group中的成员接收到
- 开始传输信息
//等待传输数据UDP协议 DatagramPacket packet=null; byte data[]=s.getBytes(); //打包数据 packet=new DatagramPacket(data,data.length,group,port); //发送数据 socket.send(packet); //广播数据包
- 准备组播的端口、地址、多点广播套接字,建立group
- 介绍:创建广播分为单点广播和多点广播。他们的共同点是IP地址都需要是广播地址(IPv4-D类,IPv6-FF0::1),而且用到的协议都是UDP。只是由服务器给用户传输数据没有用户给服务器传输数据的过程。
- 例子
BroadCast.java
Receiver.javaimport java.net.*; public class BroadCast { //设置播报内容 String s="国庆放假时间是9月30日"; //设置接口 int port=5858; //创建播组地址 InetAddress group=null; //创建多点广播套接字,作用是使所用连到这个接口的客户都能收到信息 MulticastSocket socket=null; //定义构造方法 BroadCast() { try { //设置广播组地址 group=InetAddress.getByName("239.255.8.0"); //设置多点广播组端口 socket=new MulticastSocket(port); //多点广播套接字发送数据报范围为本地网络 socket.setTimeToLive(1); //jdk14以后新定义joinGroup的方法 InetSocketAddress socketAddress = new InetSocketAddress(group,port); NetworkInterface networkInterface = NetworkInterface.getByInetAddress(group); socket.joinGroup(sockedAddress,networkInterface); //加入group后,socket发送的数据报被group中的成员接收到 } catch(Exception e) { System.out.println("Error: "+ e); } } public void play() { while(true) { try{ DatagramPacket packet=null; //待广播的数据包 byte data[]=s.getBytes(); //用UDP传输 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().play(); } }
import java.net.*; import java.util.*; 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端口广播 InetSocketAddress socketAddress = new InetSocketAddress(group,port); NetworkInterface networkInterface = NetworkInterface.getByInetAddress(group); socket.joinGroup(sockedAddress,networkInterface); //加入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) {} } } }
- 例子的示意图
- 例子的流程图