Java网络编程
InetAddress类
InetAddress类是IP地址封装类,该类并没有公共的构造方法,但是我们可以通过该类的一些静态方法来获取对象实例,然后再通过这些对象实例来对IP地址或主机名进行操作。
//InetAddress.getByName(主机名):根据主机名创建InetAddress对象
InetAddress address=InetAddress.getByName("www.baidu.com");
//InetAddress.getByAddress(byte[] address):根据IP地址创建InetAddress对象
InetAddress address=InetAddress.getByAddress(byte[] address);
/*
因为考虑到IP地址对应域名或主机名的负载平衡,所以一个主机名或域名可能存在许多个IP地址。
InetAddress.getAllByName(主机名):通过主机名创建所有的InerAddress对象,返回InerAddress对象数组。
*/
InetAddress[] addresses = InetAddress.getAllByName("www.baidu.com");
//getHostAddress():取得当前InetAddress对象的IP地址
System.out.println("IP地址为:"+address.getHostAddress());
//getHostName():取得当前InetAddress对象的主机名
System.out.println("主机名为:"+address.getHostName());
InetAddress.getLocalHost()//获取本机的InetAddress对象
URL类和URLConnection
如果说IP地址唯一标识了Internet上的计算机,那么URL则标识了计算机上的资源。URL是一个集成了传输协议、主机名称、文件名称等信息的字符串。
而URLConnection类的对象可以由URL对象的openConnection()方法创建,相当于打开了通往URL所标识资源的链接,我们可以通过从此链接中获取对应的输入输出流来对资源进行读取和写入,常用于文件的下载和上传。
/*
* 创建一个URL对象
* 它包含了http协议、域名:www.baidu.com、文件:index.html信息
* */
URL url = new URL("http://www.baidu.com/index.html");
/*
* 打开通往所标识资源的链接
* */
URLConnection connection = url.openConnection();
//获取相应的输出输入流
connection.getOutputStream();
connection.getInputStream();
TCP/IP与Socket
TCP/IP是一组网络传输的协议,它是面向连接的。两台计算机的程序之间要建立TCP/IP通信,需要一个Socket套接字来接收信息包或者将信息包发送给指定IP地址与端口的计算机程序。
服务器端的套接字为ServerSocket,客户端的套接字为Socket。
如果网络中存在服务器和多个客户端进行同时通信。那么此时的Socket就像一个电器的插头,而客户端就好比家里面各种电器,服务器就是供电机并提供了插座(ServerSocket)。
当电器(客户端)需要工作(通信)时就将插头(Socket)插入供电机(服务器)的插座口(ServerSocket对象的accept()方法得到与客户端Socket的连接)来获取电力,而每个插座口通过插头都能指定的连接了一个电器。
我们通过一个简单的客户端、服务器聊天代码来介绍Socket编程中的各个部分
服务器端
//服务端套接字,给定一个端口号
ServerSocket serverSocket = new ServerSocket(5000);
/*
* accept()方法会阻塞等待客户端的连接
* 当连接成功时返回一个包含客户端信息的Socket对象
* */
Socket socket = serverSocket.accept();
//创建一个获取控制台输入的扫描器实例对象,用于输入聊天信息
Scanner sout = new Scanner(System.in,"utf-8");
/*
* 从Socket对象中获取对客户端程序进行通信的输入输出流并进行包装
* */
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
半关闭:单独关闭从Socket对象中获取的输入流或者输出流称为半关闭,我们使用socket.shutdownOutput()或者socket.shutdownInput()来实现这种操作。
ps:在Socket输入输出流中,如果对方程序不明确关闭输出流(即使用socket.shutdownOutput()方法)那么本方的输入流read()方法就无法得知数据已经输入完毕,从而无限等待发生阻塞。但是如果使用半关闭输出流,我们就无法继续获取发送过来的信息,所以我们通常开辟出一个线程来对数据进行读取。
//接收数据信息
new Thread(()->{
boolean flag = true;
String str = null;
while(flag && !socket.isInputShutdown() && !socket.isClosed()){
try {
str = in.readLine();
System.out.println("客户端:"+str);
//当获取到EXIT字符串时时通信结束退出循环
if(str.equals("EXIT") && !socket.isClosed()) {
flag = false;
socket.shutdownInput();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//当前线程发送数据信息
boolean flag = true;
while(flag && !socket.isOutputShutdown()){
String str = sout.nextLine();
out.write(str+"\n");
out.flush();
if(str.equals("EXIT")){
flag = false;
socket.shutdownOutput();
}
}
in.close();
out.close();
socket.close();
客户端
/*
* 指定服务器IP地址和端口号创建套接字连接
* */
Socket socket = new Socket("localhost",5000);
//创建一个获取控制台输入的扫描器实例对象,用于输入聊天信息
Scanner sout = new Scanner(System.in,"utf-8");
/*
* 从Socket对象中获取对客户端程序进行通信的输入输出流并进行包装
* */
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
//接收数据
new Thread(()->{
boolean flag = true;
String str = null;
while(flag && !socket.isInputShutdown() && !socket.isClosed()){
try {
str = in.readLine();
System.out.println("服务器:"+str);
if(str.equals("EXIT") && !socket.isClosed()) {
flag = false;
socket.shutdownInput();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//当前线程发送数据
boolean flag = true;
while(flag && !socket.isOutputShutdown() ){
String str = sout.nextLine();
out.write(str+"\n");
out.flush();
if(str.equals("EXIT")){
flag = false;
socket.shutdownOutput();
}
}
in.close();
out.close();
socket.close();
从套接字Socket对象除了获取输入输出流之外,我们还可以获取两个重要信息。
- socket.getInetAddress(); //获取套接字来源的InetAddress对象。
- socket.getPort();//获取套接字来源的端口号信息
三次握手和四次挥手
基于面向连接的协议大多数都时通过三次握手和四次挥手的,因为这样安全可靠。这两者一个用于建立连接时一个用于断开连接时,下面将介绍这两者的含义。
三次握手
- 第一次:客户端发送请求连接信号给服务器。如果第一次握手客户端传过来的连接请求被服务器接收到,那么服务器就知道客户端的发送功能正常,自己的接收功能正常,然后进行第二次握手。
- 第二次:服务器向客户端发送允许连接的信号。如果客户端接收到来自服务器的允许连接请求,那么客户端就知道自己发送功能正常(因为从服务器反馈了信息回来),自己接收功能正常,服务器接收功能正常,服务器发送功能正常,然后进行第三次握手。
- 第三次:客户端向服务器发送进行连接的信号。如果服务器接受到来自客户端的进行连接信号,那么服务器就知道自己的发送功能正常,客户端的接收功能正常。
所以这三次握手是让服务器与客户端双方都能完全知道对方和自己的收发能力,从而实现可靠的安全的连接。
四次挥手
- 第一次:客户端发送断开连接请求。此时客户端停止发送数据,如果第一次挥手客户端的断开连接请求被接收到,那么进行第二次挥手。
- 第二次:服务器告知客户端收到请求并让客户端进入等待。然后服务器会处理剩余数据并做好断开连接的一些准备,等这些工作完成之后进行第三次挥手。
- 第三次:服务器告知客户端已完成断开连接的准备。如果客户端收到来自服务器的通知,那么就开始第四次挥手。
- 第四次:客户端确认服务器断开连接信号并回应。如果服务器接收到来自客户端的回应信息,就正式断开连接。而客户端会经过短暂时间段后自动断开与服务器的连接。
UDP
UDP(用户数据报协议)是一种面向无连接的通信协议。即它不能保证数据能被对方接收,也不能保证数据抵达的顺序,相反它比TCP协议要快许多。
使用UDP通信,不存在连接的概念,通常只有发送方和接收方。我们需要创建一个DatagramPacket对象,这个对象代表一个数据包,这个数据包包含了目的IP地址、目的端口和数据内容等信息。然后我们通过DatagramSocket对象来进行数据包的发送和接收。
发送方
/*
* 创建DatagramSocket(数据包套接字)对象用来发送数据包。
* */
DatagramSocket socket = new DatagramSocket();
/*
* 获取本机的IP地址对应的InetAddress对象
* */
InetAddress address = InetAddress.getByName("localhost");
/*
* 创建表示数据内容的字节数组
* */
byte[] bytes = "example content".getBytes();
/*
* new DatagramPacket(数据字节数组,数组的长度,目的地址的InetAddress对象,目的端口号)
* */
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,address,6000);
/*
* 发送数据包
* */
socket.send(packet);
socket.close();
接收方
/*
* 创建DatagramSocket对象并指明端口号(对应发送方的目的端口号)
* */
DatagramSocket socket = new DatagramSocket(6000);
/*
* 创建一个字节数组用于存储从接收到的数据包中所获得的数据
* */
byte[] bytes = new byte[1024];
/*
* 创建数据包对象,传入上面创建好的数组和数组长度
* */
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
/*
* 接收数据到数据包中
* */
socket.receive(packet);
/*
* 读取并写出到控制台中
* */
System.out.println(new String(bytes,0,bytes.length));