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

devtools/2025/1/15 19:52:02/

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

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

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

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

服务端代码:

java">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(); // 获取客户端的channeltry {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(); // 获取客户端的channelByteBuffer buffer = (ByteBuffer) key.attachment(); // 获取Client channel关联的bufferbuffer.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事件,怎么办?}}
}

客户端代码:

java">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 {// 连接serverSocket 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;}@Overridepublic 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 会自动关闭outputStreambreak;}// 发送数据给服务器端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 :client1java.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 :client2java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:53568] : no data

客户端1,exit 关闭连接

客户端2,异常关闭

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


http://www.ppmy.cn/devtools/11400.html

相关文章

redis主从复制,无法从redis读取最新的数据

目录 一、场景二、redis连接配置三、排查四、原因五、解决 一、场景 1、redis为主从复制模式 2、采用读写分离&#xff08;主节点写入&#xff0c;从节点读取&#xff09; 3、最新数据成功写入主节点&#xff0c;但从节点没有同步最新的数据 二、redis连接配置 #主节点 spr…

Vue3项目 网易严选_学习笔记

Vue3项目 网易严选_第一天 主要内容 项目搭建vuex基础路由设计首页顶部和底部布局 学习目标 知识点要求项目搭建掌握vuex基础掌握路由设计掌握首页顶部和底部布局掌握 一、项目搭建 1.1 创建项目 vue create vue-wangyi选择vue3.0版本 1.2 目录调整 大致步骤&#xff…

【笔记】ASP.NET Core 2.2 Web API —— 学习笔记

当年刚接触 ASP.NET Core 2.2 时&#xff0c;留下的学习笔记。现在把它挪到 CSDN&#xff0c;也是对过去学习 ASP.NET Core 痕迹进行记录。 VS 2019 ASP.NET Core 2.2 sqlSugarCore (ORM) 1. 仓储模式 服务 抽象接口 1.1 新建asp.net core 2.2 WebApi项目 nmmking.Core.…

【静态分析】静态分析笔记06 - 指针分析介绍

参考&#xff1a; 【课程笔记】南大软件分析课程6——指针分析介绍&#xff08;课时8&#xff09; - 简书 ----------------------------------------------------------------------------------------- 1. Motivation 指针分析必要性&#xff1a; 2. 指针分析 目标&…

金融时报:波场亮相哈佛大学并举办TRON Builder Tour活动

近日,波场TRON作为顶级白金赞助商出席哈佛区块链会议并成功举办TRON Builder Tour哈佛站活动,引发海外媒体热议。美联社、金融时报、Cointelegraph等国际主流媒体及加密知名媒体均对此给予了高度评价,认为本次大会对TRON Builder Tour活动具有里程碑意义,彰显了波场TRON致力于促…

【文章复现】基于主从博弈的社区综合能源系统分布式协同 优化运行策略

随着能源市场由传统的垂直一体式结构向交互竞争型 结构转变&#xff0c;社区综合能源系统的分布式特征愈发明显&#xff0c;传统 的集中优化方法难以揭示多主体间的交互行为。该文提出一 种基于主从博弈的社区综合能源系统分布式协同优化运行 策略&#xff0c;将综合能源销售商…

什么是大语言模型以及如何构建自己的大型语言模型?

一、关于大语言模型 LLM 对于无数的应用程序非常有用&#xff0c;如果我们自己从头开始构建一个&#xff0c;那我们可以了解底层的ML技术&#xff0c;并可以根据特定需求定制LLM&#xff0c;但是对资源的需求巨大。大型语言模型是一种 ML 模型&#xff0c;可以执行各种自然语言…

数据结构8:队列

文章目录 Queue.h 实现文件Queue.c 测试文件test.c #头文件 Queue.h #pragma once#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h>typedef int QListDataType;typedef struct QListNode {QListDataType val;struct QLi…