Java 实例 – ServerSocket 和 Socket 通信实例(超详细)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程中,网络通信是构建分布式系统和客户端-服务器架构的核心能力。无论是开发聊天应用、在线游戏,还是搭建简单的数据传输工具,掌握 ServerSocketSocket 的使用方法都至关重要。本文将通过循序渐进的实例,结合形象的比喻和代码演示,帮助读者理解 Java 网络通信的底层逻辑,并逐步构建一个完整的通信案例。


一、基础概念解析:Socket 与 ServerSocket 的角色分工

1.1 Socket:网络通信的“邮差”

想象一下,你给朋友写信时需要一个邮差(Socket)来传递信件。在 Java 中,Socket 是客户端与服务器端建立连接的核心类。它负责:

  • 主动发起连接:客户端通过 new Socket("服务器地址", 端口号) 向服务器发起请求。
  • 传输数据:通过输入/输出流(InputStream/OutputStream)发送或接收数据。

1.2 ServerSocket:监听端口的“邮局”

服务器端则像一个邮局(ServerSocket),它通过以下方式工作:

  • 监听指定端口:使用 new ServerSocket(端口号) 告诉系统“我在这里等待信件”。
  • 被动接受连接:当客户端连接时,通过 accept() 方法“接收信件”并返回一个 Socket 对象。

1.3 主动与被动的比喻

  • 客户端(Socket):像主动投递信件的邮差,必须知道邮局(ServerSocket)的位置和端口号(邮局编号)。
  • 服务器端(ServerSocket):像静候收发信件的邮局,端口是它的唯一标识。

二、第一个通信实例:Hello World

2.1 服务器端代码实现

import java.io.*;  
import java.net.*;  

public class SimpleServer {  
    public static void main(String[] args) throws IOException {  
        ServerSocket serverSocket = new ServerSocket(8888); // 监听8888端口  
        System.out.println("服务器已启动,等待连接...");  

        Socket clientSocket = serverSocket.accept(); // 阻塞等待客户端连接  
        System.out.println("客户端已连接!");  

        // 创建输入流读取客户端数据  
        BufferedReader in = new BufferedReader(  
            new InputStreamReader(clientSocket.getInputStream()));  

        // 创建输出流向客户端发送数据  
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);  

        String clientMessage = in.readLine();  
        System.out.println("客户端消息:" + clientMessage);  

        out.println("Hello Client! 服务器已收到你的消息!");  

        // 关闭资源  
        in.close();  
        out.close();  
        clientSocket.close();  
        serverSocket.close();  
    }  
}  

2.2 客户端代码实现

import java.io.*;  
import java.net.*;  

public class SimpleClient {  
    public static void main(String[] args) throws IOException {  
        Socket socket = new Socket("localhost", 8888); // 连接本地服务器的8888端口  

        // 创建输出流发送数据  
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);  
        out.println("Hello Server! 我是客户端!");  

        // 创建输入流读取服务器响应  
        BufferedReader in = new BufferedReader(  
            new InputStreamReader(socket.getInputStream()));  

        String serverResponse = in.readLine();  
        System.out.println("服务器回复:" + serverResponse);  

        // 关闭资源  
        out.close();  
        in.close();  
        socket.close();  
    }  
}  

2.3 运行步骤与验证

  1. 先运行 SimpleServer,控制台输出:“服务器已启动,等待连接...”。
  2. 再运行 SimpleClient,客户端控制台显示:“服务器回复:Hello Client! 服务器已收到你的消息!”。
  3. 服务器控制台显示客户端消息:“Hello Server! 我是客户端!”。

三、数据传输的细节处理

3.1 字符编码与流的选择

在代码中,我们使用了 PrintWriterBufferedReader 处理字符数据。若需传输二进制数据(如图片、文件),应改用字节流:

// 服务器端发送字节流示例  
OutputStream out = clientSocket.getOutputStream();  
byte[] data = "二进制数据".getBytes(StandardCharsets.UTF_8);  
out.write(data);  

// 客户端读取字节流示例  
InputStream in = socket.getInputStream();  
byte[] buffer = new byte[1024];  
int bytesRead = in.read(buffer);  
String receivedData = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);  

3.2 异常处理与资源管理

直接关闭流和 Socket 可能引发资源泄漏。推荐使用 try-with-resources 自动管理:

try (  
    ServerSocket serverSocket = new ServerSocket(8888);  
    Socket clientSocket = serverSocket.accept();  
    PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);  
    BufferedReader in = new BufferedReader(  
        new InputStreamReader(clientSocket.getInputStream()))  
) {  
    // 通信逻辑  
} catch (IOException e) {  
    e.printStackTrace();  
}  

四、扩展应用:多线程服务器

4.1 问题:单线程服务器的局限性

当前代码中,服务器每次只能处理一个客户端请求。若第二个客户端在第一个连接未关闭前尝试连接,将被阻塞在 accept() 方法中。

4.2 解决方案:多线程化

通过为每个连接创建独立线程,服务器可同时处理多个客户端。修改后的代码如下:

public class MultiThreadServer {  
    public static void main(String[] args) throws IOException {  
        ServerSocket serverSocket = new ServerSocket(8888);  
        System.out.println("多线程服务器启动...");  

        while (true) {  
            Socket clientSocket = serverSocket.accept();  
            new ClientHandler(clientSocket).start();  
        }  
    }  

    static class ClientHandler extends Thread {  
        private final Socket socket;  

        public ClientHandler(Socket socket) {  
            this.socket = socket;  
        }  

        @Override  
        public void run() {  
            try (  
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true);  
                BufferedReader in = new BufferedReader(  
                    new InputStreamReader(socket.getInputStream()))  
            ) {  
                String clientMessage = in.readLine();  
                System.out.println("客户端消息:" + clientMessage);  
                out.println("服务器已收到!");  
            } catch (IOException e) {  
                e.printStackTrace();  
            } finally {  
                try {  
                    socket.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
}  

4.3 运行效果

启动 MultiThreadServer 后,多个客户端可同时连接并收发消息,彼此之间互不干扰。


五、常见问题与解决方案

5.1 端口被占用

若启动服务器时提示端口被占用,可尝试:

  1. 更改端口号(如 new ServerSocket(9999))。
  2. 关闭占用端口的进程(如通过 netstat -ano 查找并终止)。

5.2 数据粘包问题

当数据分批次发送时,可能因网络延迟导致接收端读取到的数据“粘连”。解决方法包括:

  • 固定长度协议:发送前先发送数据长度。
  • 分隔符协议:用 \n 或自定义符号分隔消息。
// 发送端  
String message = "Hello\nWorld";  
out.write(message.getBytes());  

// 接收端  
StringBuilder buffer = new StringBuilder();  
while (true) {  
    int data = in.read();  
    if (data == '\n') {  
        break;  
    }  
    buffer.append((char) data);  
}  
String received = buffer.toString();  

六、结论

通过本文的实例,我们掌握了 ServerSocketSocket 的核心用法,从单线程通信到多线程扩展,逐步构建了一个完整的网络通信系统。理解以下关键点至关重要:

  • 主动与被动:客户端主动发起连接,服务器被动监听。
  • 资源管理:使用 try-with-resources 避免泄漏。
  • 扩展方向:通过多线程或协议设计提升系统能力。

建议读者尝试修改示例代码,例如添加文件传输功能,或使用 NIO 框架优化性能。掌握这些基础后,可进一步探索 HTTP 协议实现、WebSocket 或分布式系统开发,为构建更复杂的网络应用打下坚实基础。

最新发布