netty+websocket实现简单的在线聊天器

HttpRequestHandler

package org.lmj.chatroom;

import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.net.URL;

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private final String wsUri;
    private static File indexFile;  //文件不可改


    static {
        URL location = HttpRequestHandler.class
                .getProtectionDomain()
                .getCodeSource().getLocation();
        try {
            String path = location.toURI() + "index.html";
            path = !path.contains("file:") ? path : path.substring(5);
            indexFile = new File(path);
        } catch (URISyntaxException e) {
            throw new IllegalStateException(
                    "Unable to locate index.html", e);
        }
    }

    public HttpRequestHandler(String wsUri) {
        this.wsUri = wsUri;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx,
                             FullHttpRequest request) throws Exception {
        if (wsUri.equalsIgnoreCase(request.getUri())) {
            ctx.fireChannelRead(request.retain());
        } else {
            if (HttpHeaders.is100ContinueExpected(request)) {
                send100Continue(ctx);
            }
            RandomAccessFile file = new RandomAccessFile(indexFile, "r");
            HttpResponse response = new DefaultHttpResponse(
                    request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(
                    HttpHeaders.Names.CONTENT_TYPE,
                    "text/html; charset=UTF-8");
            boolean keepAlive = HttpHeaders.isKeepAlive(request);
            if (keepAlive) {
                response.headers().set(
                        HttpHeaders.Names.CONTENT_LENGTH, file.length());
                response.headers().set( HttpHeaders.Names.CONNECTION,
                        HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.write(response);
            if (ctx.pipeline().get(SslHandler.class) == null) {
                ctx.write(new DefaultFileRegion(
                        file.getChannel(), 0, file.length()));
            } else {
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture future = ctx.writeAndFlush(
                    LastHttpContent.EMPTY_LAST_CONTENT);
            if (!keepAlive) {
                future.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

TextWebSocketFrameHandler

package org.lmj.chatroom;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;


//纯文本的textsocket数据帧
//public class TextWebSocketFrameHandler implements ChannelHandler, EventExecutorGroup { 简化开发
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static ChannelGroup channels=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);//保存所有channel

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //super.handlerAdded(ctx);  //当有客户端连接时  连接上来的客户端的通道
        Channel incoming=ctx.channel();
        for (Channel ch:channels){
            if (ch!=incoming){
                ch.writeAndFlush("欢迎:"+incoming.remoteAddress()+"进入聊天室");
            }
        }

        channels.add(incoming);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //super.handlerRemoved(ctx);  //有科幻端断开练级
        Channel incoming=ctx.channel();
        for (Channel ch:channels){
            if (ch!=incoming){
                ch.writeAndFlush("再见:"+incoming.remoteAddress()+"离开聊天室");
            }
        }

        channels.remove(incoming);
    }

    //当客户端发送消息自动执行
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
       // 读取信息,并转发
        Channel incoming=ctx.channel();
        //对聊天室的人都遍历一下
        for (Channel ch:channels){
            if (ch!=incoming){
                //被动接收消息的人
                ch.writeAndFlush(new TextWebSocketFrame("用户"+incoming.remoteAddress()+"说:"+msg.text()+"\n"));
            }else {
                //发消息的人
                ch.writeAndFlush(new TextWebSocketFrame("我说:"+msg.text()+"\n"));
            }
        }

    }
//,hahandleradd会自动执行,将脸上的记录下来

    //督导数据,都有记账本记录一下
    //
}

WebChatServer

package org.lmj.chatroom;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;


public class WebChatServer {
    private int port;

    public WebChatServer(int port){
        this.port=port;
    }

    public void start(){
        //定义两个线程
        EventLoopGroup boss=new NioEventLoopGroup();   //netty是nio模型,处理队列里的线程去处理
        EventLoopGroup worker=new NioEventLoopGroup();  //干活的
        try {
        ServerBootstrap bootstrap=new ServerBootstrap();  //启动服务
        bootstrap.group(boss,worker)
                //传入隧道
                .channel(NioServerSocketChannel.class)
                //自定义类,来服务则服务端的处理逻辑
                .childHandler(new WebChatServerInitialize())
                //使用官方的默认配置,一下是参数设置
                .option(ChannelOption.SO_BACKLOG,128)
                //保持连接
                .childOption(ChannelOption.SO_KEEPALIVE,true);

            ChannelFuture future=bootstrap.bind(port).sync();//让他绑定端口号,然后服务启动
            //服务端如何处理这个过程呢
            //1。 客户端发信息,服务端进行转发(客户端只需要直到服务端)
            System.out.println("服务端启动");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //先写中间的代码
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }


    }
    public static void main(String args[]){
        new WebChatServer(8080).start();
    }
}

WebChatServerInitialize

package org.lmj.chatroom;

import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;


//不使用implements,使用extend简化开发
//netty应用了多种设计模式,来简化的我们代码的开发
public class WebChatServerInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        //是否是websocket请求
        //服务器切换频道将http升级成websocket
        //将0101转http
        //指明工序,谁来做
        //获得管道--要流经多少工序
        //将请求和应答边界吗为http消息
        ChannelPipeline pipeline= socketChannel.pipeline();

        /**
         * 流经管道需要做四道工序
         */
        pipeline.addLast(new HttpServerCodec())  //请求响应编码节码为http
                .addLast(new HttpObjectAggregator(64*1024)) //信息汇集公司,聚合器 //整成字节后再次封装 //缓冲区//1024这样写会白屏,缓冲区太小
                .addLast(new ChunkedWriteHandler())  //http就发送聊天界面
                .addLast(new HttpRequestHandler("/chat"))   //chat标识http,否则是http,,,这就是(ChunkedWriteHandler的)现已到工序

                //入队,出队。供料,史料就要做座机的协议,发图片也要发自己的协议,加标志位决定是公聊还是史料
                .addLast(new WebSocketServerProtocolHandler("/chat"))  //是chat就认为是websocket,就用这个类来处理好
                .addLast(new TextWebSocketFrameHandler());
        //初始化通道


    }
//public class WebChatServerInitialize extends ChannelInitializer { 不能这样写,且import java.nio.channels.SocketChannel;
//initChannel(Channel channel)里面要用initChannel(SocketChannel socketChannel) 导报为import io.netty.channel.socket.SocketChannel;

}

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
<form action="" onsubmit="return false">
    <h1>多人聊天</h1>
    <textarea id="msgTxt" cols="50" rows="20">

		       </textarea><br>
    <input type="text" name="msg" style="width: 300px">
    <input type="button" value="发送" onclick="send(this.form.msg.value)">
    <input type="button" value="清空">
</form>

<script type="text/javascript">

		  var socket   //定义对象
			  if(!window.WebSocket){

					   //有的浏览器里可能没有Websocket对象,window就代表浏览器,就做一下兼容性处理
					   window.WebSocket=window.MozWebSocket;
				   }
		       if(window.WebSocket){

		          socket=new WebSocket("ws://localhost:8080/chat");  //websocket用的是ws协议。将localhost改成自己的ip,就可以和同一局域网的人进行群聊了

		          socket.onmessage=function(event){  //收到客户端消息
					  var ta=document.getElementById("msgTxt");
					  ta.value=ta.value+"\n"+event.data;
					}
				   socket.onopen=function(event){
					 var ta=document.getElementById("msgTxt");
					 ta.value=ta.value+"\n"+"连上服务器,成功加入了聊天室";
				   }

				   socket.onclose=function(event){
					 var ta=document.getElementById("msgTxt");
					 ta.value=ta.value+"\n"+"退出聊天室";
				   }
		       //socket不断接受服务端传来的数据,然后通过三个事件进行处理
			}

			//客户端想发送数据
			function send(msg){
			  if(!window.WebSocket){
			     return false;
			  }
			  if(socket.readyState==WebSocket.OPEN){
			     socket.send(msg);
			  }else{
			    alert("连接没有建立");
			  }
			}
		</script>
</body>
</html>

在这里插入图片描述
pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.lmj</groupId>
  <artifactId>chat</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>chat</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.17.Final</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

效果:
在这里插入图片描述

发布了437 篇原创文章 · 获赞 82 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_41063141/article/details/103708490
今日推荐