目录

Java网络原理3网络编程续

Java【网络原理】(3)网络编程续

https://i-blog.csdnimg.cn/direct/acc5dbd919d84868af6c674e28f2c2f0.jpeg



1.前言

哈喽大家好,今天继续进行计算机网络的初阶学习,今天学习的是tcp回显服务器的实现,正文开始

2.正文

在正式讲解Tcp回显服务器,还要介绍两个包,一个是ServerSocket包,这个包是专门给服务器用的,Socket包是服务器和客户端都会用,下文详解。

2.1ServerSocket类

Java中的 ServerSocket 类是用于在服务器端监听客户端连接请求的核心类,属于 java.net 包。它允许服务器应用程序在指定端口上等待客户端的连接,并为每个连接创建一个 Socket 对象进行通信。


核心作用

  • 监听端口 :绑定到特定端口,等待客户端连接。
  • 接受连接 :通过 accept() 方法阻塞等待客户端连接,返回代表客户端的 Socket 对象。
  • 管理连接队列 :通过 backlog 参数设置等待连接队列的最大长度。

关键方法

  1. 构造方法

    • ServerSocket(int port) :绑定到指定端口。
    • ServerSocket(int port, int backlog) :指定端口和连接队列长度。
    • ServerSocket(int port, int backlog, InetAddress bindAddr) :绑定到特定IP地址的端口。
  2. 核心方法

    • Socket accept() :阻塞等待客户端连接,返回连接的 Socket 对象。
    • void bind(SocketAddress endpoint) :绑定到指定地址和端口。
    • void close() :关闭服务器套接字。
    • int getLocalPort() :获取监听的端口号。
    • void setSoTimeout(int timeout) :设置 accept() 的超时时间(毫秒)。

使用流程

  1. 创建 ServerSocket 并绑定端口。
  2. 循环调用 accept() 接受客户端连接。
  3. 为每个连接的 Socket 启动新线程处理请求。
  4. 处理完成后关闭资源。

2.2Socket类

Java中的 Socket 类是用于实现网络通信的核心类,属于 java.net 包。它代表客户端与服务器之间的一个连接,允许通过输入流和输出流进行双向数据传输。 Socket 类通常与 ServerSocket 类配合使用,实现客户端-服务器模型的通信。


核心作用

  • 建立连接 :连接到服务器端的指定IP地址和端口。
  • 数据传输 :通过输入流( InputStream )和输出流( OutputStream )进行数据交换。
  • 关闭连接 :释放资源并终止通信。

关键方法

  1. 构造方法

    • Socket(String host, int port) :连接到指定主机和端口。
    • Socket(InetAddress address, int port) :使用 InetAddress 对象连接到指定主机和端口。
    • Socket(String host, int port, InetAddress localAddr, int localPort) :绑定到本地地址和端口,同时连接到远程主机。
  2. 核心方法

    • InputStream getInputStream() :获取输入流,用于读取服务器发送的数据。
    • OutputStream getOutputStream() :获取输出流,用于向服务器发送数据。
    • void close() :关闭套接字,释放资源。
    • void shutdownInput() :关闭输入流。
    • void shutdownOutput() :关闭输出流。
    • boolean isConnected() :检查是否已连接。
    • boolean isClosed() :检查是否已关闭。

使用流程

  1. 创建 Socket 对象并连接到服务器。
  2. 获取输入流和输出流进行数据交换。
  3. 处理数据并完成通信。
  4. 关闭 Socket 和相关资源。

2.3Tcp回显服务器

2.3.1TcpEchoServer

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * TCP回显服务器实现类
 * 功能:接收客户端消息并原样返回(回显)
 */
public class TcpEchoServer {
    private ServerSocket serverSocket = null;  // 服务器套接字对象

    // 构造方法:初始化服务器并绑定端口
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);  // 创建ServerSocket并绑定指定端口
    }

    /**
     * 启动服务器核心逻辑
     */
    public void start() throws IOException {
        System.out.println("启动服务器");
        
        // 创建线程池(动态调整线程数量,适合短任务)
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 持续监听客户端连接
        while (true) {
            // 阻塞等待客户端连接
            Socket clientSocket = serverSocket.accept();
            
            // 将客户端连接提交给线程池处理
            executorService.submit(() -> {
                try {
                    processConnection(clientSocket);  // 处理单个客户端连接
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    /**
     * 处理单个客户端连接的完整生命周期
     * @param clientSocket 客户端套接字对象
     */
    private void processConnection(Socket clientSocket) throws IOException {
        // 打印客户端连接信息
        System.out.printf("[%s:%d] 客户端上线!\n", 
                         clientSocket.getInetAddress(), 
                         clientSocket.getPort());
        
        // 使用try-with-resources自动关闭流
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            
            // 使用Scanner和PrintWriter包装流对象
            Scanner scanner = new Scanner(inputStream);    // 输入流扫描器
            PrintWriter printWriter = new PrintWriter(outputStream);  // 输出流写入器

            // 持续处理客户端请求
            while (true) {
                // 1. 检测连接状态(若输入流中没有数据,说明客户端断开)
                if (!scanner.hasNext()) {
                    System.out.printf("[%s:%d] 客户端下线!\n", 
                                    clientSocket.getInetAddress(), 
                                    clientSocket.getPort());
                    break;
                }
                
                // 2. 读取请求并处理
                String request = scanner.next();    // 读取客户端请求(空格分隔)
                String response = process(request); // 处理请求生成响应

                // 3. 返回响应给客户端
                printWriter.println(response);  // 写入响应
                printWriter.flush();            // 强制刷新缓冲区
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();  // 确保关闭客户端套接字
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 处理请求的核心逻辑(示例为简单回显)
     * @param request 客户端请求内容
     * @return 返回与请求相同的字符串
     */
    private String process(String request) {
        // 此处可添加业务逻辑(示例直接返回原内容)
        return request;
    }

    /**
     * 主方法:启动服务器实例
     */
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);  // 创建服务器实例(端口9090)
        tcpEchoServer.start();  // 启动服务器
    }
}

核心思路讲解:


整体架构设计:

该代码实现了一个 多线程TCP回显服务器 ,核心功能是接收客户端发送的文本消息,并将消息原样返回(回显)。其架构设计遵循经典 客户端-服务器模型 ,核心特点包括:

  • 多线程处理 :通过线程池动态分配线程,避免单线程阻塞导致的性能瓶颈。
  • 资源自动管理 :利用 try-with-resources 确保流和套接字的自动释放。
  • 松耦合设计 :将连接处理( processConnection )与业务逻辑( process )分离,便于扩展。

核心组件解析:

(1) ServerSocket 的职责

  • 端口监听 :绑定到指定端口(如9090),通过 accept() 阻塞等待客户端连接。
  • 连接队列管理 :默认使用操作系统提供的连接队列(通过 backlog 参数可调整队列长度)。
  • 生命周期控制 :服务器运行时持续监听,直到进程终止(代码中未实现优雅关闭逻辑)。

(2) Socket 连接处理

  • 双向通信 :每个客户端连接对应一个 Socket 对象,通过其输入流( InputStream )和输出流( OutputStream )实现数据交换。
  • 连接状态检测 :通过 scanner.hasNext() 判断客户端是否断开(输入流关闭时返回 false )。

(3) 线程池的作用

  • 动态资源分配Executors.newCachedThreadPool() 创建的线程池会根据任务量自动扩展/收缩:

    • 空闲线程默认存活60秒后被回收。
    • 适合 短生命周期任务 (如HTTP请求)。
  • 避免线程爆炸 :相比为每个连接直接创建 new Thread() ,线程池能有效控制系统资源占用。


3. 关键流程详解:

(1) 启动阶段

  1. 初始化 ServerSocket 并绑定端口。
  2. 创建线程池,进入无限循环等待连接。

(2) 连接处理阶段

  1. 接受连接accept() 返回客户端 Socket 对象。

  2. 提交任务 :将 processConnection 方法包装为任务提交到线程池。

  3. 处理请求

    • 通过 Scanner 逐词读取客户端请求(空格分隔)。
    • 调用 process() 生成响应(此处简单回显)。
    • 通过 PrintWriter 写回响应并强制刷新缓冲区。

(3) 连接终止

  1. 客户端主动断开scanner.hasNext() 检测到输入流结束,跳出循环关闭连接。
  2. 异常处理 :捕获 IOException 并关闭套接字,防止资源泄漏。

2.3.2TcpEchoClient

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    // 定义一个Socket对象,用于与服务器建立连接
    private Socket socket = null;

    // 构造函数,用于初始化客户端并连接到指定的服务器IP和端口
    private TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 创建一个Socket对象,连接到指定的服务器IP和端口
        socket = new Socket(serverIp, serverPort);
    }

    // 启动客户端,开始与服务器进行通信
    public void start() {
        // 创建一个Scanner对象,用于从控制台读取用户输入
        Scanner scanner = new Scanner(System.in);
        try (
            // 获取Socket的输入流,用于接收服务器发送的数据
            InputStream inputStream = socket.getInputStream();
            // 获取Socket的输出流,用于向服务器发送数据
            OutputStream outputStream = socket.getOutputStream()
        ) {
            // 创建一个Scanner对象,用于从输入流中读取服务器发送的数据
            Scanner scannerNet = new Scanner(inputStream);
            // 创建一个PrintWriter对象,用于向输出流中写入数据
            PrintWriter writer = new PrintWriter(outputStream);

            // 进入一个无限循环,持续与服务器进行通信
            while (true) {
                // 从控制台读取用户输入的数据
                String request = scanner.next();
                // 将用户输入的数据发送到服务器
                writer.println(request);
                // 刷新输出流,确保数据被发送
                writer.flush();
                // 从服务器读取响应数据
                String response = scannerNet.next();
                // 将服务器返回的响应数据打印到控制台
                System.out.println(response);
            }
        } catch (IOException e) {
            // 如果发生IO异常,抛出运行时异常
            throw new RuntimeException(e);
        }
    }

    // 主函数,程序的入口
    public static void main(String[] args) throws IOException {
        // 创建一个TcpEchoClient对象,连接到本地服务器的9090端口
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        // 启动客户端,开始与服务器通信
        client.start();
    }
}

核心思路讲解:

  1. Socket连接

    • TcpEchoClient 的构造函数中,通过 new Socket(serverIp, serverPort) 创建一个Socket对象,连接到指定的服务器IP和端口。这个Socket对象将用于后续的通信。
  2. 输入输出流

    • start 方法中,通过 socket.getInputStream()socket.getOutputStream() 分别获取Socket的输入流和输出流。输入流用于接收服务器发送的数据,输出流用于向服务器发送数据。
  3. 用户输入与服务器通信

    • 使用 Scanner 从控制台读取用户输入的数据,并通过 PrintWriter 将数据发送到服务器。
    • 使用 Scanner 从输入流中读取服务器返回的响应数据,并将其打印到控制台。
  4. 循环通信

    • 通过一个无限循环 while (true) ,客户端可以持续与服务器进行通信。每次循环中,客户端都会读取用户输入,发送到服务器,并等待服务器的响应。
  5. 异常处理

    • 如果在通信过程中发生IO异常,代码会捕获该异常并抛出运行时异常。
  6. 主函数

    • main 函数中,创建一个 TcpEchoClient 对象,并连接到本地服务器的9090端口。然后调用 start 方法,启动客户端与服务器的通信。

3.小结

今天的分享到这里就结束了,喜欢的小伙伴不要忘记点点赞点个关注,你的鼓励就是对我最大的支持,加油!