0%

Java基础—网络编程

此部分记录了使用Java进行网络编程所使用的基本类与方法,共分为「InetAddress类、UDP通信、TCP通信」三个基础部分,并通过两个实例展示「自定键盘输入、字符缓冲流封装、反馈、从文件到文件的发送接收」展示IO流与网络编程结合的拓展使用方法。

InetAddress类

此类的主要作用是产生主机的对象,拥有如下方法:

使用时,通常不会单独用构造函数创建一个对象,而是直接使用这个静态函数当作参数

方法名 类型 说明
static InetAddress.getByName(String host) 构造函数 确定主机名称的IP地址。
host 可以是机器名称,也可以是IP地址
String getHostName() 方法 获取此IP地址的主机名
String getHostAddress() 方法 返回文本显示中的IP地址字符串

UDP通信

概念

  • 它在通信的两端各建立一个Socket对象,没有所谓的客户端和服务器
  • Java提供了DatagramSocket类作为基于UDP协议的Socket

UDP发送数据

构造方法

方法名 说明
DatagramSocket() 创建【数据报套接字】,并将其绑定到本机地址上的任何可用端口
DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建【数据包】,发送长度为len的数据包到指定主机的指定端口

相关方法

对应类 方法名 说明
DatagramSocket void send(DatagramPacket p) 发送数据报包
DatagramSocket void close() 关闭数据报套接字

表格描述

操作中需要两个对象:

  • 发送端对象(DatagramSocket):负责提供发送的方法
  • 数据包对象(DatagramPacket):负责指定数据包「内容、接收端主机对象、端口」
操作位置 操作方式 方法名/类名 说明
主类 new DatagramSocket() 创建发送端的Socket对象
定义 byte[] bys = “xxx”.getBytes(); 创建数据
new DatagramPacket(byte[] buf,int len,InetAddress add,int port) 数据打包
调用 void send(DatagramPacket p) 调用DatagramSocket对象方法发送数据
调用 ds.close(); 关闭接收端

代码描述

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();

//创建数据,并把数据打包
byte[] bys = "Udp 示例".getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.3"), 10086);

//调用DatagramSocket对象的方法发送数据
ds.send(dp);

//关闭发送端
ds.close();

UDP接收数据


构造方法

方法名 说明
DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包

DatagramPacket相关方法

对应类 方法名 说明
DatagramPacket byte[] getData() 返回数据缓冲区
DatagramPacket int getLength() 返回要发送的数据的长度或接收的数据的长度
DatagramSocket void receive(DatagramPacket p) 从此套接字接受数据报包给到DatagramPacket

表格描述

操作位置 操作方式 方法名/类名 说明
主类 new DatagramPacket(byte[] buf, int len) 创建接收端的Socket对象
创建 byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
创建数据包,用于接收数据
调用 ds.receive(dp); 调用DatagramSocket对象的方法接收数据(上一节)
匿名内部类/new String new String(dp.getData(), 0,dp.getLength()) 解析数据
调用 ds.close(); 关闭接收端

代码描述

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 class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10086);

//创建一个【数据包】,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);

//调用DatagramSocket对象的方法接收数据
ds.receive(dp);

//---------------啰嗦写法-----------
//解析【数据包】,取出【数据包】中的字节数组并把数据在控制台显示
byte[] datas = dp.getData();//创建一个字节数组用于接收【数据包】内的字节数组
//获取实际发送数据的长度
int len = dp.getLength();
//开始解析
String dataString = new String(datas, 0, len);//排除多余空字符
System.out.println(dataString);
//---------------啰嗦写法 End-----------
//简化写法:
System.out.println(new String(dp.getData(), 0,dp.getLength()));

//关闭接收端
ds.close();
}
}

TCP通信程序

Java中的TCP通信

  • Java使用Socket对象代表两端的通信端口,并通过Socket产生IO流来通信。
  • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

TCP发送数据

表格描述

操作位置 操作方式 方法名/类名 说明
主类 new Socket(String host, int port) 创建客户端Socket对象
调用Socket方法 OutputStream getOutputStream() 获取输出流…
调用OutputStream方法 void write(“TCP 示例”.getBytes()) …写数据
调用Socket方法 void close() 释放资源

※释放资源只要释放最顶层的对象,衍生的对象也会释放

代码描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
//Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
Socket s = new Socket("127.0.0.1", 10088);


//获取输出流,写数据
//OutputStream getOutputStream() 返回此套接字的输出流
OutputStream os = s.getOutputStream();
os.write("hello,tcp!!".getBytes());

//释放资源
s.close();
}
}

TCP接收数据

表格描述

操作位置 操作方式 方法名/类名 说明
主类 new ServerSocket(int port) 创建服务器端的Socket对象(ServerSocket)
调用ServerSocket方法 Socket accept() 侦听连接到此的套接字
接收成为【socket】对象
调用Socket方法 InputStream getInputStream() 获取输入流…
调用InputStream方法 byte[] bys = new byte[1024];
int len = is.read(bys)
…读数据…
new/匿名内部类 String new String(bys,0,len) …数据处理
调用Socket与ServerSocket方法 close() 释放资源

※此处「字节流读数据」不能使用while循环

拓展操作实例(以TCP为例)

自定键盘输入+封装字符缓冲流 发送

案例需求

客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束

服务端:接收到数据在控制台输出

案例分析

客户端创建对象,使用键盘录入循环接受数据,接受一行发送一行,直到键盘录入886为止

服务端创建对象,使用输入流按行循环接受数据,直到接受到null为止

代码实现

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ClientDemo {
public static void main(String[] args) throws IOException {
//手寫鍵盤輸入為字符輸出流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Socket s = new Socket("127.0.0.1", 10086);
//通信的IO流封裝字節輸出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

String line;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();

if(line.equals("886")){
break;
}
}
s.close();
br.close();
}
}

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
Socket s = ss.accept();
//封裝字符輸入流
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));

String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
ss.close();
}
}

文件到文件+反馈+多线程

案例需求

客户端:数据来自于文本文件,接收服务器反馈

服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程

案例分析

创建客户端对象,创建输入流对象指向文件,每读入一行数据就给服务器输出一行数据,输出结束后使 用shutdownOutput()方法告知服务端传输结束

创建多线程类,在run()方法中读取客户端发送的数据,为了防止文件重名,使用计数器给文件名编号, 接受结束后使用输出流给客户端发送反馈信息。

创建服务端对象,每监听到一个客户端则开启一个新的线程接受数据。 客户端接受服务端的回馈信息

代码实现

客户端

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
import java.io.*;
import java.net.Socket;

public class ClientDemo {
public static void main(String[] args) throws IOException {
//客户端对象+字符流
Socket s = new Socket("127.0.0.1", 10090);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

//文件输入流对象+发送文件
BufferedReader br = new BufferedReader(new FileReader("C:\\test\\ArrayListToFileDemo.txt"));

String line;
while ((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}

s.shutdownOutput();

//接收反馈
BufferedReader receive = new BufferedReader(new InputStreamReader(s.getInputStream()));
String receiveLine = receive.readLine();
System.out.println(receiveLine);

s.close();
br.close();
}
}

服务端线程类

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
import java.io.*;
import java.net.Socket;

public class ServerThread implements Runnable {
//接收传入的对象
private Socket s;

public ServerThread(Socket s) {
this.s = s;
}

@Override
public void run() {
//排除文件重名问题
int i = 0;
File file = new File("C:\\test\\TCPTHREAD" + i + ".txt");
while (file.exists()) {
i++;
file = new File("C:\\test\\TCPTHREAD" + i + ".txt");
}

try {
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
//追加写入
BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
bw.write(line);
bw.newLine();
bw.flush();
bw.close();
}

//反馈
BufferedWriter back = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
back.write("接收到文件" + i);
back.newLine();
back.flush();

s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.*;
import java.net.ServerSocket;

public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器Socket对象
ServerSocket ss = new ServerSocket(10090);

while(true) {
//美接收到一个 ss.accept() 就创建并开启线程
new Thread(new ServerThread(ss.accept())).start();
}
}
}

实例说明

  • 线程类中:不可以在排除文件重名时创建文件,否则会一直循环。如果要创建,需要利用if去break
  • 线程类中:之所以使用追加写入,就是为了能释放资源
  • 服务端类中:开启线程使用的是缩略写法
  • 此实例中如果排除“多线程”的话写法如下,可以更清晰的感知服务端的写法:
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
public class ServerDemo {
public static void main(String[] args) throws IOException {
//标准服务端操作(SS对象+侦听+封装)
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));

//文件写入封装
BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\test\\TCPWriterNoTwo.txt"));

String line;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}

//反馈
BufferedWriter back = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
back.write("接收完成");
back.newLine();
back.flush();

//释放资源
ss.close();
br.close();

}
}