网络编程
第三阶段
★★★第十九章 网络编程
InetAddress
InetAddress
用于表示Internet Protocol(IP)
地址。public class InetAddress implements java.io.Serializable
InetAddress
的常用方法:public static InetAddress getLocalHost()
:获取本机的InetAddress
对象。public static InetAddress getByName(String host)
:根据指定的主机名/域名获取ip
地址对象。getHostName
:获取InetAddress
对象的主机名/域名。getHostAddress
:获取InetAddress
对象的地址。
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
30package com.f.chapter21.inetaddress_;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author fzy
* @date 2023/7/14 11:56
* 演示 InetAddress 类的使用
*/
public class InetAddress_API {
public static void main(String[] args) throws UnknownHostException {
//1.`public static InetAddress getLocalHost()`:获取本机的 `InetAddress` 对象。
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost); //Running-Noob/192.168.177.1
//2.`public static InetAddress getByName(String host)`:根据指定的主机名/域名获取 `ip` 地址对象。
InetAddress host1 = InetAddress.getByName("Running-Noob");
System.out.println(host1); //Running-Noob/192.168.177.1
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2); //www.baidu.com/112.80.248.76
//3.`getHostName`:获取 `InetAddress` 对象的主机名/域名。
System.out.println(host2.getHostName()); //www.baidu.com
//4.`getHostAddress`:获取 `InetAddress` 对象的地址。
System.out.println("IP地址:" + host2.getHostAddress()); //IP地址:112.80.248.76
}
}
Socket
- 套接字(
Socket
)开发网络应用程序被广泛采用,以至于成为事实上的标准。 - 通信的两端都要有
Socket
,是两台机器间通信的端点。 - 网络通信其实就是
Socket
间的通信。 Socket
允许程序把网络连接当成一个流,数据在两个Socket
间通过IO传输。- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
Socket
用于读写数据的方法:socket.getInputStream()
-> 返回一个InputStream
对象socket.getOutputStream()
-> 返回一个OutputStream
对象
- 一个
Socket
对象只有一个InputStream
对象和一个OutputStream
对象。
★★★TCP网络通信编程
★TCP字节流编程
案例 1(用TCP字节流编程):
- 编写一个服务器端,和一个客户端。
- 服务器端在
9999
端口监听。 - 客户端连接到服务器端,发送 “
hello, server
“,然后退出。 - 服务器端接收到客户端发送的信息,输出,并退出。

服务端:
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
43package com.f.chapter21.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 16:32
* 服务端
*/
public class SocketServer01 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.在本机的 9999 端口监听,等待连接
// 这里要求本机没有其他服务在监听9999,否则抛异常
// serverSocket可以通过 accept() 返回多个 Socket 对象[如果有多个客户端连接服务器]
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务端在 " + port + " 端口进行监听,等待连接...");
//2.当没有客户端连接 9999 端口时,程序会阻塞,等待连接。
// 如果有客户端连接,则会返回一个 Socket 对象,该对象是服务端的 Socket 对象(在图中对应绿框内的socket)
Socket socket = serverSocket.accept();
//有客户端连接 9999 端口,服务端socket:Socket[addr=/192.168.177.1,port=49534,localport=9999]
System.out.println("有客户端连接 " + port + " 端口,服务端socket:" + socket);
//3.当有客户端连接 9999 端口后,服务端通过 socket.getInputStream()
// 读取客户端写入到数据通道的数据,然后服务端对数据进行输出显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
System.out.println("读取客户端的内容如下:");
while ((readLen = inputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
System.out.println();
System.out.println("读取完毕...");
//4.关闭流对象、socket和serverSocket,避免资源浪费
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("服务端退出...");
}
}客户端:
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
30package com.f.chapter21.socket_;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 16:32
* 客户端
*/
public class SocketClient01 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.连接服务端(ip, 端口)
// 如果连接成功,生成客户端的Socket对象(在图中对应红框内的socket)
Socket socket = new Socket(InetAddress.getLocalHost(), port);
//连接服务端成功!客户端Socket:Socket[addr=Running-Noob/192.168.177.1,port=9999,localport=49534]
System.out.println("连接服务端成功!客户端Socket:" + socket);
//2.当有了Socket对象后,就可以通过socket.getOutputStream
// 得到和Socket对象关联的输出流对象,然后进行输出
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, server".getBytes());
//3.关闭流对象和socket,避免资源浪费
outputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
案例 2(用TCP字节流编程):
- 编写一个服务器端,和一个客户端。
- 服务器端在
9999
端口监听。 - 客户端连接到服务器端,发送 “
hello, server
“,并接收服务器端回发的 “hello, client
“,再退出。 - 服务器端接收到客户端发送的信息,输出,并发送 “
hello, client
“,再退出。
★注意:为了让通信双方知晓对方发送的信息是否发送完毕,应该设置一个发送结束标记,用
socket.shutdownOutput();
来设置。★注意:一旦
socket.shutdownOutput();
了,则与socket
相关联的outputStream
流就不能够再向外写东西了,否则会报错,所以socket.shutdownOutput();
的位置和关socket
输出流的位置要处理好,因为关socket
输出流的时候会有写数据的操作。服务端:
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
38package com.f.chapter21.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 20:39
*/
public class SocketServer02 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.监听 9999 端口,如果监听到有客户端请求 9999 端口,
// 就建立服务端的 Socket 对象
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
//2.建立和Socket对象相关联的输入输出流对象
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
//3.读取客户端发送的消息
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
//4.发送消息到客户端,并设置结束标记
outputStream.write("hello, client".getBytes());
socket.shutdownOutput(); //设置结束标记
//5.释放资源
outputStream.close();
inputStream.close();
socket.close();
serverSocket.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
35package com.f.chapter21.socket_;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 20:39
*/
public class SocketClient02 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.建立客户端的Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), port);
//2.建立和Socket对象相关联的输入输出流对象
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
//3.发送消息到服务端,并设置结束标记
outputStream.write("hello, server".getBytes());
socket.shutdownOutput(); //设置结束标记
//4.读取服务端发送的消息
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
//5.释放资源
inputStream.close();
outputStream.close();
socket.close();
}
}
★TCP字符流编程
案例 3(用TCP字符流编程):
- 编写一个服务器端,和一个客户端。
- 服务器端在
9999
端口监听。 - 客户端连接到服务器端,发送 “
hello, server
“,并接收服务器端回发的 “hello, client
“,再退出。 - 服务器端接收到客户端发送的信息,输出,并发送 “
hello, client
“,再退出。
在案例 2 的基础上使用转换流
InputStreamReader
和OutputStreamWriter
即可。★注意:在使用字符流编程时,记得**写入后要
flush
**,否则数据不会写入数据通道。服务端:
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
40package com.f.chapter21.socket_;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 20:39
*/
public class SocketServer03 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.监听 9999 端口,如果监听到有客户端请求 9999 端口,
// 就建立服务端的 Socket 对象
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
//2.建立和Socket对象相关联的输入输出流对象
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
//3.将TCP字节流编程转换为TCP字符流编程
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "gbk"));
//4.读取客户端发送的消息
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//5.发送消息到客户端,并设置结束标记
bufferedWriter.write("hello, client");
bufferedWriter.newLine();
bufferedWriter.flush(); //如果使用的是字符流编程,则需要手动刷新,否则数据不会写入数据通道
socket.shutdownOutput(); //设置结束标记
//6.释放资源(后打开的先关闭)
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.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
37package com.f.chapter21.socket_;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/14 20:39
*/
public class SocketClient03 {
public static void main(String[] args) throws IOException {
int port = 9999;
//1.建立客户端的Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), port);
//2.建立和Socket对象相关联的输入输出流对象
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
//3.将TCP字节流编程转换为TCP字符流编程
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "gbk"));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));
//4.发送消息到服务端,并设置结束标记
bufferedWriter.write("hello, server");
bufferedWriter.newLine();
bufferedWriter.flush(); //如果使用的是字符流编程,则需要手动刷新,否则数据不会写入数据通道
socket.shutdownOutput(); //设置结束标记
//5.读取服务端发送的消息
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//6.释放资源(后打开的先关闭)
bufferedReader.close();
bufferedWriter.close();
socket.close();
}
}
★网络上传文件
案例 4:
- 编写一个服务端,和一个客户端。
- 服务器端在
9999
端口监听。 - 客户端连接到服务端,发送一张图片
hello.jpg
。 - 服务器端接收到客户端发送的图片,保存到
src
下,发送 “收到图片” 再退出。 - 客户端接收到服务端发送的 “收到图片”,再退出。
- 该程序要求使用
StreamUtils.java
(一个自己编写的流处理工具类)。
★注意:文件一开始是在磁盘上的,所以要先把文件从磁盘读到程序内存中,然后再进行服务端和客户端之间的传输。
服务端:
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
44package com.f.chapter21.socket_;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/15 13:22
* 文件传输的服务端
*/
public class FileDownloadServer {
public static void main(String[] args) throws IOException {
int port = 9999;
String savePath = "file\\copy.jpg"; //文件保存的位置
//1.得到服务端的Socket对象
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务端正在 " + port + " 端口监听...");
Socket socket = serverSocket.accept();
//2.得到Socket对象相关联的输入输出流,用于数据的传输
InputStream socketInputStream = socket.getInputStream();
OutputStream socketOutputStream = socket.getOutputStream();
BufferedInputStream socketBufferedInputStream = new BufferedInputStream(socketInputStream);
BufferedWriter socketBufferedWriter = new BufferedWriter(new OutputStreamWriter(socketOutputStream));
//3.得到要保存的文件的输出流
BufferedOutputStream fileBufferedOutputStream = new BufferedOutputStream(new FileOutputStream(savePath));
//4.服务端从客户端接收数据,并将数据写入到保存文件中
byte[] fileBytes = StreamUtils.streamToByteArray(socketBufferedInputStream); //文件对应的字节数组
fileBufferedOutputStream.write(fileBytes);
//5.服务端将消息发送给客户端
socketBufferedWriter.write("收到图片...");
//如果没有flush将数据写入数据通道,则在socketBufferedWriter.close();时会报异常:
// Cannot send after socket shutdown: socket write error
// 因为数据还并没有写入到数据通道中
socketBufferedWriter.flush();
socket.shutdownOutput(); //设置结束标记
//6.释放资源
socketBufferedWriter.close();
fileBufferedOutputStream.close();
socketBufferedInputStream.close();
socket.close();
serverSocket.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
40package com.f.chapter21.socket_;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author fzy
* @date 2023/7/15 13:21
* 文件传输的客户端
*/
public class FileUploadClient {
public static void main(String[] args) throws IOException {
int port = 9999;
String srcFilePath = "C:\\Users\\1\\Doc\\入党相关\\会议背景.jpg";
//1.得到客户端的Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), port);
//2.得到Socket对象相关联的输入输出流,用于数据的传输
OutputStream socketOutputStream = socket.getOutputStream();
InputStream socketInputStream = socket.getInputStream();
BufferedOutputStream socketBufferedOutputStream = new BufferedOutputStream(socketOutputStream);
BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(socketInputStream));
//3.得到要发送的文件的输入流
BufferedInputStream fileBufferedInputStream = new BufferedInputStream(new FileInputStream(srcFilePath));
//4.客户端将文件数据发送给服务端
byte[] fileBytes = StreamUtils.streamToByteArray(fileBufferedInputStream); //文件对应的字节数组
socketBufferedOutputStream.write(fileBytes);
socket.shutdownOutput(); //设置结束标记
//5.客户端从服务端接收“收到文件”的消息
String line = null;
while ((line = socketBufferedReader.readLine()) != null) {
System.out.println(line);
}
//6.释放资源
socketBufferedReader.close();
socketBufferedOutputStream.close();
fileBufferedInputStream.close();
socket.close();
}
}StreamUtils
工具类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.f.chapter21.socket_;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author fzy
* @date 2023/7/15 14:20
*/
public class StreamUtils {
public static byte[] streamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = is.read(buf)) != -1) {
bos.write(buf, 0, readLen); //把读取到的数据写入bos
}
byte[] bytes = bos.toByteArray(); //将bos中的数据转换为字节数组
bos.close();
return bytes;
}
}
netstat
netstat -an
可以查看当前主机网络情况,包括端口监听情况和网络连接情况。LISTENING
表示某个端口在监听。如果有一个外部程序连接到该端口,就会显示一条连接信息,状态变为
ESTABLISHED
。当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是
TCP/IP
来分配的,是不确定的,是随机的。
UDP网络编程
- 类
DatagramSocket
和DatagramPacket
实现了基于UDP
协议网络程序。 UDP
数据报DatagramPacket
通过数据报套接字DatagramSocket
发送和接收,系统不保证UDP
数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。DatagramPacket
对象封装了UDP
数据报,在数据报中包含了发送端的IP
地址和端口号以及接收端的IP
地址和端口号。UDP
协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。
注意:
UDP
没有明确的服务端和客户端,而是数据的发送端和接收端,而且数据的发送端和接收端是可能变化的。- 接收数据和发送数据是通过
DatagramSocket
对象来完成的。 - 数据是封装在
DatagramPacket
对象中的,发送数据时要将数据 “装包” 成数据报,接收数据时要将数据报 “拆包” 成数据。

案例:
- 编写一个接收端
A
和一个发送端B
。 - 接收端
A
在9999
端口等待接收数据(receive
)。 - 发送端
B
向接收端A
发送数据 “hello, 明天吃火锅~
“。 - 接收端
A
接收到发送端B
发送的数据,回复 “好的,明天见
”,再退出。 - 发送端
B
接收回复的数据,再退出。
发送端:
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
42package com.f.chapter21.udpsocket_;
import java.io.IOException;
import java.net.*;
/**
* @author fzy
* @date 2023/7/16 14:54
*/
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1.创建DatagramSocket对象,准备发送数据
int port = 9998; //发送端和接收端的端口不要重复,该端口是该DatagramSocket对象接收数据时用的端口
DatagramSocket sendSocket = new DatagramSocket(port);
//2.将需要发送的数据封装到 DatagramPacket 对象中
byte[] buf = "hello, 明天吃火锅~".getBytes();
/*创建要发送的DatagramPacket对象时,传入的参数依次是:
* buf:数据包数据。
* offset:分组数据偏移量。
* length:分组数据长度。
* address:目的地址。
* port:目的端口号。
* */
DatagramPacket sendPacket = new DatagramPacket(buf, 0, buf.length, InetAddress.getLocalHost(), 9999);
//3.发送数据报
sendSocket.send(sendPacket);
//4.发送端接收接收端发送过来的消息
byte[] receive = new byte[64 * 1024];
DatagramPacket receivePacket = new DatagramPacket(receive, receive.length);
sendSocket.receive(receivePacket);
//5.对接收到的数据报进行拆包,取出其中的数据并显示
System.out.println("接收到接收端的回复:");
byte[] data = receivePacket.getData();
int length = receivePacket.getLength();
System.out.println(new String(data, 0, length));
//System.out.println(receivePacket.getAddress()); //接收方的地址
//System.out.println(receivePacket.getPort()); //接收方的端口
//6.关闭资源
sendSocket.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
42package com.f.chapter21.udpsocket_;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author fzy
* @date 2023/7/16 14:54
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket对象,准备在 9999 端口接收数据
int port = 9999;
DatagramSocket receiveSocket = new DatagramSocket(port);
//2.构建一个DatagramPacket对象,准备接收数据
// UDP数据报的大小限制在64K内
byte[] buf = new byte[64 * 1024];
/*创建接收数据报的DatagramPacket对象时,传入的参数依次是:
* buf - 用于保存传入数据报的缓冲区。
* length - 要读取的字节数。
* */
//因为不知道对方发送过来的数据报的大小,所以传入的length参数为buf.length
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
//3.调用接收方法,将通过网络传输的DatagramPacket对象填充到receivePacket中去
System.out.println("接收端A等待接收数据...");
receiveSocket.receive(receivePacket);
//4.对receivePacket进行拆包,取出数据并显示
int length = receivePacket.getLength(); //实际接收到的数据长度
byte[] data = receivePacket.getData(); //接收到的数据
System.out.println(new String(buf, 0, length));
//System.out.println(receivePacket.getAddress()); //发送方的地址
//System.out.println(receivePacket.getPort()); //发送方的端口
//5.接收端向发送端回复消息
byte[] response = "好的,明天见".getBytes();
//receivePacket中含有发送端的IP地址和端口号
DatagramPacket sendPacket = new DatagramPacket(response, 0, response.length, receivePacket.getAddress(), receivePacket.getPort());
receiveSocket.send(sendPacket);
//6.关闭资源
receiveSocket.close();
}
}
- 编写一个接收端