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日

相关文章

  • IOS初始化控制器的实现方法总结

    下面我将详细讲解 iOS 初始化控制器的实现方法总结。 前言 在 iOS 开发中,控制器(ViewController)是不可或缺的角色,而初始化控制器是使用控制器的第一步。本文将分享 iOS 初始化控制器的实现方法总结。 常见的初始化控制器方法 在 iOS 开发中,初始化控制器的方法非常丰富,我将列出其常见的方法: 实例化控制器对象 这种方法是最常见的初始…

    other 2023年6月20日
    00
  • QT实现贪吃蛇游戏代码详解

    QT实现贪吃蛇游戏代码详解 1. 介绍 贪吃蛇是一款经典的游戏,在QT中实现贪吃蛇游戏,可以通过练习,加深对游戏编程的理解,也可以加深对QT编程的熟练程度。 2. 程序结构 在QT中实现贪吃蛇游戏,建议采用以下的结构: – main.cpp – mainwindow.h – mainwindow.cpp – snake.h – snake.cpp 其中,ma…

    other 2023年6月26日
    00
  • vue 使用vant插件做tabs切换和无限加载功能的实现

    下面是详细讲解“Vue 使用 Vant 插件做 Tabs 切换和无限加载功能的实现”的完整攻略。 1. 安装 Vant 插件 要使用 Vant 插件,我们首先需要在我们的项目中安装它。可以通过命令行来安装: npm i vant -S 安装完成之后,我们可以在项目的 main.js 文件中引入 Vant: import Vant from ‘vant’; i…

    other 2023年6月25日
    00
  • Win8学生开发者账号的免费注册流程

    Win8学生开发者账号的免费注册流程如下: 步骤一:注册微软学生俱乐部账号 首先,进入微软学生俱乐部(Microsoft Imagine),点击右上角“注册”按钮; 选择“学生”选项,并填写相关信息,包括姓名、所在国家、出生日期、邮箱等; 接下来,选择“验证方式”,可以通过学校邮箱或学生证明进行验证; 在完成验证后,您需要等待微软对您的账号验证,一般需要1-…

    other 2023年6月26日
    00
  • QQ怎么设置自定义皮肤?

    下面是详细的攻略说明: QQ怎么设置自定义皮肤? 1. 下载皮肤素材 首先,你需要找到喜欢的QQ皮肤素材,可以在相关网站或者社交平台上搜寻并下载。通常,皮肤素材都会包含一个”*.zip”的压缩包,里面包含了相应的皮肤素材文件。在下载之前,你需要确保素材来源可信。 2. 解压缩皮肤文件 下载皮肤素材后,你需要解压缩文件。可以使用Windows系统自带的压缩软件…

    other 2023年6月25日
    00
  • Python 3 实现定义跨模块的全局变量和使用教程

    Python 3 实现定义跨模块的全局变量和使用教程 在Python中,全局变量是在整个程序中都可访问的变量。然而,当我们使用多个模块时,要在不同的模块之间共享全局变量可能会有些困难。在本教程中,我们将学习如何在不同的模块之间定义和使用跨模块的全局变量。 方法一:使用模块 一个简单的方法是创建一个专门用于存储全局变量的模块。我们可以在这个模块中定义全局变量,…

    other 2023年7月28日
    00
  • 解析linux或android添加文件系统的属性接口的方法

    下面是详细讲解“解析linux或android添加文件系统的属性接口的方法”的攻略。 什么是文件系统属性接口 在Linux或Android中,每个文件或目录都有一些属性,如权限、所有者、大小等等。这些属性可以通过文件系统属性接口来读取或修改。 添加文件系统属性接口 如果您想要为您的文件系统添加自定义属性,您可以遵循以下步骤: 步骤1:实现文件系统操作 添加自…

    other 2023年6月26日
    00
  • python-使用pip安装flask

    以下是关于“Python使用pip安装Flask”的完整攻略,包括环境准备、安装步骤、示例说明和注意事项。 环境准备 在安装Flask之前,需要先准备好Python环境。可以使用以下命令检查Python版本: python –version 如果Python未安装或版本过低,可以使用以下命令安装Python: sudo apt-get update sud…

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