杰瑞科技汇

Java客户端与服务器Socket通信如何实现?

  1. 核心概念:理解 Socket、IP 地址和端口号。
  2. 通信模型:客户端/服务器模型。
  3. 简单示例:一个基础的“请求-响应”模型,客户端发送消息,服务器返回消息。
  4. 多线程服务器:一个更高级的服务器,能够同时处理多个客户端连接。
  5. I/O 模式简介:传统的 BIO、NIO 和 AIO 的简单对比。

核心概念

  • Socket (套接字):可以看作是网络通信的“端点”,一个 Socket 由一个 IP 地址和一个端口号唯一标识,程序通过 Socket 发送和接收数据,Java 中,java.net.Socket 代表客户端,java.net.ServerSocket 代表服务端。
  • IP 地址:网络中设备的唯一地址,168.1.100www.google.com
  • 端口号:计算机上应用程序的“门牌号”,一个 IP 地址可以同时运行多个网络服务,它们通过不同的端口号来区分,端口号范围是 0-65535,0-1023 是知名端口,一般不使用,HTTP 默认使用 80 端口,HTTPS 使用 443 端口,我们可以使用 1024 以上的任意空闲端口。

通信模型

客户端/服务器模型是最常见的网络通信模式:

Java客户端与服务器Socket通信如何实现?-图1
(图片来源网络,侵删)
  1. 服务器

    • 在一个特定的 IP 地址和端口上启动,并开始监听客户端的连接请求。
    • 当一个客户端请求连接时,服务器接受连接,并建立一个与该客户端的通信通道(一个 Socket)。
    • 通过这个通道,服务器可以读取客户端发送的数据,并向客户端发送数据。
  2. 客户端

    • 知道服务器的 IP 地址和端口号。
    • 向服务器发起连接请求。
    • 一旦连接成功,客户端也拥有一个与服务器通信的 Socket。
    • 通过这个 Socket,客户端可以向服务器发送数据,并读取服务器的响应。

数据流向: 客户端 -> (发送数据) -> 服务器 服务器 -> (读取数据) -> 客户端 服务器 -> (发送响应) -> 客户端 客户端 -> (读取响应) -> 服务器


简单示例:单线程服务器

这个例子将展示最基本的交互:客户端发送一条消息,服务器接收后打印并发送一条确认消息,然后连接关闭。

Java客户端与服务器Socket通信如何实现?-图2
(图片来源网络,侵删)

1 服务器端代码

// SimpleServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleServer {
    public static void main(String[] args) {
        int port = 12345; // 定义服务器监听的端口号
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("服务器已启动,正在监听端口 " + port + "...");
            // accept() 方法会阻塞,直到有客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
            // 使用 try-with-resources 确保流被正确关闭
            // 获取输入流,用于读取客户端发送的数据
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            // 获取输出流,用于向客户端发送数据
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            // 读取客户端发送的消息
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("收到客户端消息: " + inputLine);
                // 如果客户端发送 "exit",则退出循环
                if ("exit".equalsIgnoreCase(inputLine)) {
                    break;
                }
                // 发送响应给客户端
                out.println("服务器已收到你的消息: " + inputLine);
            }
            System.out.println("客户端断开连接。");
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

2 客户端代码

// SimpleClient.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SimpleClient {
    public static void main(String[] args) {
        String hostName = "localhost"; // 或服务器的IP地址,如 "192.168.1.100"
        int port = 12345;
        try (Socket socket = new Socket(hostName, port)) {
            System.out.println("已连接到服务器 " + hostName + ":" + port);
            // 获取输入流,用于读取服务器发送的数据
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 获取输出流,用于向服务器发送数据
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 从控制台读取用户输入
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
            String userInput;
            System.out.println("请输入要发送的消息 (输入 'exit' 退出):");
            while ((userInput = stdIn.readLine()) != null) {
                // 发送消息到服务器
                out.println(userInput);
                // 如果用户输入 "exit",则退出循环
                if ("exit".equalsIgnoreCase(userInput)) {
                    break;
                }
                // 读取并打印服务器的响应
                String response = in.readLine();
                System.out.println("服务器响应: " + response);
            }
        } catch (UnknownHostException e) {
            System.err.println("无法找到主机: " + hostName);
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("无法连接到主机 " + hostName + " 端口 " + port);
            e.printStackTrace();
        }
    }
}

3 如何运行

  1. 先启动服务器:运行 SimpleServer,控制台会显示 "服务器已启动,正在监听端口 12345...",并在此处等待。
  2. 再启动客户端:运行 SimpleClient,控制台会显示 "已连接到服务器..."。
  3. 交互
    • 在客户端的控制台输入任意文本,"Hello, Server!",然后按回车。
    • 服务器控制台会打印 "收到客户端消息: Hello, Server!",并向客户端发送响应。
    • 客户端控制台会打印 "服务器响应: 服务器已收到你的消息: Hello, Server!"。
  4. 退出:在客户端输入 "exit" 并回车,客户端和服务器都会关闭连接并程序结束。

多线程服务器

上面的简单服务器一次只能处理一个客户端连接,当 serverSocket.accept() 阻塞时,它无法接受新的连接,要解决这个问题,我们需要为每个客户端连接创建一个新的线程来处理。

1 服务器端代码 (多线程版)

// MultiThreadServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MultiThreadServer {
    public static void main(String[] args) {
        int port = 12345;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("多线程服务器已启动,正在监听端口 " + port + "...");
            while (true) { // 循环接受所有客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("新客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
                // 为每个客户端创建一个新的线程来处理
                ClientHandler clientHandler = new ClientHandler(clientSocket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
    // 内部类,用于处理单个客户端的连接
    private static class ClientHandler implements Runnable {
        private final Socket clientSocket;
        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }
        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                 PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    System.out.println("[" + Thread.currentThread().getName() + "] 收到客户端消息: " + inputLine);
                    if ("exit".equalsIgnoreCase(inputLine)) {
                        break;
                    }
                    out.println("服务器已收到你的消息: " + inputLine);
                }
            } catch (IOException e) {
                System.err.println("处理客户端时出错: " + e.getMessage());
            } finally {
                try {
                    clientSocket.close();
                    System.out.println("客户端连接已关闭.");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
Java客户端与服务器Socket通信如何实现?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇