杰瑞科技汇

Java Socket编程实例具体怎么实现?

我们将创建两个独立的 Java 程序:

Java Socket编程实例具体怎么实现?-图1
(图片来源网络,侵删)
  1. EchoServer.java: 一个服务器端程序,它在指定端口上监听客户端连接,当收到客户端的消息后,它会将消息原封不动地回显给客户端。
  2. EchoClient.java: 一个客户端程序,它连接到服务器,向服务器发送一条消息,并接收服务器回显的消息。

服务器端程序 (EchoServer.java)

服务器端的核心逻辑是:

  1. 创建一个 ServerSocket 并绑定到一个特定的端口(8888),开始监听客户端连接。
  2. 使用 accept() 方法阻塞,等待并接受一个客户端的连接请求,当有客户端连接时,accept() 返回一个代表该客户端连接的 Socket 对象。
  3. 为每个客户端连接创建一个新的线程来处理,这样服务器就可以同时为多个客户端服务(多线程模型)。
  4. 在线程中,通过 Socket 获取输入流和输出流。
  5. 从输入流中读取客户端发送的数据。
  6. 将读取到的数据通过输出流写回给客户端。
  7. 关闭连接和资源。

EchoServer.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 EchoServer {
    public static void main(String[] args) {
        int port = 8888; // 服务器监听端口
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("服务器已启动,正在监听端口 " + port + "...");
            // 循环等待客户端连接
            while (true) {
                // accept() 方法会阻塞,直到有客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
                // 为每个客户端创建一个新的线程来处理,以实现多客户端并发
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
/**
 * 客户端处理线程
 */
class ClientHandler implements Runnable {
    private Socket clientSocket;
    public ClientHandler(Socket socket) {
        this.clientSocket = socket;
    }
    @Override
    public void run() {
        // 使用 try-with-resources 语句自动关闭资源
        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("来自客户端 [" + clientSocket.getInetAddress() + "] 的消息: " + inputLine);
                // 将接收到的消息回显给客户端
                out.println("服务器回显: " + inputLine);
                // 如果客户端发送 "exit",则关闭连接
                if ("exit".equalsIgnoreCase(inputLine)) {
                    System.out.println("客户端请求断开连接。");
                    break;
                }
            }
        } catch (IOException e) {
            // 如果客户端异常断开,会抛出 SocketException,这里可以忽略或打印日志
            if (e.getMessage() != null && !e.getMessage().equals("Connection reset")) {
                System.err.println("处理客户端时发生IO异常: " + e.getMessage());
            }
        } finally {
            // 确保在连接结束时关闭Socket
            try {
                if (clientSocket != null && !clientSocket.isClosed()) {
                    clientSocket.close();
                    System.out.println("与客户端 " + clientSocket.getInetAddress() + " 的连接已关闭。");
                }
            } catch (IOException e) {
                System.err.println("关闭客户端Socket时出错: " + e.getMessage());
            }
        }
    }
}

客户端程序 (EchoClient.java)

客户端的核心逻辑是:

  1. 创建一个 Socket 对象,指定服务器的 IP 地址(或主机名)和端口号,发起连接请求。
  2. 连接成功后,通过 Socket 获取输入流和输出流。
  3. 使用输出流向服务器发送消息。
  4. 使用输入流读取服务器回显的消息。
  5. 关闭连接和资源。

EchoClient.java 代码:

Java Socket编程实例具体怎么实现?-图2
(图片来源网络,侵删)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
 * 一个简单的回显客户端
 */
public class EchoClient {
    public static void main(String[] args) {
        String hostName = "127.0.0.1"; // 服务器地址,本地回环地址
        int port = 8888;             // 服务器端口
        try (
            // 创建Socket并连接到服务器
            Socket socket = new Socket(hostName, port);
            // 获取输出流,用于向服务器发送数据
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 获取输入流,用于读取服务器返回的数据
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))
        ) {
            System.out.println("已成功连接到服务器 " + hostName + ":" + port);
            System.out.println("请输入要发送的消息 (输入 'exit' 退出):");
            Scanner scanner = new Scanner(System.in);
            String userInput;
            // 循环读取用户从控制台输入的消息
            while (true) {
                System.out.print("客户端> ");
                userInput = scanner.nextLine();
                // 将用户输入发送给服务器
                out.println(userInput);
                // 如果用户输入 "exit",则退出循环
                if ("exit".equalsIgnoreCase(userInput)) {
                    break;
                }
                // 读取服务器回显的消息
                String response = in.readLine();
                if (response != null) {
                    System.out.println("服务器> " + response);
                }
            }
        } catch (UnknownHostException e) {
            System.err.println("不知道主机: " + hostName);
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("IO异常: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("客户端已关闭。");
    }
}

如何运行和测试

  1. 保存代码: 将上面的两段代码分别保存为 EchoServer.javaEchoClient.java 文件,放在同一个目录下。

  2. 编译: 打开终端或命令提示符,进入该目录,使用 javac 命令编译两个文件。

    javac EchoServer.java EchoClient.java

    这会生成 EchoServer.classEchoClient.class 文件。

  3. 启动服务器: 在终端中,先运行服务器程序。

    java EchoServer

    你会看到输出:

    服务器已启动,正在监听端口 8888...

    服务器会一直等待客户端连接。

  4. 启动客户端: 打开另一个新的终端窗口,运行客户端程序。

    java EchoClient

    你会在客户端终端看到:

    已成功连接到服务器 127.0.0.1:8888
    请输入要发送的消息 (输入 'exit' 退出):
    客户端> 
  5. 进行通信:

    • 在客户端终端输入任意文本,你好,服务器!,然后按回车。
    • 你会在客户端终端立即看到服务器的回显:
      客户端> 你好,服务器!
      服务器> 服务器回显: 你好,服务器!
    • 在服务器终端,你会看到相应的日志:
      服务器已启动,正在监听端口 8888...
      客户端已连接: 127.0.0.1
      来自客户端 [/127.0.0.1] 的消息: 你好,服务器!
  6. 关闭连接:

    • 在客户端输入 exit,然后按回车。
    • 客户端和服务器都会打印关闭信息,然后客户端程序退出,服务器端会继续监听下一个客户端的连接。

代码关键点解析

类/方法/概念 作用 备注
ServerSocket(int port) 创建一个服务器套接字,并绑定到指定的端口。 服务器端专用。
serverSocket.accept() 监听并接受到此套接字的连接,这是一个阻塞方法,没有连接时会一直等待。 返回一个 Socket 对象,代表与客户端的连接通道。
Socket(String host, int port) 创建一个套接字,并尝试连接到指定主机和端口。 客户端专用,这是一个阻塞方法,连接成功或失败才会返回。
socket.getInputStream() 返回此套接字的输入流,用于读取从对方(客户端/服务器)发送过来的数据。 通常用 BufferedReader 包装,以便按行读取。
socket.getOutputStream() 返回此套接字的输出流,用于向对方发送数据。 通常用 PrintWriter 包装,并启用 autoFlush (true),这样调用 println() 后会自动刷新缓冲区。
多线程 在服务器端,为每个 accept() 返回的 Socket 创建一个新线程来处理,这是实现并发处理多个客户端的标准模式。 如果不使用多线程,服务器一次只能处理一个客户端的请求。
阻塞与非阻塞 accept(), Socket 的构造函数,以及 InputStreamread() 方法都是阻塞的,程序会暂停执行,直到有预期的结果(如连接、数据到达)。 这是同步 I/O 的特点,Java NIO 提供了非阻塞 I/O 的解决方案,性能更高,但更复杂。
try-with-resources try (Resource res = ...) 语法,确保在 try 块执行完毕后,资源(如 Socket, InputStream, OutputStream)会被自动关闭,即使发生异常。 强烈推荐使用,可以避免资源泄漏。

可能遇到的问题与解决方法

  • java.net.BindException: Address already in use

    • 原因: 端口 8888 已经被另一个程序占用(可能是你之前运行的服务器没有正常关闭)。
    • 解决: 在终端运行 netstat -ano | findstr "8888" (Windows) 或 lsof -i :8888 (macOS/Linux) 查找占用端口的进程,然后终止它,或者换一个端口。
  • java.net.ConnectException: Connection refused

    • 原因: 客户端尝试连接时,服务器没有运行,或者 IP 地址/端口号错误。
    • 解决: 确保服务器已经成功启动,并且客户端的 hostNameport 与服务器配置一致。0.0.1 是本机地址,如果服务器和客户端在同一台电脑上,就用这个。
  • 如何在不同电脑上测试?

    • EchoClient.java 中的 hostName"127.0.0.1" 改为服务器的实际 IP 地址("192.168.1.100")。
    • 确保服务器的防火墙允许在指定端口(8888)上的入站连接。

这个例子是 Java Socket 编程的基础,掌握了它,你就可以在此基础上构建更复杂的网络应用,如聊天室、文件传输、远程调用等。

分享:
扫描分享到社交APP
上一篇
下一篇