Java Socket编程

Socket 基础概念

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();
    }
}

代码分析:

  1. ServerSocket(8888):创建监听 8888 端口的服务端,就像开通了一个分机号码
  2. accept():阻塞方法,直到有客户端连接才会继续执行
  3. 输入输出流:
    • InputStream:接收客户端数据的管道
    • OutputStream:向客户端发送数据的管道
  4. 通信过程是双向的:可以同时收发数据
  5. 最后需要关闭所有资源,就像挂断电话

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();
    }
}

代码分析:

  1. Socket("localhost", 8888):尝试连接本机的 8888 端口
  2. 同样建立了输入输出流来与服务器通信
  3. stdIn 用于从控制台读取用户输入
  4. 通信是交互式的:发送消息 → 等待响应 → 显示响应

文件传输:

小文件的传输:

// 发送方
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);

数据传输过程详解

  1. 建立连接(三次握手):

    • 客户端发送 SYN
    • 服务端回应 SYN-ACK
    • 客户端发送 ACK
  2. 数据传输

    sequenceDiagram
    客户端->>服务端: out.println("Hello")
    服务端->>客户端: out.println("服务器回应:Hello")
  3. 数据封装

    • 应用层数据 → TCP 分段 → IP 包 → 网络帧

关键点理解

  1. 阻塞与非阻塞

    • accept()read() 都是阻塞方法
    • 在实际应用中可能需要多线程处理
  2. 流的概念

    • 输入流:从网络读取数据(InputStream)
    • 输出流:向网络写入数据(OutputStream)
  3. 异常处理
    实际代码中应该添加 try-catch 块处理:

    • SocketTimeoutException
    • ConnectException
    • UnknownHostException