Java Socket编程
Socket 基础概念
Socket 就像两个程序之间的"电话线":
- 服务端:像接听电话的一方(ServerSocket)
- 客户端:像拨打电话的一方(Socket)
- 传输的数据就是"通话内容"
核心代码示例与分析
1. 服务端代码
import java.io.*;
import java.net.*;
public class SimpleServer {
public static void main(String[] args) throws IOException {
// 1. 创建ServerSocket,监听8888端口(就像电话总机)
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,等待连接...");
// 2. 等待客户端连接(等待电话打入)
Socket clientSocket = serverSocket.accept(); // 这里会阻塞直到有连接
System.out.println("客户端已连接:" + clientSocket.getInetAddress());
// 3. 获取输入流(接收客户端发来的数据)
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
// 4. 获取输出流(向客户端发送数据)
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true);
// 5. 通信循环
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息:" + inputLine);
// 将消息原样返回并加上"服务器回应:"
out.println("服务器回应:" + inputLine);
if ("bye".equalsIgnoreCase(inputLine)) {
break;
}
}
// 6. 关闭资源
in.close();
out.close();
clientSocket.close();
serverSocket.close();
}
}
代码分析:
ServerSocket(8888)
:创建监听 8888 端口的服务端,就像开通了一个分机号码accept()
:阻塞方法,直到有客户端连接才会继续执行- 输入输出流:
InputStream
:接收客户端数据的管道OutputStream
:向客户端发送数据的管道
- 通信过程是双向的:可以同时收发数据
- 最后需要关闭所有资源,就像挂断电话
2. 客户端代码
import java.io.*;
import java.net.*;
public class SimpleClient {
public static void main(String[] args) throws IOException {
// 1. 创建Socket连接服务器(拨打电话)
Socket socket = new Socket("localhost", 8888);
System.out.println("已连接到服务器");
// 2. 获取输入输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 3. 从控制台读取输入
BufferedReader stdIn = new BufferedReader(
new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
// 发送消息到服务器
out.println(userInput);
// 接收服务器响应
System.out.println("服务器响应:" + in.readLine());
if ("bye".equalsIgnoreCase(userInput)) {
break;
}
}
// 4. 关闭资源
out.close();
in.close();
stdIn.close();
socket.close();
}
}
代码分析:
Socket("localhost", 8888)
:尝试连接本机的 8888 端口- 同样建立了输入输出流来与服务器通信
stdIn
用于从控制台读取用户输入- 通信是交互式的:发送消息 → 等待响应 → 显示响应
文件传输:
小文件的传输:
// 发送方
File file = new File("test.zip");
FileInputStream fis = new FileInputStream(file);
OutputStream os = socket.getOutputStream();
byte[] buffer = new byte[8192];
int count;
while ((count = fis.read(buffer)) > 0) {
os.write(buffer, 0, count);
}
os.flush();
// 接收方
FileOutputStream fos = new FileOutputStream("received.zip");
InputStream is = socket.getInputStream();
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
大文件的传输:
// Java 7+的NIO方式
FileChannel fileChannel = FileChannel.open(Paths.get("largefile.iso"));
long transferSize = fileChannel.transferTo(0, fileChannel.size(),
Channels.newChannel(socket.getOutputStream()));
// 接收方
FileChannel outChannel = FileChannel.open(Paths.get("output.iso"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE);
outChannel.transferFrom(
Channels.newChannel(socket.getInputStream()), 0, Long.MAX_VALUE);
数据传输过程详解
-
建立连接(三次握手):
- 客户端发送 SYN
- 服务端回应 SYN-ACK
- 客户端发送 ACK
-
数据传输:
sequenceDiagram 客户端->>服务端: out.println("Hello") 服务端->>客户端: out.println("服务器回应:Hello")
-
数据封装:
- 应用层数据 → TCP 分段 → IP 包 → 网络帧
关键点理解
-
阻塞与非阻塞:
accept()
、read()
都是阻塞方法- 在实际应用中可能需要多线程处理
-
流的概念:
- 输入流:从网络读取数据(InputStream)
- 输出流:向网络写入数据(OutputStream)
-
异常处理:
实际代码中应该添加 try-catch 块处理:SocketTimeoutException
ConnectException
UnknownHostException