电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

java基于netty NIO的簡單聊天室的實現

瀏覽:16日期:2022-08-28 18:21:56

一、為何要使用netty開發

由于之前已經用Java中的socket寫過一版簡單的聊天室,這里就不再對聊天室的具體架構進行細致的介紹了,主要關注于使用netty框架重構后帶來的改變。對聊天室不了解的同學可以先看下我的博客(《JAVA簡單聊天室的實現》)

本篇博客所使用的netty版本為4.1.36,完整工程已上傳到Github(https://github.com/Alexlingl/Chatroom),其中lib文件夾下有相應的netty jar包和source包,自行導入即可。

1、為何要重構

之前的聊天室是基于Java原生socket實現的,socket的處理機制屬于BIO模型,也就是阻塞IO模型。對于每一個客戶端的連接我們都需要啟動一個線程來處理,并且該線程會一直阻塞在讀取用戶數據上面。如此一來,一旦有大量的客戶端并發連接我們的服務器,服務器將難以承受。之前用JMeter測試過,單純使用Java原生socket開發的服務器所能支持的最大并發在2300左右。雖然采用線程池的策略可以在一定程度上提升最大并發數,但也無法超過1W。因此我們需要對其進行重構,使其能夠具有更高的性能。

2、為何使用netty框架

使用netty框架主要還是為了提升代碼的開發速度,并且減少代碼維護成本。使用netty框架開發的程序在復雜度上比使用Java原生NIO類庫開發的要小很多。具體可以看下我之前關于解決C10k問題的系列文章,里面有具體的代碼。

3、為何netty框架只實現了NIO而沒有AIO

前面在解決C10問題時,探究過NIO和AIO的區別,并且使用Java所提供的類庫實現了兩個小程序,理論上來說AIO性能明顯要比NIO高,那為什么netty使用了NIO而不是AIO呢?

官方說法如下:

We obviously did not consider Windows as a serious platform so far, and that’s why we were neglecting NIO.2 AIO API which was implemented using IOCP on Windows. (On Linux, it wasn’t any faster because it was using the same OS facility - epoll.)

大意就是,windows上面有IOCP來支持AIO的實現,因此AIO的性能會比NIO好。而Linux上面不管是NIO還是AIO,底層都是用epoll實現的,性能差距不大。然而當下絕大部分的服務器還是建立在Linux上,因此沒必要使用AIO(使用AIO反而會增加代碼的復雜度,增大維護成本)。

二、基于netty NIO的處理模型

1、服務器的類關系

(1)、SubreqServer:創建兩個NIO線程組,一個用來監聽處理客戶端的連接請求,一個用來監聽客戶端的消息。同時實例化一個ServerBootStrap啟動類的對象來啟動兩個NIO線程組,并且配置必要的參數。(2)、ChannelInitializer:初始化SocketChannel管道的各項參數,主要有指定解碼器和編碼器,并指明管道的處理類(3)、SubreqServerHandler:SocketChannel管道的處理類,負責處理來自客戶端的消息

2、客戶端的類關系

(1)、SubreqClient:創建一個NIO線程組,用來監聽客戶端的消息。同時實例化一個ServerBootStrap啟動類的對象來啟動這個NIO線程組,并且配置必要的參數。最后讓主程序阻塞在監聽客戶端的鍵盤輸入。(2)、ChannelInitializer:初始化SocketChannel管道的各項參數,主要有指定解碼器和編碼器,并指明管道的處理類(3)、SubreqClientHandler:SocketChannel管道的處理類,負責處理來自服務器的消息

三、所涉及類庫的源碼解讀

1、ChannelHandlerAdapter(用來對channel的注冊和注銷做出反應的類):

(1)、功能

用來實現當用戶上線或下線時,通知其他在線的用戶。

(2)、類定義

它是ChannelHandler的框架實現

java基于netty NIO的簡單聊天室的實現

(3)、HandlerAdded()和HandlerRemoved()方法

java基于netty NIO的簡單聊天室的實現

當有一個channel被注冊后將會調用HandlerAdded(),而當有一個channel被注銷后將調用HandlerRemoved()方法。并且根據注釋我們知道,這兩個方法默認不做任何處理,它希望由繼承的子類自己去寫相應的處理實現。

2、SimpleChannelInboundHandler(用來對接收的消息做出反應)

(1)、功能

用來實現當接收到消息時做出相應反應,如果是服務端,那么將當前消息轉發給其他在線的客戶端;如果是客戶端,就將消息簡單地打印出來。

(2)、類定義

java基于netty NIO的簡單聊天室的實現

java基于netty NIO的簡單聊天室的實現

這個類是一個泛型類,它只能用于處理一種具體類型的消息。注釋中給出一種使用方法,StringHandler繼承了SimpleChannelInboundHandler,并且指定泛型變量為String,因此這個繼承類只能用于String類型消息的處理。

(2)、channelRead(ChannelHandlerContext ctx, Object msg)

java基于netty NIO的簡單聊天室的實現

這個方法主要用來處理類型為Object的消息,也就是所有消息。

首先當接收到msg時,先使用accpetInboundMessage()方法來判斷該消息是否可以處理。我們來看下該方法的實現。

java基于netty NIO的簡單聊天室的實現

如果接收到的這個消息應該被處理就返回true,如果它應當被傳到ChannelPipeLine的下一個ChannelInboundHandler就返回false。

我們繼續來看下ChannelRead()方法。如果接受到的消息可以處理時。它將對消息進行強制轉化,將其轉為I,并且調用channelRead0()。它是一個抽象類,因此我們在繼承SimpleChannelInboundHandler類時,需要根據自己的實際去實現這個方法。

java基于netty NIO的簡單聊天室的實現

四、關鍵的代碼實現

1、server端

(1)、SubreqServer類

package nettyserverv1; import java.util.ArrayList; import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.*;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.serialization.ClassResolvers;import io.netty.handler.codec.serialization.ObjectDecoder;import io.netty.handler.codec.serialization.ObjectEncoder; /** * Created by linguolong on 2019/05/08. * Chatroom server built using netty framework */ public class SubreqServer {//保存已注冊的用戶信息public static ArrayList<UserInfo> userlist = new ArrayList<UserInfo>();//自動生成注冊用戶static{for(int i=0;i<10;i++){UserInfo user=new UserInfo();user.setUserID('123'+i);user.setUserName('user'+i);user.setPassword('pwd'+i);userlist.add(user);}} public void bind(int port) throws Exception{ //配置服務端NIO 線程組 EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap server = new ServerBootstrap(); try { server.group(boss, worker) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { /*** 解碼器: 構造器傳入了兩個參數: * #1 單個對象序列化后最大字節長度,這是設置是1M;* #2 類解析器: weakCachingConcurrentResolver創建線程安全的WeakReferenceMa對類加載器進行緩存,* 支持多線程并發訪問,當虛擬機內存不足時,會釋放緩存中的內存,防止內存泄漏.*/ ch.pipeline().addLast(new ObjectDecoder(1024*1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())) ); ch.pipeline().addLast(new ObjectEncoder()); ch.pipeline().addLast(new SubreqServerHandler()); } }); System.out.println('Start the server success'); //綁定端口, 同步等待成功 ChannelFuture future = server.bind(port).sync(); //等待服務端監聽端口關閉 future.channel().closeFuture().sync(); } finally { //優雅關閉 線程組 boss.shutdownGracefully(); worker.shutdownGracefully(); } } public static void main(String[] args) { SubreqServer server = new SubreqServer(); try { server.bind(18888); } catch (Exception e) { e.printStackTrace(); } }}

(2)、SubreqServerHandler類

package nettyserverv1; import io.netty.channel.Channel;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.channel.group.DefaultChannelGroup;import io.netty.channel.group.ChannelGroup;import io.netty.util.concurrent.GlobalEventExecutor; /** * Created by linguolong on 2019/05/08. * Chatroom client built using netty framework */ public class SubreqServerHandler extends SimpleChannelInboundHandler<String>{//新建一個channelGroup,用于存放連接的channelpublic static ChannelGroup online_channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overridepublic void handlerRemoved(ChannelHandlerContext ctx){ Channel leave_channel = ctx.channel(); for (Channel channel : online_channels) { if (channel != leave_channel){ channel.writeAndFlush('[用戶 ' + leave_channel.remoteAddress() + ']下線了!n'); } } System.out.println(ctx.channel().id()+'下線了');//把剛下線的channel移除出在線用戶隊列online_channels.remove(leave_channel);} @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //判斷用戶是否已經登錄 if(!check_login(ctx)){ //未登錄則進行登錄驗證 check_identity(ctx,msg); }else{ //已登錄則進行消息轉發 Channel coming_channel = ctx.channel(); for (Channel channel : online_channels) { if (channel != coming_channel){ channel.writeAndFlush('[用戶 ' + coming_channel.remoteAddress() + ']: ' + msg ); } else { channel.writeAndFlush('[我]: ' + msg); } } } } //用戶信息驗證,檢查用戶ID和密碼是否正確 public void check_identity(ChannelHandlerContext ctx, Object msg){ UserInfo req = (UserInfo) msg; System.out.println('service receive client login req :{'+ req.toString() +'}'); boolean login_flag = false; for(int i=0;i<SubreqServer.userlist.size();i++){ if( SubreqServer.userlist.get(i).getUserID().equalsIgnoreCase(req.getUserID())&&(SubreqServer.userlist.get(i).getPassword().equals(req.getPassword()))){ login_flag=true; } } if(login_flag){ System.out.println('賬號'+req.getUserID()+'登錄成功'); ctx.writeAndFlush('您已登錄成功~n'); //將當前的通道加入在線隊列中 online_channels.add(ctx.channel()); } else{ System.out.println('賬號'+req.getUserID()+'登錄失敗'); ctx.writeAndFlush('登錄失敗!'); //關閉連接 ctx.close(); online_channels.remove(ctx.channel()); } } //判斷用戶是否已經在線 public boolean check_login(ChannelHandlerContext ctx){ boolean online_flag = false; for(int i=0;i<online_channels.size();i++){ if(online_channels.contains(ctx.channel())){ online_flag = true; } }return online_flag; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //釋放資源 ctx.close(); }@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {} //ChannelRead0()只能處理類型為String的消息,因此我們這里不能用ChannelRead0()這個方法,這里的第二個參數類型使用了泛型@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {}}

(3)、用戶信息UserInfo類

package nettyserverv1; import java.io.Serializable; /** * Created by linguolong on 2019/05/08. * Chatroom User Infomation */ public class UserInfo implements Serializable{ private String userID; private String userName; private String password; public String getUserID() { return userID; } public void setUserID(String userID) { this.userID = userID; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Override public String toString() { return 'SubscribeReq: [messageID]:'+ userID + ' [userName]:' +userName + ' [password]:' +password; } }

2、Client端

(1)、SubreqClient類

package nettyclientv1; import java.io.BufferedReader;import java.io.InputStreamReader; import io.netty.bootstrap.Bootstrap;import io.netty.channel.Channel;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import io.netty.handler.codec.serialization.ClassResolvers;import io.netty.handler.codec.serialization.ObjectDecoder;import io.netty.handler.codec.serialization.ObjectEncoder; /** * Created by linguolong on 2019/05/08. * Chatroom client built using netty framework */ public class SubreqClient { public void connect(int port, String host) throws Exception{ //配置客戶端NIO 線程組 EventLoopGroup group = new NioEventLoopGroup(); Bootstrap client = new Bootstrap(); try { client.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //添加解碼器 ch.pipeline().addLast(new ObjectDecoder(1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())) ); //添加編碼器 ch.pipeline().addLast(new ObjectEncoder()); ch.pipeline().addLast(new SubreqClientHandler()); } }); //異步獲取當前已連接的channel Channel now_channel = client.connect(host,port).sync().channel(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); //異步等待客戶端連接端口關閉// now_channel.closeFuture().sync(); //使客戶端一直處于輸入狀態,直到讀取到'bye' String message = ' '; while (true) { //讀到bye時退出 if(message.equals('bye')) break; message = reader.readLine(); now_channel.writeAndFlush(message+'n'); } //讀到了'bye'字符串,主動斷開連接 now_channel.close(); } finally { //優雅關閉 線程組 group.shutdownGracefully(); } } public static void main(String[] args) { SubreqClient client = new SubreqClient(); try { client.connect(18888, '127.0.0.1'); } catch (Exception e) { e.printStackTrace(); } }}

(2)、SubreqClientHandler類

package nettyclientv1; import nettyserverv1.UserInfo;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter; /** * Created by linguolong on 2019/05/08. * Chatroom client built using netty framework */ public class SubreqClientHandler extends ChannelInboundHandlerAdapter{ public SubreqClientHandler() { } /** * @param ctx * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(subReq('1231','user1','pwd1')); ctx.flush(); } private UserInfo subReq(String id,String userName,String password){ UserInfo req = new UserInfo(); req.setUserID(id); req.setUserName(userName); req.setPassword(password); return req; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.print(msg.toString()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); }}

五、說明

1、登錄功能的實現:當客戶端剛連接上服務器時,便構造一個UserInfo對象,對對象進行編碼后發送給服務器。服務器接收到后對其進行解碼,驗證相應的賬戶ID和密碼是否正確。

到此這篇關于java基于netty NIO的簡單聊天室的實現的文章就介紹到這了,更多相關java簡單聊天室內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 挤塑板-XPS挤塑板-挤塑板设备厂家[襄阳欧格] | 手术室净化厂家-成都做医院净化工程的公司-四川华锐-15年特殊科室建设经验 | 四川成人高考_四川成考报名网| 济南网站建设_济南网站制作_济南网站设计_济南网站建设公司_富库网络旗下模易宝_模板建站 | 广州二手电缆线回收,旧电缆回收,广州铜线回收-广东益福电缆线回收公司 | 东莞市海宝机械有限公司-不锈钢分选机-硅胶橡胶-生活垃圾-涡电流-静电-金属-矿石分选机 | 塑料瓶罐_食品塑料瓶_保健品塑料瓶_调味品塑料瓶–东莞市富慷塑料制品有限公司 | 选矿设备-新型重选设备-金属矿尾矿重选-青州冠诚重工机械有限公司 | 工控机-图像采集卡-PoE网卡-人工智能-工业主板-深圳朗锐智科 | 小程序开发公司-小程序制作-微信小程序开发-小程序定制-咏熠软件 | 【星耀裂变】_企微SCRM_任务宝_视频号分销裂变_企业微信裂变增长_私域流量_裂变营销 | 合肥仿石砖_合肥pc砖厂家_合肥PC仿石砖_安徽旭坤建材有限公司 | 座椅式升降机_无障碍升降平台_残疾人升降平台-南京明顺机械设备有限公司 | 成都顶呱呱信息技术有限公司-贷款_个人贷款_银行贷款在线申请 - 成都贷款公司 | 钢衬四氟管道_钢衬四氟直管_聚四氟乙烯衬里管件_聚四氟乙烯衬里管道-沧州汇霖管道科技有限公司 | 齿轮减速机_齿轮减速电机-VEMT蜗轮蜗杆减速机马达生产厂家瓦玛特传动瑞环机电 | 电镀电源整流器_高频电解电源_单脉双脉冲电源 - 东阳市旭东电子科技 | 太平洋亲子网_健康育儿 品质生活 | 发电机价格|发电机组价格|柴油发电机价格|柴油发电机组价格网 | 真空干燥烘箱_鼓风干燥箱 _高低温恒温恒湿试验箱_光照二氧化碳恒温培养箱-上海航佩仪器 | 橡胶接头_橡胶软接头_套管伸缩器_管道伸缩器厂家-巩义市远大供水材料有限公司 | 卫生人才网-中国专业的医疗卫生医学人才网招聘网站! | 回收二手冲床_金丰旧冲床回收_协易冲床回收 - 大鑫机械设备 | 【北京写字楼出租_写字楼租赁_办公室出租网/出售】-远行地产官网 | 欧洲MV日韩MV国产_人妻无码一区二区三区免费_少妇被 到高潮喷出白浆av_精品少妇自慰到喷水AV网站 | 校车_校车价格_19座幼儿园校车_幼儿园校车_大鼻子校车 | 光泽度计_测量显微镜_苏州压力仪_苏州扭力板手维修-苏州日升精密仪器有限公司 | 冷水机-工业冷水机-冷水机组-欧科隆品牌保障 | 板框压滤机-隔膜压滤机-厢式压滤机生产厂家-禹州市君工机械设备有限公司 | 银川美容培训-美睫美甲培训-彩妆纹绣培训-新娘化妆-学化妆-宁夏倍莱妮职业技能培训学校有限公司 临时厕所租赁_玻璃钢厕所租赁_蹲式|坐式厕所出租-北京慧海通 | 钛合金标准件-钛合金螺丝-钛管件-钛合金棒-钛合金板-钛合金锻件-宝鸡远航钛业有限公司 | 东莞爱加真空科技有限公司-进口真空镀膜机|真空镀膜设备|Polycold维修厂家 | 反渗透水处理设备|工业零排放|水厂设备|软化水设备|海南净水设备--海南水处理设备厂家 | ET3000双钳形接地电阻测试仪_ZSR10A直流_SXJS-IV智能_SX-9000全自动油介质损耗测试仪-上海康登 | 江苏农村商业银行招聘网_2024江苏农商行考试指南_江苏农商行校园招聘 | 香蕉筛|直线|等厚|弧形|振动筛|香蕉筛厂家-洛阳隆中重工 | 安全光栅|射频导纳物位开关|音叉料位计|雷达液位计|两级跑偏开关|双向拉绳开关-山东卓信机械有限公司 | _网名词典_网名大全_qq网名_情侣网名_个性网名 | 武汉创亿电气设备有限公司_电力检测设备生产厂家 | 通用磨耗试验机-QUV耐候试验机|久宏实业百科 | PSI渗透压仪,TPS酸度计,美国CHAI PCR仪,渗透压仪厂家_价格,微生物快速检测仪-华泰和合(北京)商贸有限公司 |