Java 网络编程之TCP(五):分析服务端注册OP_WRITE写数据的各种场景(一)

在前面的文章中,我们分析了在NIO模式下,服务端接收和读取多个客户端数据的情况;

本文,我们看下NIO模式下,使用Selector如何把数据发送给客户端。

一、直接通过java.nio.channels.SocketChannel#write(java.nio.ByteBuffer)方法写

二、注册OP_WRITE,在写状态满足情况下,通过java.nio.channels.SocketChannel#write(java.nio.ByteBuffer)方法写

我们看下第一种情况:

服务端在有数据需要发送给客户端的情况下,直接使用客户端对应的SocketChannel写数据即可,不考虑数据是否可以正常发送。

这和BIO下,直接通过输出流写数据是一样的

我们可以基于聊天室的功能,把收到的数据直接写给其他客户端

客户端代码统一使用BIO,如下:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;/*** 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端**/
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);}}
}

服务端代码如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
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 NIOSelectorDirectWriteServer {Selector selector;public static void main(String[] args) throws IOException {NIOSelectorDirectWriteServer server = new NIOSelectorDirectWriteServer();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));// 转发给其他客户端forwardMsg(client, data);} else if (readLen == 0) {// 没读到数据System.out.println(client + " : no data");break;} else if (readLen < 0) {// client 关闭连接// 当客户端主动连接断开时,为了让服务器知道断开了连接,会产生OP_READ事件。所以需要判断读取长度,当读到-1时,关闭channel。System.out.println(client + " close");client.close();break;}}} catch (IOException exception) {exception.printStackTrace();// client 关闭连接System.out.println(client + " disconnect");// todo:disconnect 导致一直有read事件,怎么办?try {client.close();} catch (IOException ex) {System.out.println("close ex");}}}private void forwardMsg(SocketChannel myself, byte[] msg) throws IOException {Set<SelectionKey> keys = selector.keys();// read/write 对应同一个key,同一个client不会发送两遍for (SelectionKey key : keys) {SelectableChannel channel = key.channel();if (channel instanceof SocketChannel && channel != myself) {// 发送数据SocketChannel client = (SocketChannel) channel;System.out.println("forward msg to " + client);ByteBuffer buff = ByteBuffer.wrap(msg);while (buff.hasRemaining()) {client.write(buff);}}}}
}

可以看到,这里我们在forwardMsg()方法中,直接通过channel 把数据转发出去了

测试:

先启动服务器,然后启动两个客户端

客户端1发送消息client1,服务端接收到后转发给客户端2

客户端2发送消息client2,服务端接收到后转发给客户端1

服务端日志:

server init success
server receive a client :java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23536]
server receive a client :java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23549]
read handler
server read data from java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23536], data is :client1forward msg to java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23549]
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23536] : no data
read handler
server read data from java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23549], data is :client2forward msg to java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23536]
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23549] : no data

客户端1日志:

client connected to server
client1
client receive data from consolejava.io.BufferedInputStream@65231a33 : client1client receive data from serverjava.net.Socket$SocketInputStream@4629104a data size:8: client2

客户端2日志:

client connected to server
client receive data from serverjava.net.Socket$SocketInputStream@27f8302d data size:8: client1client2
client receive data from consolejava.io.BufferedInputStream@72cdfe9a : client2

测试:客户端1 正常退出关闭

客户端1日志:

exit
client receive data from consolejava.io.BufferedInputStream@65231a33 : exitclient close :Socket[addr=localhost/127.0.0.1,port=9090,localport=23536]
Exception in thread "main" java.net.SocketException: Socket closedat java.base/sun.nio.ch.NioSocketImpl.endRead(NioSocketImpl.java:248)at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:327)at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)at java.base/java.io.InputStream.read(InputStream.java:218)at com.huawei.io.chatroom.bio.ChatClient.main(ChatClient.java:29)Process finished with exit code 1

服务端日志:

read handler
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23536] close

测试:客户端2异常断开

服务端日志:

read handler
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:23549] disconnect
java.net.SocketException: Connection resetat java.base/sun.nio.ch.SocketChannelImpl.throwConnectionReset(SocketChannelImpl.java:394)at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:426)at com.huawei.io.chatroom.nio.sel.NIOSelectorDirectWriteServer.readHandler(NIOSelectorDirectWriteServer.java:107)at com.huawei.io.chatroom.nio.sel.NIOSelectorDirectWriteServer.doService(NIOSelectorDirectWriteServer.java:56)at com.huawei.io.chatroom.nio.sel.NIOSelectorDirectWriteServer.start(NIOSelectorDirectWriteServer.java:34)at com.huawei.io.chatroom.nio.sel.NIOSelectorDirectWriteServer.main(NIOSelectorDirectWriteServer.java:28)

二、注册OP_WRITE写数据

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux安装Matlab运行时

一般而言&#xff0c;安装Matlab的linux系统是带桌面版的&#xff0c;如果没带&#xff0c;不在本教程范围内。 一、下载Matlab 下载地址&#xff1a;MATLAB Runtime - MATLAB Compiler - MATLAB 本教程使用R2020b(9.9) 二、linux系统中进行解压 将zip传入linux系统&#xf…

Java23种设计模式-行为型模式之策略模式

策略模式&#xff08;Strategy Pattern&#xff09;&#xff1a;将算法的使用从算法的实现中分离出来&#xff0c;从而让算法的变化不会影响到使用算法的用户。 通常涉及三个角色&#xff1a; 1.上下文&#xff08;Context&#xff09;&#xff1a;持有策略接口的引用&#xf…

EigenLayer生态全解析:再质押与AVS崛起的序章

基于以太坊网络的再质押协议EigenLayer提出了利用为以太坊网络验证而质押的ETH来与其他协议共享安全性和资本效率&#xff0c;同时为协议参与者提供额外利息。在AVS、再质押、积分系统等概念的推动下&#xff0c;逐渐形成一个庞大的生态系统&#xff0c;从2024年初到现在EigenL…

Linux内核驱动开发-001字符设备开发-内核中断驱动独立按键

1驱动程序 /*************************************************************************> File Name: key_enit.c> Author: yas> Mail: rage_yashotmail.com> Created Time: 2024年04月22日 星期一 20时20分42秒**********************************************…

使用JS代理 实现大对象的功能拆解

序言 在Android开发中&#xff0c;可以通过webView的addJavascriptInterface方法注入一个对象到网页中。但是随着开发的需求越来越多。这个对象身上的方法也越来越多。这个对象对应的java类&#xff0c;体积越来越大&#xff0c;不利于维护。为了在不影响之前代码的基础上。把…

【C++干货基地】深度理解C++中的高效内存管理方式 new delete

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

Golang基础5-指针、结构体、方法、接口

指针 和c/c类似&#xff0c;但是go语言中指针不能进行偏移和运算&#xff0c;安全指针 &&#xff08;取地址) *(根据地址取值) nil(空指针&#xff09; make和new之前对比&#xff1a;make用于初始化slice&#xff0c;map&#xff0c;channel这样的引用类型 而new用于类…

Metasploit Framework(MSF)从入门到实战(一)

MSF的简介 目前最流行、最强大、最具扩展性的渗透测试平台软件 基于Metasploit进行渗透测试和漏洞分析的流程和方法 2003年由HD More发布第一版&#xff0c;2007年用ruby语言重写 架集成了渗透测试标准 (PETS&#xff09; 思想 一定程度上统一了渗透测试和漏洞研究的工作环…

针孔相机模型原理坐标系辨析内参标定流程内参变换

针孔相机的内参标定 针孔相机原理真空相机模型图片的伸缩和裁剪变换 内参标定———非线性优化张正定标定详细原理(含公式推导)通过多张棋盘格照片完成相机的内参标定流程(C代码)其他工具箱 相机分为短焦镜头和长焦镜头&#xff0c;短焦镜头看到的视野更广阔&#xff0c;同样距…

Debian常用命令

Debian是一种常见的Linux发行版&#xff0c;以下是一些常用的Debian命令&#xff1a; 1. apt-get&#xff1a;用于安装、升级和删除软件包。例如&#xff1a;apt-get install package_name。 2. dpkg&#xff1a;用于管理Debian软件包。例如&#xff1a;dpkg -i package_name…

白平衡简介

文章目录 白平衡的概念白平衡的调节常见的白平衡模式 白平衡的概念 白平衡是指摄影、摄像和显示技术中的一项重要概念&#xff0c;用于调节图像中的白色或中性灰色的色彩&#xff0c;使其看起来在不同光源条件下都是准确的白色或灰色。白平衡的主要目的是确保图像的色彩准确性…

C语言 | Leetcode C语言题解之第49题字母异位词分组

题目&#xff1a; 题解&#xff1a; /*1.将字符串原串与副本进行绑定成一个节点2.对字符串副本进行按ascii码表进行从小到大排序3.按照字符串进行比较排序4.合并 */ typedef struct Node{char*s;char*s_vice;int len; }Node;void sortShellChar(char*s,int len){for(int dista…

TS类型断言

类型断言&#xff08;Type Assertions&#xff09;在 TypeScript 中确实是一种在表达式级别上临时指定类型的机制&#xff0c;它允许开发者在编译时覆盖编译器的类型推断结果。类型断言有两种语法形式&#xff1a; “尖括号”语法&#xff1a; let someValue: any this is a s…

查找总价格为目标值的两个商品 ---- 双指针

题目链接 题目: 分析: 解法一: 暴力解法, 将每两个的和都算出来, 判断是否为目标值解法二: 数组中的数是按升序排序的, 我们可以定义左右指针 如果和小于目标值, 则应该让和变大, 所以左指针右移如果和大于目标值, 则应该让和变小, 所以右指针左移 思路: 定义left 0, righ…

python学习29:python中的字典dict

python中的字典dict 1.字典的定义&#xff1a; 同样是使用{},不过存储的元素是一个个的&#xff1a;键值对&#xff0c;如下语法&#xff1a; """ 字典的定义&#xff1a; #定义字典字面量&#xff1a;{key:value,key:value,....,key:value} #定义字典变量&am…

每天一个数据分析题(二百九十)——直方图与条形图

关于直方图与条形图的描述&#xff0c;下列说法正确的是&#xff08;&#xff09; A. 直方图用于展示分类型数据的分布情况 B. 直方图用来展示数值型数据的分布情况 C. 条形图只能用于展示顺序型数据的分布情况 D. 条形图只能用于展示分类型数据的分布情况 题目来源于CDA模…

绝地求生【商城更新】WIA联名上架//专属商店下架

大家好&#xff0c;我是闲游盒. 本周商城将在4.24&#xff08;周三&#xff09;更新&#xff0c;商城内容更新如下&#xff1a; 上架物品 ▲W.I.A联名皮肤大礼包 小礼包如下&#xff1a; 包含3套衣服以及MINI、DBS的联名皮肤&#xff0c;3个头饰还挺有特色的&#xff0c;你喜欢…

链栈算法库构建

学习贺利坚老师, 链栈 , 构建链栈算法库 数据结构之自建算法库——链栈_领会链栈结构和掌握链栈中的各种基本算法-CSDN博客文章浏览阅读3.9k次&#xff0c;点赞3次&#xff0c;收藏8次。本文针对数据结构基础系列网络课程(3)&#xff1a;栈和队列中第4课时栈的链式存储结构及其…

安全开发实战(3)--存活探测与端口扫描

目录 安全开发专栏 前言 存活探测 端口扫描 方式一: 1.3.1 One 1.3.2 Two 1.3.3 批量监测 方式二: 1.3.1 One 1.3.2 Two 1.3.3 Three 1.3.4 扫描ip地址,提取出开放端口和协议 ​编辑 1.3.5 批量扫描(最终完成版) 总结 安全开发专栏 安全开发实战​http://t.csd…

数据安全风险评估流程

数据安全风险评估是一个系统性的过程&#xff0c;旨在识别、评估和管理数据安全风险。以下是数据安全风险评估的一般流程及其内容&#xff1a; 确定评估范围&#xff1a;确定评估的范围和目标&#xff0c;包括评估的数据资产、系统、流程和相关方。 收集信息&#xff1a;收集有…