Java 网络编程之TCP(四):基于NIO中的selector实现服务端,BIO实现客户端

上一篇文章中,没有使用Selector,实习服务端的读取多个客户端的数据;本文先使用Selector实现读取多个客户单数据的功能,然后做些扩展。

一、基于NIO Selector读取多个客户的数据

1.服务端:基于Selector处理客户端的连接事件:OP_READ,处理客户端的数据具备事件:OP_READ

2.客户端:和上一篇一样,基于BIO实现连接和发送数据

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * 基于NIO实现服务端,通过Selector基于事件驱动客户端的读取
 *
 */
class NIOSelectorServer {
    Selector selector;

    public static void main(String[] args) throws IOException {
        NIOSelectorServer server = new NIOSelectorServer();
        server.start(); // 开启监听和事件处理
    }

    public void start() {
        initServer();
        // selector非阻塞轮询有哪些感兴趣的事件到了
        doService();
    }

    private void doService() {
        if (selector == null) {
            System.out.println("server init failed, without doing read/write");
            return;
        }
        try {
            while (true) {
                while (selector.select() > 0) {
                    Set<SelectionKey> keys = selector.selectedKeys(); // 感兴趣且准备好的事件
                    Iterator<SelectionKey> iterator = keys.iterator(); // 迭代器遍历处理,后面要删除集合元素
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove(); // 删除当前元素,防止重复处理
                        // 下面根据事件进行分别处理
                        if (key.isAcceptable()) {
                            // 客户端连接事件
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            // 读取客户端数据
                            readHandler(key);
                        }
                    }
                }
            }
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

    private void initServer() {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(9090));

            // 此时在selector上注册感兴趣的事件
            // 这里先注册OP_ACCEPT: 客户端连接事件
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("server init success");
        } catch (IOException exception) {
            exception.printStackTrace();
            System.out.println("server init failied");
        }
    }

    public void acceptHandler(SelectionKey key) {
        ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获取客户端的channel
        try {
            SocketChannel client = server.accept();
            client.configureBlocking(false); // 设置client非阻塞
            System.out.println("server receive a client :" + client);
            // 注册OP_READ事件,用于从客户端读取数据
            // 给Client分配一个buffer,用于读取数据,注意buffer的线程安全
            ByteBuffer buffer = ByteBuffer.allocate(1024); // buffer这个参数注册的时候也可以不用
            client.register(key.selector(), SelectionKey.OP_READ, buffer);
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

    public void readHandler(SelectionKey key) {
        System.out.println("read handler");
        SocketChannel client = (SocketChannel) key.channel(); // 获取客户端的channel
        ByteBuffer buffer = (ByteBuffer) key.attachment(); // 获取Client channel关联的buffer
        buffer.clear(); // 使用前clear

        // 防止数据分包,需要while循环读取
        try {
            while (true) {
                int readLen = client.read(buffer);
                if (readLen > 0) {
                    // 读取到数据了
                    buffer.flip();
                    byte[] data = new byte[buffer.limit()];
                    buffer.get(data);
                    System.out.println("server read data from " + client + ", data is :" + new String(data));
                } else if (readLen == 0) {
                    // 没读到数据
                    System.out.println(client + " : no data");
                    break;
                } else if (readLen == -1) {
                    // client 关闭连接
                    System.out.println(client + " close");
                    break;
                }
            }
        } catch (IOException exception) {
            // exception.printStackTrace();
            // client 关闭连接
            System.out.println(client + " disconnect");
            // todo:disconnect 导致一直有read事件,怎么办?
        }

    }
}

客户端代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端
 *
 * @author freddy
 */
class ChatClient {
    public static void main(String[] args) throws IOException {
        // 连接server
        Socket serverSocket = new Socket("localhost", 9090);
        System.out.println("client connected to server");

        // 读取用户在控制台上的输入,并发送给服务器
        new Thread(new ClientThread(serverSocket)).start();

        // 接收服务端发送过来的数据
        try (InputStream serverSocketInputStream = serverSocket.getInputStream();) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = serverSocketInputStream.read(buffer)) != -1) {
                String data = new String(buffer, 0, len);
                System.out.println(
                    "client receive data from server" + serverSocketInputStream + " data size:" + len + ": " + data);
            }
        }

    }
}

class ClientThread implements Runnable {
    private Socket serverSocket;

    public ClientThread(Socket serverSocket) {
        this.serverSocket = serverSocket;
    }

    @Override
    public void run() {
        // 读取用户在控制台上的输入,并发送给服务器
        InputStream in = System.in;
        byte[] buffer = new byte[1024];
        int len;
        try (OutputStream outputStream = serverSocket.getOutputStream();) {
            // read操作阻塞,直到有数据可读,由于后面还要接收服务端转发过来的数据,这两个操作都是阻塞的,所以需要两个线程
            while ((len = in.read(buffer)) != -1) {
                String data = new String(buffer, 0, len);
                System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));
                if ("exit\n".equals(data)) {
                    // 模拟客户端关闭连接
                    System.out.println("client close :" + serverSocket);
                    // 这里跳出循环后,try-with-resources 会自动关闭outputStream
                    break;
                }
                // 发送数据给服务器端
                outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

先启动服务端,再启动2个客户端,客户端发送数据

server init success
server receive a client :java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53568]
server receive a client :java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53584]
read handler
server read data from java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53584], data is :client1

java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53584] : no data
read handler
server read data from java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53568], data is :client2

java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53568] : no data

客户端1,exit 关闭连接

客户端2,异常关闭

会导致一直有read事件,这个要看看为啥

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/567726.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

书生·浦语大模型实战营之OpenXLab 部署 InternLM2 实践指南

书生浦语大模型实战营之OpenXLab 部署 InternLM2 实践指南 本文档将手把手教您如何在 OpenXLab 部署一个 InternLM2-7B chat 的应用 目录 资料介绍书生浦语 InternLM介绍OpenXLab浦源平台介绍部署 InternLM2-Chat-7B demo模型准备上传模型编写代码部署应用 资料介绍 书生浦语…

揭开ChatGPT面纱(1):准备工作(搭建开发环境运行OpenAI Demo)

文章目录 序言&#xff1a;探索人工智能的新篇章一、搭建开发环境二、编写并运行demo1.代码2.解析3.执行结果 本博客的gitlab仓库&#xff1a;地址&#xff0c;本博客对应01文件夹。 序言&#xff1a;探索人工智能的新篇章 随着人工智能技术的飞速发展&#xff0c;ChatGPT作为…

GITHUB的VB代码无法加载的问题解决

GITHUB里有不少好的VB代码&#xff0c;但是下载之后&#xff0c;经常出现工程加载出错的问题&#xff0c;例如&#xff1a; LOG文件为&#xff1a; 不能加载 0 行 0: 不能加载文件 D:\xxxx\Semi VB API Loader\frmMain.frm 。 原因其实很简单&#xff0c;github里的换行符是u…

OpenFE:开启数据特征工程新时代

OpenFE&#xff1a;开启数据特征工程新时代 数据特征工程是机器学习和数据分析领域中至关重要的一环&#xff0c;它涉及对原始数据进行处理和转换&#xff0c;以提取出有用的特征&#xff0c;为模型构建和预测提供更好的输入。在这个领域中&#xff0c;Python库OpenFE为数据科学…

高级控件4:Spinner

Spinner下拉列表组件 主要集合ArrayAdapter、SimpleAdapter以及自定义的Adapter&#xff08;继承自BaseAdapter&#xff09;配合使用实现下拉选择或者对话框中选择某一条目。下拉使用的更多&#xff0c;所以&#xff0c;接下来的案例也会重在演示下拉效果。 本次基本就是上代…

深入理解高级加密标准(Advanced Encryption Standard)

title: 深入理解高级加密标准&#xff08;Advanced Encryption Standard&#xff09; date: 2024/4/23 20:04:36 updated: 2024/4/23 20:04:36 tags: AES概述加密原理优势特点算法详解安全性应用实践案例分析 第一章&#xff1a;AES概述 AES的历史和背景 历史&#xff1a; 高…

【八股文】Spring 谈谈你对AOP的理解

AOP AOP(Aspect-Oriented Programming&#xff0c;面向切面编程)&#xff1a;是一种新的方法论&#xff0c;是对传统 OOP(Object-Oriented Programming&#xff0c;面向对象编程)的补充。 面向对象是纵向继承&#xff0c;面向切面是横向抽取。 OOP思想是一种垂直纵向的继承体…

上网行为管理软件怎么选 三款好用的上网行为管理软件

上网行为管理软件怎么选 三款好用的上网行为管理软件 一款优秀的上网行为管理软件可以满足企业的多种需求&#xff0c;帮助企业有效监督员工的行为&#xff0c;提升工作效率和企业效益&#xff0c;但是这些软件差异较大&#xff0c;选择的时候需要考虑这些因素。 1、明确需求 …

基于一款最多能够支持10000路的 modbus RS485 led灯光控制板做灯控程序

背景 介绍一款之前用过的一款设备&#xff0c;基于RS485通讯协议&#xff0c;控制LED灯或RGB灯带。 设备介绍 之前用它来做智能中药柜的灯控板&#xff0c;结合物联网网关&#xff0c;modbus采集&#xff0c;mqtt转发&#xff0c;以及mqtt的rpc指令下发 设备图片 功能说明 …

Java基本语法(基础部分)

Java基本语法 文章目录 Java基本语法前言一、准备工作1.1 计算机软件与硬件1.2 计算机编程语言1.3 Java语言概述&程序分析1.4 Java环境搭建&Java API1.5 Java核心机制JVM 二、变量2.1 关键字&标识符2.2 变量2.3 数据类型(基本数据类型)2.3.1 基本数据类型2.3.2 基本…

互联网营销两大宗师:周鸿祎和雷军做个人IP有什么不同?

前几天周鸿祎说要把自己的迈巴赫卖了,准备换国产新能源,还喊话让各个车企给他送车去体验。不少车企都送去了自己的最新车型,只有雷军直接回答,“等SUV出”。我们是在吃瓜,作者却是从中看到了新老营销宗师的手法不同。 最近,在纪念互联网30周年的座谈会上,发生了一件趣事…

JAVA网络编程、项目验证码实现

什么是网络编程? 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件、等等 不管是什么场景&#xff0c;都是计算机跟计算机之间通过网络进行数据传输 Java中可以使用ja…

wordpress建网站主题案例推荐

wordpress企业网站主题案例 https://www.mymoban.com/wordpress/ wordpress公司官网主题案例 https://www.wowsoho.com/jianzhan wordpress外贸主题案例 https://www.wpniu.com/moban

一维递归:递去

示例&#xff1a; /*** brief how about recursive-forward-1? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>…

深度学习-数据操作

目录 张量通过shape属性访问张量的形状通过shape属性访问张量中元素的总数reshape改变张量的形状&#xff08;不改变元素数量和元素值&#xff09;使用全0、全1、其他常量或者从特定分布中随机采样的数字通过提供包含数值的Python列表为所需张量中的每个元素赋予确定值。张量的…

记录交叉编译环境配置--海思开发板的 嵌入式nginx和 php的移植

嵌入式 lnmp搭建的记录 一些交叉编译的配置环境思路分享&#xff1a;P&#xff1a;php编译PHP可能遇到的问题configure阶段&#xff1a;Makefile-make阶段&#xff1a;Makefile-make install阶段&#xff1a; N&#xff1a;Nginx 文章比较水&#xff0c;并没有没解决什么实际问…

导出JVM的线程信息

1. 查询出Java应用的进程的PID ps -ef|grep java 此时的PID是 33 2. 使用JDK自带的工具jstack导出日志 jstack -l 33 > 2022jstack.log 3.然后直接下载

自动备份的小软件

自动备份的小软件 前几天有个小姐姐和我说&#xff0c;他的硬盘坏了&#xff0c;但是他有没有备份&#xff0c;所以我决定做一个自动备份的软件。 软件整体是使用pythonpyqt5做到。 github链接 软件截图 使用效果 使用方法 教程 流程图 优势 可以很大程度上解决数据丢失…

平均月薪超4.6万!AI领域重磅课程汇总,哈佛,斯坦福,微软,谷歌等出品!

2023年底&#xff0c;由脉脉高聘人才智库发布的《2023泛人工智能人才洞察》报告显示&#xff0c;2023年前八个月内新发布的AI岗位平均月薪超过了4.6万元&#xff0c;而且人才供不应求&#xff0c;甚至出现了5个岗位争夺2个人才的情况。 本文章整理了10项来自全球各高校与知名企…

手把手教数据结构与算法:有序线性表设计

问题描述 设计一个有序线性表类&#xff0c;要求完成初始化&#xff0c;插入和遍历功能&#xff0c;使得表内元素实现有序排列&#xff08;从小到大&#xff09;。同时实现合并功能&#xff0c;使得两个线性表能够合并为一个线性表&#xff08;可能存在重复元素&#xff09;。…
最新文章