socket
我们可以通过Socket技术(它是计算机之间进行通信的一种约定或一种方式),实现两台计算机之间的通信,Socket也被翻译为套接字
,是操作系统底层提供的一项通信技术,它支持TCP和UDP。而Java就对Socket底层支持进行了一套完整的封装,我们可以通过Java来轻松实现Socket通信。
要实现Socket通信,我们必须创建一个数据发送者和一个数据接收者,也就是客户端和服务端,我们需要提前启动服务端,来等待客户端的连接,而客户端只需要随时启动去连接服务端即可,它们默认采用的是TCP协议进行连接。
socket实现双向通信(Tcp)
主线程用于发送消息,子线程用于接收消息。
服务端:
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 46 47 48 49 50 51 52 53 54
| package com.dmw.socket;
import java.io.*; import java.net.*;
public class Server { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器已启动,等待客户端连接...");
Socket socket = serverSocket.accept(); System.out.println("客户端已连接,IP地址:" + socket.getInetAddress().getHostAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
Thread receiveThread = new Thread(() -> { try { String clientMessage; while ((clientMessage = in.readLine()) != null) { System.out.println("客户端说: " + clientMessage); } } catch (IOException e) { System.out.println("与客户端的连接已断开"); } }); receiveThread.start();
String serverMessage; while ((serverMessage = systemIn.readLine()) != null) { out.println(serverMessage); if ("bye".equalsIgnoreCase(serverMessage)) { break; } }
receiveThread.interrupt(); in.close(); out.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
客户端:
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 46 47 48 49 50 51 52 53
| package com.dmw.socket;
import java.io.*; import java.net.*;
public class Client { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 8888); System.out.println("已连接到服务器");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
Thread receiveThread = new Thread(() -> { try { String serverMessage; while ((serverMessage = in.readLine()) != null) { System.out.println("服务器说: " + serverMessage); } } catch (IOException e) { System.out.println("与服务器的连接已断开"); } }); receiveThread.start();
String clientMessage; while ((clientMessage = systemIn.readLine()) != null) { out.println(clientMessage); if ("bye".equalsIgnoreCase(clientMessage)) { break; } }
receiveThread.interrupt(); in.close(); out.close(); socket.close(); } catch (UnknownHostException e) { System.err.println("无法找到服务器"); e.printStackTrace(); } catch (IOException e) { System.err.println("无法连接到服务器"); e.printStackTrace(); } } }
|
Socket 通信中I/O流的使用
在 Java 的 Socket 通信中,主要使用了以下几种 I/O 流(Stream) 来处理数据的输入和输出。下面详细介绍这些流的作用、特点以及它们在 Socket 通信中的应用。
1. InputStream
和 OutputStream
作用
InputStream
:用于从数据源(如 Socket、文件等)读取字节数据(byte
)。
OutputStream
:用于向目标(如 Socket、文件等)写入字节数据(byte
)。
特点
- 处理的是 原始字节(
byte
),适合二进制数据传输(如图片、文件等)。
- 是 所有字节流的基类,
Socket
通信中的 socket.getInputStream()
和 socket.getOutputStream()
返回的就是这两个流。
在 Socket 通信中的应用
1 2 3
| InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream();
|
2. BufferedReader
和 PrintWriter
作用
BufferedReader
:用于 高效读取字符数据(String
),内部有缓冲区,减少 I/O 操作次数。
PrintWriter
:用于 方便地写入格式化文本数据(String
),支持 print()
、println()
等方法。
特点
- 处理的是 字符数据(
String
),适合文本通信(如聊天消息)。
BufferedReader
提供 readLine()
方法,可以逐行读取数据。
PrintWriter
可以设置 autoflush
,使数据立即发送(而不是等到缓冲区满)。
在 Socket 通信中的应用
1 2 3 4 5
| BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
|
3. InputStreamReader
和 OutputStreamWriter
作用
InputStreamReader
:将 InputStream
(字节流)转换成 Reader
(字符流),可以指定字符编码(如 UTF-8
)。
OutputStreamWriter
:将 OutputStream
(字节流)转换成 Writer
(字符流),同样支持指定编码。
特点
- 是 字节流和字符流之间的桥梁,使我们可以用字符方式处理字节数据。
- 如果不指定编码,默认使用系统编码(可能导致乱码,建议显式指定
UTF-8
)。
在 Socket 通信中的应用
1 2 3 4 5 6 7 8 9
| BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8) );
PrintWriter out = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true );
|
4. DataInputStream
和 DataOutputStream
作用
DataInputStream
:用于读取 基本数据类型(如 int
、double
、String
)。
DataOutputStream
:用于写入 基本数据类型。
特点
- 适合传输 结构化数据(如数字、布尔值、字符串等)。
- 使用
readUTF()
和 writeUTF()
可以安全地传输字符串(避免乱码)。
在 Socket 通信中的应用
1 2 3 4 5 6 7 8 9
| DataOutputStream out = new DataOutputStream(socket.getOutputStream()); out.writeInt(100); out.writeUTF("Hello");
DataInputStream in = new DataInputStream(socket.getInputStream()); int num = in.readInt(); String msg = in.readUTF();
|
5. ObjectInputStream
和 ObjectOutputStream
作用
ObjectInputStream
:用于读取 Java 对象(反序列化)。
ObjectOutputStream
:用于写入 Java 对象(序列化)。
特点
- 适合传输 Java 对象(如自定义的
Message
类)。
- 对象必须实现
Serializable
接口。
在 Socket 通信中的应用
1 2 3 4 5 6 7
| ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(new Message("Alice", "Hello Bob!"));
ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); Message msg = (Message) in.readObject();
|
总结:不同流的适用场景
流类型 |
适用场景 |
示例 |
InputStream / OutputStream |
原始字节传输(文件、图片) |
socket.getInputStream() |
BufferedReader / PrintWriter |
文本通信(聊天消息) |
new BufferedReader(new InputStreamReader(in)) |
DataInputStream / DataOutputStream |
结构化数据(数字、字符串) |
out.writeInt(100) |
ObjectInputStream / ObjectOutputStream |
Java 对象传输 |
out.writeObject(user) |
最佳实践
- 文本通信:
BufferedReader
+ PrintWriter
(推荐 autoflush=true
)。
- 二进制数据传输:直接使用
InputStream
和 OutputStream
。
- 结构化数据:
DataInputStream
/ DataOutputStream
。
- 对象传输:
ObjectInputStream
/ ObjectOutputStream
(确保对象可序列化)。
文件传输
除了发送消息之外,有了输入输出流,即使是文件传输也不在话下,我们可以来编写一个文件传输程序,让客户端传输文件到服务端去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| try(ServerSocket server = new ServerSocket(8080)) { Socket socket = server.accept(); System.out.println("接受到来自客户端的连接准备开始进行文件传输"); InputStream in = socket.getInputStream(); FileOutputStream stream = new FileOutputStream("test"); long len, total = 0; byte[] buffer = new byte[1024 * 1024 * 4]; while ((len = in.read(buffer)) > 0) { System.out.println("正在进行文件传输,当前已接收: " + total + " 字节数据"); stream.write(buffer, 0, (int) len); total += len; } } catch (IOException e) { e.printStackTrace(); }
|
1 2 3 4 5 6 7 8 9
| try (Socket socket = new Socket("localhost", 8080); Scanner scanner = new Scanner(System.in)) { System.out.println("已连接到服务端,请输入要发送的文件路径: "); OutputStream out = socket.getOutputStream(); FileInputStream stream = new FileInputStream(scanner.nextLine()); stream.transferTo(out); } catch (IOException e) { e.printStackTrace(); }
|
Socket实现UDP通信
接着来看如何使用Socket实现UDP通信,Java为我们提供了DatagramSocket API,它是一种UDP Socket,用于发送和接收UDP数据报。
由于UDP不像TCP那样需要提前连接,所以我们只需要创建一个Socket等待数据到来即可:
1 2 3 4 5
| try(DatagramSocket socket = new DatagramSocket(8080)) {
} catch (IOException e) { e.printStackTrace(); }
|
接着我们即可连续读取客户端发来的数据:
1 2 3 4 5 6
| while(true) { DatagramPacket packet = new DatagramPacket(new byte[1024], 1024); socket.receive(packet); System.out.println(new String(packet.getData())); }
|
客户端这边由于不需要预先建立连接,所以我们直接创建一个UDP数据包,并在数据包中指定发送的IP地址和端口等内容,发送后直接就可以到达:
1 2 3 4 5 6 7 8 9 10 11 12
| try (DatagramSocket socket = new DatagramSocket(); Scanner scanner = new Scanner(System.in)) { while (true) { String str = scanner.nextLine(); byte[] data = str.getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); DatagramPacket packet = new DatagramPacket(data, data.length, address, 8080); socket.send(packet); } } catch (IOException e){ e.printStackTrace(); }
|
这个相比TCP就简单很多了,直接发送就能收到:

使用浏览器访问Socket服务器
最后我们来研究一下HTTP协议,我们前面说过,浏览器访问一个网站实际上用的就是HTTP协议,并且HTTP协议是基于TCP协议的,所以说我们可以创建一个ServerSocket来处理浏览器的访问请求,看看HTTP请求到底长啥样。首先我们还是直接编写一个服务端来等待连接:
1 2 3 4 5 6 7 8 9 10 11
| try(ServerSocket server = new ServerSocket(8080)){ Socket socket = server.accept(); InputStream in = socket.getInputStream(); while (!socket.isClosed()) { int i = in.read(); if(i == -1) break; System.out.print((char) i); } }catch (IOException e){ e.printStackTrace(); }
|
此时我们使用Chrome浏览器访问:http://localhost:8080,可以直接在服务端收到以下数据:

这正是我们之前说的HTTP协议对应的报文格式,可以看到当我们使用浏览器访问服务端时,浏览器会默认向我们的服务端发送一个GET请求,路径为根路径”/“,并且还包含了很多请求标头。
但是此时我们会发现,浏览器会一直处于加载中状态:

这是因为HTTP规定请求完成之后服务端需要给一个响应结果,所以说浏览器实际上一直在等待我们的服务端发送一个请求结果给它,但是我们这里没有做任何处理,所以说就会一直卡住。
现在我们来尝试给它返回一个简单的HTML页面(看不懂代码没关系,这里只是测试一下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| try(ServerSocket server = new ServerSocket(8080)){ Socket socket = server.accept(); OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream()); String html = """ <!DOCTYPE html> <html lang="en"> <head> <title>测试网站</title> </head> <body> <h1>欢迎访问我们的测试网站</h1> <p>这个网站包含很多你喜欢的内容,但是没办法展示出来,因为我们还没学会</p> </body> """; writer.write("HTTP/1.1 200 OK\r\n"); writer.write("Content-Type: text/html;charset=utf-8\r\n"); writer.write("\r\n"); writer.write(html); writer.flush(); }catch (IOException e){ e.printStackTrace(); }
|
此时我们使用Chrome浏览器再次访问服务端,就可以展示出我们返回的数据了:

是不是感觉非常神奇,只需要返回给浏览器一个HTML代码,就成功以网站的形式打开了。这也是我们后续学习的主要方向,只不过使用Socket这种框架来编写网站,太过于原始了,尤其是对HTTP协议报文的处理上,自己写太过麻烦了,后面我们会学习各种Web服务端,它们是专用于网站服务器的程序,我们就能快速编写更加高级的Web应用程序。