Java基础(下)之网络编程
网络编程
什么是网络编程
在网络通信协议下,不同设备上运行的程序,进行数据传输。Java中可以使用Java.net包开发网络应用程序。

网络编程三要素
- IP:设备在网络中的地址,是唯一标识
- 端口号:程序在设备中的唯一标识
- 协议:数据在网络中传输的规则,如TCP、UDP、HTTP、FTP、MQTT等
InetAddress 表示IP地址的类
class Main {
public static void main(String[] args) throws UnknownHostException {
InetAddress device = InetAddress.getByName("ZJQ");//获取网络上设备的对象,名称可以是机器名称,也可以是IP地址
System.out.println(device);
System.out.println(device.getHostName());//获取该设备对象的主机名,若设备不存在/获取不到,则返回IP地址
System.out.println(device.getHostAddress());//获取该设备对象的IP地址
}
}
UDP通信程序
单播
发送数据

public class UDPSender {
static void main(String[] args) throws IOException, InterruptedException {
//1.创建本机的套接字对象 DatagramSocket
DatagramSocket ds = new DatagramSocket(5678);//参数为本机发出数据的端口,空参:随机选择一个可用端口;有参:指定端口进行绑定
//2.打包要发送的数据报 Packet:填写内容及目标套接字(IP:Port)
String str = "Hello,NetWork!";
byte[] data = str.getBytes();
InetAddress targetAdd = InetAddress.getByName("ZJQ");
int targetPort = 1234;//目标地址的端口,即目标机器监听并接收的端口
DatagramPacket dp = new DatagramPacket(data,data.length,targetAdd,targetPort);
//3.发送数据
for (int i = 0; i < 10; i++) {
ds.send(dp);
System.out.println("Send a packet");
Thread.sleep(500);
}
//4.释放资源
ds.close();
}
}
接收数据
public class UDPReceiver {
static void main(String[] args) throws IOException {
//1.创建本机上接收/监听的套接字对象DatagramSocket
int listenPort = 1234;//监听本机的端口,数据会发到这个端口来
DatagramSocket ds = new DatagramSocket(listenPort);
//2.创建用于接收数据的数据报对象DatagramPacket,
byte[] data = new byte[1024];
InetAddress listenAddr = InetAddress.getByName("ZJQ");
//接收端的数据报仅需分配内存空间,无需指定地址和端口
DatagramPacket dp = new DatagramPacket(data,data.length);
//3.接收并解析数据
while(true){
ds.receive(dp);//接收数据报,同步方法,收不到就阻塞,直到收到数据
byte[] rev_data = dp.getData();//数据报中的数据
int len = dp.getLength();//数据长度
InetAddress send_addr = dp.getAddress(); //数据报发送方的IP
int send_port = dp.getPort(); //数据报发送方的端口
String str = new String(rev_data,0,len);//编码为字符串
System.out.println("接收到来自" + send_addr + ":" + send_port + "的数据:" + str);
}
}
}
组播
组播地址:224.0.0.0 ~ 239.255.255.255,其中224.0.0.0 ~ 224.0.0.255为预留的组播地址。
示例:通过组播方式写一个群发程序
UDPSender.java
package com.example.helloworld;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPSender {
static void main(String[] args) throws IOException, InterruptedException {
//1.创建本机的套接字对象 MulticastSocket
MulticastSocket ms = new MulticastSocket(12345);//参数为本机发出数据的端口,空参:随机选择一个可用端口;有参:指定端口进行绑定
Scanner sc = new Scanner(System.in);
//2.打包要发送的数据报 Packet:填写内容及目标套接字(IP:Port)
InetAddress targetAdd = InetAddress.getByName("224.0.0.1");
int targetPort = 54321;
while(true){
String str = sc.nextLine();
if("886".equals(str)){
break;
}
byte[] data = str.getBytes();
DatagramPacket dp = new DatagramPacket(data,data.length,targetAdd,targetPort);
ms.send(dp);
}
System.out.println("欢迎下次使用!");
ms.close();
}
}
UDPReceiver.java
package com.example.helloworld;
import java.io.IOException;
import java.net.*;
public class UDPReceiver {
static void main(String[] args) throws IOException {
//1.创建本机上接收/监听组播数据的套接字对象MulticastSocket
InetAddress listenAddr = InetAddress.getByName("224.0.0.1");
int listenPort = 54321;//监听本机的端口,数据会发到这个端口来
MulticastSocket ms = new MulticastSocket(listenPort);
//2.将当前本机添加到组播这一组当中
ms.joinGroup(listenAddr);
//3.创建用于接收数据的数据报对象DatagramPacket,接收端的数据报仅需分配内存空间,无需指定地址和端口
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data,data.length);
//4.接收并解析数据
while(true){
ms.receive(dp);//接收数据包,同步方法,收不到就阻塞,直到收到数据
byte[] rev_data = dp.getData();//数据包中的数据
int len = dp.getLength();//数据长度
InetAddress send_addr = dp.getAddress(); //数据包发送方的IP
int send_port = dp.getPort(); //数据包发送方的端口
String str = new String(rev_data,0,len);//编码为字符串
System.out.println("接收到来自" + send_addr + ":" + send_port + "的数据:" + str);
}
}
}
广播
广播地址:255.255.255.255
广播不需要修改socket类,将单播代码中发送地址修改即可
TCP通信程序

基本流程

示例程序
客户端发送,服务器接收
TCPServer.java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象,等待客户端来连接,因此无需传入IP地址
ServerSocket ss = new ServerSocket(12345);
while(true){
boolean exit = false;
//2.监听端口,等待客户端连接,若没有则阻塞直到有客户端来连接,此时会获取到客户端的socket对象
Socket socket = ss.accept();
System.out.println("有客户端连接:" + socket.getInetAddress().getHostAddress());
//3.有客户端连接后,需要获取socket对象的输入流,本例中传输的是字符串信息,因此使用缓冲字符流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//4.从流中读取数据
char[] data = new char[1024];
int len;
while((len = br.read(data)) != -1){
String str = new String(data,0,len);
System.out.println("收到客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "的请求数据:" + str);
if("886".equals(str)){
exit = true;
break;
}
}
if(exit) break;
}
// 5.断开连接,关闭服务器ServerSocket对象,释放资源
ss.close();
System.out.println("Bye");
}
}
TCPClient.java
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
Socket socket = new Socket("192.168.3.108",12345);
//2. 连接成功后需要获取socket的输出流,通过输出流发送数据,本例中传输的是字符串信息,因此使用缓冲字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//3.发送数据
Scanner sc = new Scanner(System.in);
while(true){
String str = sc.nextLine();
bw.write(str);
bw.flush();//注意:必须立刻刷新,否则数据不能及时传输
if("886".equals(str)) break;
}
//4.断开连接,释放资源
socket.close();
System.out.println("Bye");
}
}
改进:客户端和服务器均可收发,且客户端断开连接后服务器仍继续监听,等待下一个客户端连接
TCPServer.java
package com.example.helloworld;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPServer {
static boolean connecting = false;
static boolean running = true;
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象,等待客户端来连接,因此无需传入IP地址
ServerSocket ss = new ServerSocket(12345);
Scanner sc = new Scanner(System.in);
while(running){
Socket socket = ss.accept();
System.out.println("有客户端连接:" + socket.getInetAddress().getHostAddress());
TCPServer.connecting = true;
//启动监听线程,以接收服务器消息
Thread th_listen = new Thread(new Runnable() {
@Override
public void run() {
try {
listen(socket);
} catch (IOException e) {
if (TCPServer.connecting) {
System.err.println("网络读取异常: " + e.getMessage());
} else {
System.out.println("监听线程正常结束。");
}
}
}
});
th_listen.start();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (TCPServer.connecting) {
String str = sc.nextLine();
bw.write(str);
bw.newLine();
bw.flush();
if ("886".equals(str)) {
running = false;
break;
}
}
socket.close();
}
// 5.断开连接,关闭服务器ServerSocket对象,释放资源
ss.close();
System.out.println("Bye");
}
public static void listen(Socket socket) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str;
while (TCPClient.running && (str = br.readLine()) != null) { //对 TCP 连接 EOF(服务端正常关闭 FIN)的严谨判断
System.out.println("收到客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "消息:" + str);
if ("886".equals(str)) {
TCPServer.connecting = false;
}
}
System.out.println("监听线程已退出");
}
}
TCPClient.java
package com.example.helloworld;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
static volatile boolean running = true;
public static void main(String[] args) throws IOException {
//1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
Socket socket = new Socket("192.168.3.107",12345);
//2. 连接成功后需要获取socket的输出流,通过输出流发送数据,本例中传输的是字符串信息,因此使用缓冲字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//启动监听线程,以接收服务器消息
Thread th_listen = new Thread(new Runnable() {
@Override
public void run() {
try {
listen(socket);
} catch (IOException e) {
if (running) {
System.err.println("网络读取异常: " + e.getMessage());
} else {
System.out.println("监听线程正常结束。");
}
}
}
});
th_listen.start();
//3.发送数据
Scanner sc = new Scanner(System.in);
while(TCPClient.running){
String str = sc.nextLine();
bw.write(str);
bw.newLine();
bw.flush();//注意:必须立刻刷新,否则数据不能及时传输
if("886".equals(str)) TCPClient.running = false;
}
//4.断开连接,释放资源
socket.close();
System.out.println("Bye");
}
public static void listen(Socket socket) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str;
while (TCPClient.running && (str = br.readLine()) != null) {
System.out.println("收到服务器消息:" + str);
}
br.close();
}
}
客户端上传文件至服务器
客户端:将本地文件上传到服务器,接收服务器的反馈信息
服务器:接收客户端上传的文件并保存,接收完成后给出反馈信息,且能够支持多个客户端同时上传文件(使用多线程或线程池)
TCPClient.java
package com.example.helloworld;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
static volatile boolean running = true;
public static void main(String[] args) throws IOException {
//1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
Socket socket = new Socket("192.168.3.107",12345);
System.out.println("Conneced to " + socket.getInetAddress().getHostAddress() + ":" + socket.getPort());
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
File file = new File("data\\a.txt");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
byte[] buf = new byte[1024];
int len;
while((len = bis.read(buf)) != -1){
bos.write(buf,0,len);
bos.flush();
}
System.out.println("File uploaded!");
bis.close();
socket.shutdownOutput();//向服务器写出结束标记
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str = br.readLine();
System.out.println("服务器消息:" + str);
socket.close();
}
}
TCPServer.java
package com.example.helloworld;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(12345);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
9,
9,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
while(true){
Socket socket = ss.accept();
System.out.println("Request from " + socket.getInetAddress().getHostAddress());
MyThread thread = new MyThread(socket);
pool.submit(thread);
}
}
}
class MyThread implements Runnable{
public MyThread(Socket socket) {
this.socket = socket;
}
private Socket socket;
@Override
public void run() {
try {
String filename = UUID.randomUUID().toString().replace("-","");
File file = new File("data",filename + ".jar");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
BufferedInputStream net_bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream net_bos = new BufferedOutputStream(socket.getOutputStream());
byte[] buf = new byte[1024];
int len;
while((len = net_bis.read(buf)) != -1){
bos.write(buf,0,len);
bos.flush();
}
String str = "Finished!";
net_bos.write(str.getBytes());
net_bos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
建立连接过程:三次握手

思考:为什么需要第三次握手?
断开连接过程:四次挥手

思考:网上需要第四次挥手?

浙公网安备 33010602011771号