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日

相关文章

  • C语言指针超详细讲解上篇

    【C语言指针超详细讲解上篇】 一、指针的概念 指针变量是指保存地址的变量,即它的值就是一个地址。通过指针变量可以间接访问该地址处的数据。在C语言中,指针变量可以用来访问位于数组中的元素,也可以用来访问一段连续的数据。 二、指针变量的声明与初始化 指针变量的声明方式与普通变量不同,需要在变量名前面添加星号“*”符号。指针变量的初始化可以为该指针赋值为一个变量的…

    other 2023年6月27日
    00
  • php的大小写敏感问题整理

    PHP的大小写敏感问题整理 1. 理解大小写敏感性 PHP是一种区分大小写的编程语言,这意味着在PHP中,标识符(如变量、函数名、类名等)的大小写是敏感的。这就意味着”hello”和”Hello”是不同的标识符。 2. 变量的大小写敏感 在PHP中,变量的大小写是敏感的,这意味着定义、赋值和访问变量时必须保持一致的大小写。 示例1: <?php $na…

    other 2023年6月28日
    00
  • View事件分发原理和ViewPager+ListView嵌套滑动冲突

    View事件分发原理 在Android中,View事件分发是指将触摸事件从父View传递到子View的过程。View事件分发涉及到三个方法:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。 dispatchTouchEvent():该方法用于分发触摸事件,它会根据事件类型和触摸位置将事件…

    other 2023年7月28日
    00
  • js变量作用域及可访问性的探讨

    JS变量作用域及可访问性的探讨 在JavaScript中,变量的作用域和可访问性是非常重要的概念。了解这些概念可以帮助我们编写更具可维护性和可扩展性的代码。本攻略将详细讲解JavaScript中的变量作用域和可访问性,并提供两个示例来说明这些概念。 1. 变量作用域 变量作用域指的是变量在代码中的可见范围。在JavaScript中,有三种变量作用域:全局作用…

    other 2023年7月29日
    00
  • C#实现DataList里面嵌套DataList的折叠菜单

    C#实现DataList里面嵌套DataList的折叠菜单攻略 1. 准备工作 在开始实现之前,确保你已经安装了适当的开发环境,比如Visual Studio,并且已经创建了一个C#项目。 2. 创建数据模型 首先,我们需要创建一个数据模型来表示菜单项。假设我们的菜单项有以下属性:Id、Name、ParentId和Children。Id是菜单项的唯一标识符,…

    other 2023年7月28日
    00
  • Jmeter笔记:响应断言详解

    下面是“Jmeter笔记:响应断言详解”的完整攻略,包括基本原理、实现方法和两个示例说明。 基本原理 Jmeter是一款开源的压力测试工具,可以模拟多种协议和场景进行测试。响应断言是Jmeter中的一种断言方式,用于检查服务器响应是否符合预期。响应断言可以检查响应的内容、响应头、响应代码等多个方面,以确保服务器响应的正确性。 实现方法 实现响应断言的方法如下…

    other 2023年5月5日
    00
  • Java中构造器内部的多态方法的行为实例分析

    Java中构造器内部的多态方法的行为实例分析 在Java中,构造器内部的多态方法的行为可能会有一些令人困惑的地方。本攻略将详细讲解这个问题,并提供两个示例来说明。 1. 多态方法的定义 多态方法是指在父类中定义的方法,可以被子类重写。当使用子类对象调用这个方法时,会根据实际的对象类型来确定调用哪个版本的方法。 2. 构造器内部的多态方法 在构造器内部调用多态…

    other 2023年8月6日
    00
  • C语言的模板与泛型编程你了解吗

    C语言的模板与泛型编程攻略 概述 模板与泛型编程是现代高级编程语言的一个重要特性,旨在提高代码的复用和灵活性。但在C语言中并不直接支持模板和泛型编程,因此需要通过一些技巧和工具去实现相应的功能。本文将针对C语言的模板与泛型编程做详细的讲解。 C语言中的模板 宏定义 宏定义是C语言中实现模板的一种方式,可以通过宏定义来实现泛型编程的功能。 下面是一个示例,定义…

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