使用最新Netty實現一個簡單的聊天程式
阿新 • • 發佈:2019-02-15
1、概述
Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、事件驅動的網路應用程式框架和工具,用以快速開發高效能、高可靠性的網路伺服器和客戶端程式。
目前最新的是版本是4.1.4(5.x版本官網已經棄用,不推薦使用)。
Netty4.x同時也是原生支援Android的,所以後面的程式,放到android上也是可以正常執行的(親測)。
專案如果使用Maven開發,直接新增以下依賴即可:
2、實現服務端
首先是訊息處理類ServerHandler:
- package com.anxpp.im.server.handler;
- import io.netty.channel.ChannelHandler;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter
; - import java.io.IOException;
- import com.anxpp.im.common.IMMessage;
- import com.anxpp.im.common.OnlineUser;
- import com.anxpp.im.server.config.BaseConfig;
- @ChannelHandler.Sharable
- publicclassServerHandlerextendsChannelInboundHandlerAdapterimplementsBaseConfig{
- privateChannelHandlerContext ctx;
- @Override
- public
void handlerAdded(ChannelHandlerContext ctx)throwsException{ - System.err.println("服務端Handler建立...");
- super.handlerAdded(ctx);
- }
- @Override
- publicvoid channelInactive(ChannelHandlerContext ctx)throwsException{
- System.err.println("channelInactive");
- super.channelInactive(ctx);
- }
- /**
- * tcp鏈路建立成功後呼叫
- */
- @Override
- publicvoid channelActive(ChannelHandlerContext ctx)throwsException{
- this.ctx = ctx;
- System.err.println("有客戶端連線:"+ctx.channel().remoteAddress().toString());
- }
- /**
- * 傳送訊息
- */
- publicboolean sendMsg(IMMessage msg)throwsIOException{
- System.err.println("伺服器推送訊息:"+msg);
- ctx.writeAndFlush(msg);
- return msg.getMsg().equals("q")?false:true;
- }
- @Override
- publicvoid channelRead(ChannelHandlerContext ctx,Object msg)throwsIOException{
- System.err.println("伺服器接收到訊息:"+msg);
- IMMessage message =(IMMessage)msg;
- if(OnlineUser.get(message.getReceiveId())==null){
- OnlineUser.put(message.getUid(), ctx);
- }
- ChannelHandlerContext c =OnlineUser.get(message.getReceiveId());
- if(c==null){
- message.setMsg("對方不線上!");
- OnlineUser.get(message.getUid()).writeAndFlush(message);
- }
- else
- c.writeAndFlush(message);
- }
- /**
- * 異常處理
- */
- @Override
- publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
- System.err.println("與客戶端斷開連線:"+cause.getMessage());
- cause.printStackTrace();
- ctx.close();
- }
- }
接收客戶端連線,如果客戶端沒有連線過,就儲存ChannelHandlerContext到Map中用於傳送訊息。然後判斷訊息接收方是否線上,若線上就直接傳送訊息,沒有線上就返回“對方不線上”。當然,可以將訊息傳送者和訊息接收者ID設定為相同就能僅開一個客戶端自己跟自己聊天(類似Echo Server)。
伺服器程式Server:
- package com.anxpp.im.server;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- 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.NioServerSocketChannel;
- import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
- import io.netty.handler.codec.LengthFieldPrepender;
- import java.io.IOException;
- import java.util.Scanner;
- import com.anxpp.im.common.IMMessage;
- import com.anxpp.im.common.MsgPackDecode;
- import com.anxpp.im.common.MsgPackEncode;
- import com.anxpp.im.server.config.BaseConfig;
- import com.anxpp.im.server.handler.ServerHandler;
- publicclassServerimplementsRunnable,BaseConfig{
- ServerHandler serverHandler =newServerHandler();
- publicstaticvoid main(String[] args)throwsIOException{
- newServer().start();
- }
- publicvoid start()throwsIOException{
- newThread(this).start();
- runServerCMD();
- }
- /**啟動服務端控制檯
- * @throws IOException */
- privatevoid runServerCMD()throwsIOException{
- //服務端主動推送訊息
- int toID =1;
- IMMessage message =newIMMessage(
- APP_IM,
- CLIENT_VERSION,
- SERVER_ID,
- TYPE_MSG_TEXT,
- toID,
- MSG_EMPTY);
- @SuppressWarnings("resource")
- Scanner scanner =newScanner(System.in);
- do{
- message.setMsg(scanner.nextLine());
- }
- while(serverHandler.sendMsg(message));
- }
- publicvoid run(){
- EventLoopGroup bossGroup =newNioEventLoopGroup();
- EventLoopGroup workerGroup =newNioEventLoopGroup();
- try{
- ServerBootstrap b =newServerBootstrap();
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024)
- // .childOption(ChannelOption.SO_KEEPALIVE, true)
- .childHandler(