Socket结合线程池使用实现客户端和服务端通信demo

首先,我们需要先了解 Socket 是什么。 Socket 是一种网络通信协议,它能够在计算机之间实现双向通信。在使用 Socket 进行通信时,通常需要使用线程池,以便能够同时处理多个连接。

接下来,我们将演示如何使用 Socket 和线程池来实现一个基本的客户端和服务端通信 Demo,包含两个示例:

示例一:实现一个简单的客户端和服务端通信

首先,我们需要启动服务端程序,代码如下:

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Server {
    private static final int PORT = 8080; // 服务端监听的端口号
    private static final int THREADS_NUM = 10; // 线程池中线程的数量

    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            ExecutorService pool = Executors.newFixedThreadPool(THREADS_NUM); // 创建线程池
            System.out.println("Server is running...");
            while (true) {
                Socket socket = serverSocket.accept(); // 等待客户端连接
                pool.execute(new ServerWorker(socket)); // 把客户端连接交给线程池处理
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

在上面的代码中,我们创建了一个 ServerSocket 并监听指定的端口号 PORT。当有客户端连接到该端口时,创建一个 Socket 并将其传递给 ServerWorker 对象,以便线程池处理。

下面是 ServerWorker 的代码:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ServerWorker implements Runnable {
    private final Socket socket;

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

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("Server received message: " + message);
                out.println("Server response: " + message.toUpperCase()); // 将请求转化为大写并响应
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ServerWorker 实现了 Runnable 接口,用于执行线程池中的任务。在 run 方法中,我们通过 InputStream 和 OutputStream 对客户端传递的请求进行处理,并通过 OutputStream 将响应传递给客户端。

现在,我们可以启动客户端程序,并向服务端发送请求,代码如下:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {
    private static final String HOST = "127.0.0.1"; // 服务端 IP 地址
    private static final int PORT = 8080; // 服务端端口号

    public static void main(String[] args) {
        try (Socket socket = new Socket(HOST, PORT);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
            out.println("Hello, Server!"); // 向服务端发送请求
            String response = in.readLine();
            System.out.println("Client received response: " + response);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

我们可以看到,客户端程序中,我们通过 Socket 向服务端发送请求,并读取服务端响应。

示例二:使用多路复用的方式实现客户端和服务端通信

接下来,我们将演示如何使用多路复用的方式来实现客户端和服务端通信。

在这种实现方式中,我们将使用 ServerSocketChannel 和 SocketChannel,以及 Selector 对象来实现多路复用。具体代码如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.IntStream;

public class MultiplexingServer {
    private static final int PORT = 8080;
    private static final int THREADS_NUM = 10;
    private boolean running = true;

    public void run() throws IOException {
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new InetSocketAddress(PORT));

            Selector selector = Selector.open();
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);

            ExecutorService pool = Executors.newFixedThreadPool(THREADS_NUM);

            ConcurrentLinkedQueue<SocketChannel> clientSockets = new ConcurrentLinkedQueue<>();

            IntStream.range(0, THREADS_NUM).forEach(i -> {
                pool.execute(() -> {
                    while (running) {
                        try {
                            selector.select(); // 阻塞
                            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                            while (keys.hasNext()) {
                                SelectionKey key = keys.next();
                                if (key.isAcceptable()) {
                                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                                    SocketChannel client = server.accept();
                                    client.configureBlocking(false);
                                    client.register(selector, SelectionKey.OP_READ);
                                    clientSockets.offer(client);
                                    System.out.println("Client accepted: " + client.getRemoteAddress());
                                } else if (key.isReadable()) {
                                    SocketChannel client = (SocketChannel) key.channel();
                                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                                    client.read(buffer);
                                    buffer.flip();
                                    String message = new String(buffer.array()).trim();
                                    System.out.println("Server received message: " + message);
                                    clientSockets.stream().filter(socketChannel -> client != socketChannel)
                                            .forEach(socketChannel -> {
                                                try {
                                                    socketChannel.write(ByteBuffer.wrap(message.toUpperCase().getBytes()));
                                                } catch (IOException e) {
                                                    e.printStackTrace();
                                                }
                                            });
                                }
                                keys.remove();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Server closed.");
                });
            });

            // Ctrl-C 终止启动的程序
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                running = false;
                selector.wakeup();
                System.out.println("Shutting down...");
                pool.shutdown();
            }));

            System.out.println("Server is running...");
            while (true) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

首先,我们创建一个 ServerSocketChannel,并设置为非阻塞模式。然后,我们将 ServerSocketChannel 注册到 Selector 中,以便能够监听客户端连接请求。

接下来,我们创建一个线程池,并用于处理客户端 Socket 连接请求。当有客户端连接请求到达时,我们创建一个 SocketChannel,并将其注册到 Selector 中。

在客户端发来消息时,我们通过 Selector 获取可读事件,并对其进行处理。对于每个读事件,我们向所有客户端发送相同的消息,这里采用了并发队列来管理多个 SocketChannel。

最后,我们通过启动线程来处理所有客户端请求,并使用 Selector 对客户端进行监听,以便能够接收客户端的请求和响应。

除此之外,我们为应用程序添加了一些优雅地关闭的支持,以便能够在用户按下 Ctrl-C 停止应用程序时优雅地关闭线程池和选择器,并输出相应的信息。

总结

通过以上两个实例,我们可以了解如何使用 Socket 和线程池来实现基本的客户端和服务端通信,以及如何使用多路复用的方式来实现客户端和服务端通信。在实际开发中,我们可以根据具体的需求来进行选择。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Socket结合线程池使用实现客户端和服务端通信demo - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Android Navigation重建Fragment问题分析及解决

    我来详细讲解一下“Android Navigation重建Fragment问题分析及解决”的完整攻略。 什么是Navigation重建Fragment问题? 在使用Android Navigation组件时,如果使用了NavigationUI.setupWithNavController()来设置BottomNavigationView或者使用了AppBar…

    other 2023年6月27日
    00
  • Win11加载驱动失败怎么办?Win11无法加载驱动程序的三种解决方法

    当使用Windows 11时,我们可能会遇到一些驱动加载失败的问题,这会使系统无法正常工作。下面是三种解决方法: 方法一:使用设备管理器更新驱动程序 打开“设备管理器”,可以通过在搜索栏中输入“设备管理器”或使用快捷键“Win + X”,然后选择“设备管理器”。 在设备管理器窗口中,找到出现错误的设备并右键单击它,然后选择“更新驱动程序”。 在弹出的对话框中…

    other 2023年6月25日
    00
  • 个人作业2:APP案例分析

    个人作业2:APP案例分析 引言 在移动互联网时代,APP已经成为人们生活中不可或缺的一部分。越来越多的企业选择通过开发APP来构建自己的在线业务,但是APP开发和运营需要考虑到很多因素,例如用户体验、功能需求、安全性等等。在本篇文章中,我们将通过分析两个APP的案例,探讨如何在设计、开发和运营过程中平衡这些因素。 案例一:微信 作为全球最大的移动社交应用,…

    其他 2023年3月28日
    00
  • Fiddler抓包6-get请求(url详解)

    下面是“Fiddler抓包6-get请求(url详解)”的完整攻略,包括Fiddler的安装、抓包设置、抓包过程和两个示例等方面。 Fiddler的安装 首先,需要下载并安装Fiddler。可以使用以下步骤下载并安装Fiddler: 打开Fiddler官网; 下载Fiddler安装包; 运行安装包; 按照安装向导的提示完成安装。 安装完成后,可以开始设置Fi…

    other 2023年5月6日
    00
  • 《以太坊 2.0 术语库》信标链、PoS、分片…接触以太坊 2.0 得先理解这些术语

    让我来详细讲解一下以太坊 2.0 的一些关键术语。 1. 信标链 Beacon Chain 信标链(Beacon Chain)是以太坊 2.0 的核心组成部分,它是一条新的区块链,负责协调网络中的 PoS 共识算法和分片技术。在信标链上,每个验证者账户都负责验证一部分交易,并参与共识过程。信标链的引入可以提高以太坊的交易吞吐量和安全性。 例如,假设一个以太坊…

    other 2023年6月27日
    00
  • Linux系统下图形界面更改IP地址

    Linux系统下图形界面更改IP地址攻略 1. 打开网络设置 首先,我们需要打开Linux系统的网络设置界面。在大多数Linux发行版中,可以通过以下步骤打开网络设置: 在任务栏或系统托盘中找到网络图标,通常是一个无线信号图标或以太网图标。 单击鼠标右键,在弹出菜单中选择“网络设置”或类似选项。 2. 进入网络设置界面 一旦打开了网络设置界面,你将看到当前连…

    other 2023年7月31日
    00
  • Window系统的批处理变量大全

    Window系统的批处理变量大全攻略 介绍 在Windows系统的批处理脚本中,变量是一种非常有用的工具,可以存储和操作数据。本攻略将详细介绍Window系统的批处理变量,并提供一些示例说明。 系统变量 Windows系统提供了一些默认的系统变量,可以在批处理脚本中直接使用。以下是一些常用的系统变量: %DATE%:当前日期。 %TIME%:当前时间。 %U…

    other 2023年8月16日
    00
  • Xshell怎么开启布局管理?Xshell开启布局管理教程

    Xshell怎么开启布局管理 Xshell是一款功能强大的终端模拟器,可以通过开启布局管理来实现多个终端窗口的同时显示和管理。下面是详细的攻略: 步骤一:打开Xshell 首先,双击打开Xshell应用程序。 步骤二:创建新会话 在Xshell的菜单栏中,点击\”文件\”,然后选择\”新建\”,再选择\”会话\”。这将打开一个新的会话窗口。 步骤三:开启布局…

    other 2023年9月5日
    00
合作推广
合作推广
分享本页
返回顶部