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


目录

1.前言

2.正文

2.1ServerSocket类

2.2Socket类

2.3Tcp回显服务器

2.3.1TcpEchoServer

2.3.2TcpEchoClient

3.小结


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

java 复制代码
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

java 复制代码
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.小结

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

相关推荐
言慢行善16 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
forAllforMe16 小时前
etherCAT的协议VoE,FoE,EoE,CoE的概念和区别
网络
专吃海绵宝宝菠萝屋的派大星16 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
斯普信云原生组16 小时前
Prometheus 环境监控虚机 Redis 方案(生产实操版)
运维·docker·容器
大数据新鸟16 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z16 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可16 小时前
Java 中的实现类是什么
java·开发语言
He少年16 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新17 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏49417 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构