Netty 之 Http与Websocket通信

1.引入pom依赖

  <dependencies>

    ......

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.44.Final</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
  </dependencies>

2.请求分发

  a.创建启动类,添加 DispatchHandler、HttpHandler、WebSocketHandler 三个自定义处理器到管道中

public class StartServer {

    //端口号
    private int port;

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

    //启动方法
    public void start() throws Exception {
        //负责接收客户端的连接的线程。线程数设置为1即可,netty处理链接事件默认为单线程,过度设置反而浪费cpu资源
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //负责处理数据传输的工作线程。线程数默认为CPU核心数乘以2
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            //在ServerChannelInitializer中初始化ChannelPipeline责任链,并添加到serverBootstrap中
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel channel) {
                    //添加HTTP编解码
                    channel.pipeline().addLast("decoder", new HttpRequestDecoder());
                    channel.pipeline().addLast("encoder", new HttpResponseEncoder());
                    //消息聚合器,将消息聚合成FullHttpRequest
                    channel.pipeline().addLast("aggregator", new HttpObjectAggregator(1024*1024*5));
                    //支持大文件传输
                    channel.pipeline().addLast("chunked", new ChunkedWriteHandler());
                    //自定义Handler
                    channel.pipeline().addLast("dispatchHandler", new DispatchHandler());
                    channel.pipeline().addLast("httpHandler", new HttpHandler());
                    channel.pipeline().addLast("webSocketHandler", new WebSocketHandler());

                }
            });
            //标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //Netty4使用对象池,重用缓冲区
            bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //是否启用心跳保活机制
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
            //禁止使用Nagle算法,便于小数据即时传输
            bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

            //绑定端口后,开启监听
            ChannelFuture future = bootstrap.bind(port).sync();
            //等待服务监听端口关闭
            future.channel().closeFuture().sync();
        } finally {
            //释放资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    //测试代码
    public static void main(String[] args) {
        try {
            int port = 8080;
            new StartServer(port).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

  b.创建请求分发处理器,负责分发Http请求和WebSocket请求

public class DispatchHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            //判断是否为websocket握手请求
            if(isWebSocketHandShake(request)){
                ctx.fireChannelRead(new WebSocketRequestVo(request));
                //Http请求
            }else{
                ctx.fireChannelRead(new HttpRequestVo(request));
            }
            //websocket请求
        } else if (msg instanceof WebSocketFrame) {
            WebSocketFrame frame = (WebSocketFrame) msg;
            ctx.fireChannelRead(new WebSocketRequestVo(frame));
        }
    }

    //判断是否为websocket握手请求
    private boolean isWebSocketHandShake(FullHttpRequest request){
        //1、判断是否为get 2、判断Upgrade头是否包含websocket 3、Connection头是否包含upgrade
        return request.method().equals(HttpMethod.GET)
                && request.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true)
                && request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

3.Websocket部分

  a.创建Websocket请求Vo类

public class WebSocketRequestVo {

    //握手请求
    private FullHttpRequest request;
    //websocket请求
    private WebSocketFrame frame;

    public WebSocketRequestVo(FullHttpRequest request) {
        this.request = request;
    }

    public WebSocketRequestVo(WebSocketFrame frame) {
        this.frame = frame;
    }

    public FullHttpRequest getRequest() {
        return request;
    }

    public void setRequest(FullHttpRequest request) {
        this.request = request;
    }

    public WebSocketFrame getFrame() {
        return frame;
    }

    public void setFrame(WebSocketFrame frame) {
        this.frame = frame;
    }
}

  b.创建Websocket请求处理器

public class WebSocketHandler extends SimpleChannelInboundHandler<WebSocketRequestVo> {

    //属性名称:握手处理器
    private static final AttributeKey<WebSocketServerHandshaker> HAND_SHAKE_ATTR = AttributeKey.valueOf("HAND_SHAKE");
    //属性名称:websocket自定义id
    private static final AttributeKey<String> SOCKET_ID_ATTR = AttributeKey.valueOf("SOCKET_ID");

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //删除信道
        String id = ctx.channel().attr(SOCKET_ID_ATTR).get();
        if(id == null){
            return;
        }
        WsClientManager.getInstance().removeChannel(id);
        //TODO
        System.out.println("[" + id + "]断开连接。。。");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketRequestVo requestVo) throws Exception {
        //处理握手
        if(requestVo.getRequest() != null){
            this.handleShake(ctx, requestVo.getRequest());
        }
        //处理websocket数据
        if(requestVo.getFrame() != null){
            this.handleFrame(ctx, requestVo.getFrame());
        }
    }

    //处理握手
    private void handleShake(ChannelHandlerContext ctx, FullHttpRequest request){
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(null, null, false);
        WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), request);
            //保存socket的自定义ID与信道的对应关系
            Map<String, String> params = ParamUtil.getRequestParams(request);
            String id = params.get(MyConfig.SOCKET_ID);
            WsClientManager.getInstance().putChannel(id, ctx.channel());
            //绑定属性到channel
            ctx.channel().attr(HAND_SHAKE_ATTR).set(handshaker);
            ctx.channel().attr(SOCKET_ID_ATTR).set(id);
            //TODO
            System.out.println("[" + id + "]正在握手。。。");
        }
    }

    //处理websocket数据
    private void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            WebSocketServerHandshaker handshaker = ctx.channel().attr(HAND_SHAKE_ATTR).get();
            if(handshaker == null){
                ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
                return;
            }
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 暂仅支持文本消息,不支持二进制消息
        if (! (frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException("暂不支持该消息类型:" + frame.getClass().getName());
        }
        // 处理消息
        String msg = ((TextWebSocketFrame) frame).text();
        WsClientManager.getInstance().handleMsg(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

  c.创建消息Vo类

public class MsgVo implements Serializable {

    private int type;
    private String fromId;
    private String toId;
    private String body;

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getFromId() {
        return fromId;
    }

    public void setFromId(String fromId) {
        this.fromId = fromId;
    }

    public String getToId() {
        return toId;
    }

    public void setToId(String toId) {
        this.toId = toId;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

  d.创建Websocket客户端管理类

public class WsClientManager {

    //单例
    private static WsClientManager instance = new WsClientManager();
    private WsClientManager(){}
    public static WsClientManager getInstance(){
        return instance;
    }

    //socket自定义ID与信道的对应关系
    private Map<String, Channel> channelMap = new ConcurrentHashMap<>();

    //添加信道
    public void putChannel(String id, Channel channel){
        this.channelMap.put(id, channel);
    }

    //获取信道
    public void getChannel(String id){
        this.channelMap.get(id);
    }

    //删除信道
    public void removeChannel(String id){
        this.channelMap.remove(id);
    }

    //发送消息
    public void sendMsg(String id, String msg){
        TextWebSocketFrame frame = new TextWebSocketFrame(msg);
        Channel channel = channelMap.get(id);
        if(channel != null){
            channel.writeAndFlush(frame);
        }
    }

    //群发消息
    public void sendMsg2All(String msg){
        for(Channel channel : channelMap.values()){
            TextWebSocketFrame frame = new TextWebSocketFrame(msg);
            channel.writeAndFlush(frame);
        }
    }

    //处理消息
    public void handleMsg(String msgJson){
        MsgVo msgVo = JSON.parseObject(msgJson, MsgVo.class);
        if(msgVo.getType() == MsgTypeEnum.ONE2ONE.getCode()){
            this.sendMsg(msgVo.getToId(), msgJson);
            this.sendMsg(msgVo.getFromId(), msgJson);
        }else if(msgVo.getType() == MsgTypeEnum.ONE2ALL.getCode()){
            this.sendMsg2All(msgJson);
        }
    }

}

  e.创建消息类型枚举

public enum MsgTypeEnum {

    ONE2ONE(1,"私聊"),
    ONE2ALL(2,"公屏");

    private int code;
    private String name;

    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    private MsgTypeEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public static MsgTypeEnum getMsgTypeEnum(int code) {
        for(MsgTypeEnum item : MsgTypeEnum.values()) {
            if (item.getCode() == code) {
                return item;
            }
        }
        return null;
    }

}

4.Http部分

  a.创建Http请求Vo

public class HttpRequestVo {

    private FullHttpRequest request;

    public HttpRequestVo(FullHttpRequest request) {
        this.request = request;
    }

    public FullHttpRequest getRequest() {
        return request;
    }

    public void setRequest(FullHttpRequest request) {
        this.request = request;
    }
}

  b.创建Http请求处理器

public class HttpHandler extends SimpleChannelInboundHandler<HttpRequestVo> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpRequestVo requestVo) throws Exception {
        FullHttpRequest request = requestVo.getRequest();
        String uri = ParamUtil.getUri(request);
        HandleMappingVo mappingVo = ControllerContext.getInstance().getHandleMapping(uri);
        //没有对应映射关系,则返回400
        if(mappingVo == null){
            ResponseUtil.sendHttpResponse(ctx, request, ResponseUtil.get400Response());
            return;
        }

        Map<String, String> params = ParamUtil.getRequestParams(request);
        //执行Controller中的方法
        try {
            String content = (String) mappingVo.getMethod().invoke(mappingVo.getInstance(), request, params);
            ResponseUtil.sendHttpResponse(ctx, request, ResponseUtil.get200Response(content));
        }catch (Exception e){
            e.printStackTrace();
            ResponseUtil.sendHttpResponse(ctx, request, ResponseUtil.get500Response());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

  c.创建请求路径映射Vo

public class HandleMappingVo {

    //类实例
    private Object instance;
    //方法
    private Method method;
    //请求路径
    private String requestPath;

    public HandleMappingVo(Object instance, Method method, String requestPath) {
        this.instance = instance;
        this.method = method;
        this.requestPath = requestPath;
    }

    public Object getInstance() {
        return instance;
    }

    public void setInstance(Object instance) {
        this.instance = instance;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public String getRequestPath() {
        return requestPath;
    }

    public void setRequestPath(String requestPath) {
        this.requestPath = requestPath;
    }
}

  d.创建自定义 Controller 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyController {

    String value() default "";

}

  e.创建自定义 RequestMapping 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyRequestMapping {

    String value();

}

  f.创建全局上下文类

public class ControllerContext {

    //保存类路径的缓存
    private List<String> classCache = Collections.synchronizedList(new ArrayList<String>());
    //保存类实例的容器
    private Map<String, Object> beanFactory = new ConcurrentHashMap<>();
    //Controller方法与请求路径的映射关系
    private Map<String, HandleMappingVo> handleMapping = new ConcurrentHashMap<>();

    //单例
    private static ControllerContext instance = new ControllerContext();
    public static ControllerContext getInstance(){
        return instance;
    }

    //初始化
    private ControllerContext(){
        String path = MyConfig.CONTROLLER_PATH;
        //扫描包
        scanPackage(path);
        //注册Bean
        registerBean();
        //MVC路径映射
        mappingMVC();
    }

    /**
     * 扫描包
     */
    private void scanPackage(final String path) {
        URL url = this.getClass().getClassLoader().getResource(path.replaceAll("\\.", "/"));
        try {
            File file = new File(url.toURI());
            file.listFiles(new FileFilter(){
                //遍历当前目录下的所有文件
                @Override
                public boolean accept(File childFile) {
                    //递归查找文件
                    if(childFile.isDirectory()){
                        scanPackage(path + "." + childFile.getName());
                    }else{
                        if(childFile.getName().endsWith(".class")){
                            String classPath = path + "." + childFile.getName().replace(".class","");
                            classCache.add(classPath);
                        }
                    }
                    return true;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 注册Bean
     */
    private void registerBean(){
        if(classCache.isEmpty()){
            return;
        }
        for(String path:classCache){
            try {
                //使用反射,通过类路径获取class 对象
                Class<?> clazz = Class.forName(path);
                //找出需要被容器管理的类,@MyController
                if(clazz.isAnnotationPresent(MyController.class)){
                    //根据类对象,创建实例
                    Object instance = clazz.newInstance();
                    //首字母小写的类名作为默认名字
                    String aliasName = lowerClass(clazz.getSimpleName());
                    if(clazz.isAnnotationPresent(MyController.class)){
                        MyController controller = clazz.getAnnotation(MyController.class);
                        if(! "".equals(controller.value())){
                            aliasName = controller.value();
                        }
                    }
                    if(beanFactory.get(aliasName) == null){
                        beanFactory.put(aliasName, instance);
                    }
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /*
     * MVC路径映射
     */
    private void mappingMVC() {
        //遍历容器,保存Requestmapping、Contoller与请求路径的对应关系
        for(Map.Entry<String,Object> entry : beanFactory.entrySet()){
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();
            if(clazz.isAnnotationPresent(MyController.class)){
                //获取类上的路径
                String classPath = "";
                if(clazz.isAnnotationPresent(MyRequestMapping.class)){
                    MyRequestMapping clazzAnnotation = clazz.getAnnotation(MyRequestMapping.class);
                    classPath = clazzAnnotation.value();
                }
                //获取方法上的路径
                Method[] methods = clazz.getMethods();
                for(Method method : methods){
                    if(method.isAnnotationPresent(MyRequestMapping.class)){
                        MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class);
                        if(methodAnnotation != null){
                            String methodPath = methodAnnotation.value();
                            String requestPath = classPath + methodPath;
                            HandleMappingVo mappingVo= new HandleMappingVo(instance, method, requestPath);
                            handleMapping.put(requestPath, mappingVo);
                        }
                    }
                }
            }
        }
    }

    /**
     * 首字母小写
     */
    private String lowerClass(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        String result = String.valueOf(chars);
        return result;
    }

    /**
     * 获取Controller方法与请求路径的映射关系
     */
    public HandleMappingVo getHandleMapping(String requestPath){
        return handleMapping.get(requestPath);
    }
}

  g.创建测试Controller

@MyController("testController")
public class TestController {

    @MyRequestMapping("/add")
    public String add(FullHttpRequest request, Map<String, String> params){
        int param1 = Integer.valueOf(params.get("param1"));
        int param2 = Integer.valueOf(params.get("param2"));
        return String.valueOf(param1 + param2);
    }

}

5.其他

  a.配置常量类

public class MyConfig {

    //websocket的自定义id
    public static final String SOCKET_ID = "userId";
    //controller的路径
    public static final String CONTROLLER_PATH = "com.wode.http.controller";

}

  b.请求参数工具类

public class ParamUtil {

    /**
     * 获取请求参数
     */
    public static Map<String, String> getRequestParams(HttpRequest request){
        Map<String, String>requestParams=new HashMap<>();
        // 处理get请求
        if (request.method() == GET) {
            QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
            Map<String, List<String>> params = decoder.parameters();
            for(Map.Entry<String, List<String>> entry : params.entrySet()){
                requestParams.put(entry.getKey(), entry.getValue().get(0));
            }
        }
        // 处理POST请求
        if (request.method() == POST) {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request);
            List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
            for(InterfaceHttpData data : postData){
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MemoryAttribute attribute = (MemoryAttribute) data;
                    requestParams.put(attribute.getName(), attribute.getValue());
                }
            }
        }
        return requestParams;
    }

    /**
     * 获取请求Uri
     */
    public static String getUri(HttpRequest request){
        return new QueryStringDecoder(request.uri()).path();
    }

}

  c.响应工具类

public class ResponseUtil {

    /**
     * 获取400响应
     */
    public static FullHttpResponse get400Response(){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
    }

    /**
     * 获取200响应
     */
    public static FullHttpResponse get200Response(String content){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes()));
    }

    /**
     * 获取500响应
     */
    public static FullHttpResponse get500Response(){
        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR, Unpooled.wrappedBuffer("服务器异常".getBytes()));
    }

    /**
     * 发送HTTP响应
     */
    public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
        // 返回应答给客户端
        if (response.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
        }

        //添加header描述length,避免客户端接收不到数据
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());

        // 如果是非Keep-Alive,关闭连接
        if (! HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
            ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }else{
            response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            ctx.channel().writeAndFlush(response);
        }
    }

}

6.测试

  a.Websocket测试:前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <style type="text/css">
        h1{
            text-align:center;
        }
        .row{
            margin:10px 0px;
        }
        .col{
            display: inline-block;
            margin:0px 5px;
        }
        .msg-container{
            width: 40%;
            height: 500px;
        }
        .type-select-wrapper{
            margin: 0 0 0 10%;
        }
        .msg-div{
            width: 80%;
            margin:0 0 0 10%;
            height: 300px;
            border:solid 1px;
            overflow: scroll;
        }
    </style>
</head>
<body>
<div>
    <div class="row">
        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口1</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-1">发送</button>
                </div>
            </div>
        </div>

        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口2</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-2">发送</button>
                </div>
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口3</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-3">发送</button>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
    let userIdArray = ["张三", "李四", "王五"];
    let wsArray = new Array(3);

    $(function(){
        initWebSocketFunc(userIdArray[0], 0);
        initWebSocketFunc(userIdArray[1], 1);
        initWebSocketFunc(userIdArray[2], 2);
        $("#send-btn-1").on("click", {num: 0, fromId: userIdArray[0], toId: userIdArray[1]}, sendMsgFunc);
        $("#send-btn-2").on("click",{num: 1, fromId: userIdArray[1], toId: userIdArray[2]}, sendMsgFunc);
        $("#send-btn-3").on("click",{num: 2, fromId: userIdArray[2], toId: userIdArray[0]}, sendMsgFunc);
    });


    let initWebSocketFunc = function(userId, num){
        // 初始化一个 WebSocket 对象
        let ws = new WebSocket("ws://localhost:8080/ws?userId=" + userId);

        // 建立 web socket 连接成功触发事件
        ws.onopen = function () {
            console.log("正在建立连接...");
        };

        // 接收服务端数据时触发事件
        ws.onmessage = function (evt) {
            let msg = JSON.parse(evt.data);
            let out;
            if(msg.type == 1){
                out = "[私聊] " + (msg.fromId==userId?"":msg.fromId) + "" + (msg.toId==userId?"":msg.toId) + " 说:" + msg.body;
            }else if(msg.type == 2){
                out = "[公屏] " + (msg.fromId==userId?"":msg.fromId) + " 说:" + msg.body;
            }
            $(".msg-div:eq(" + num + ")").append(out + "<br/>");
            // $($(".msg-div")[num]).append(out + "<br/>");
        };

        // 断开 web socket 连接成功触发事件
        ws.onclose = function () {
            console.log("连接已关闭...");
        };

        wsArray[num] = ws;
    };


    let sendMsgFunc = function(e){
        let num = e.data.num;
        let fromId = e.data.fromId;
        let toId = e.data.toId;

        let type = $(".type-select:eq(" + num + ")").find("option:selected").attr("data-value");
        let msg = $(".msg-input:eq(" + num + ")").val();
        // let type = $($(".type-select")[num]).find("option:selected").attr("data-value");
        // let msg = $($(".msg-input")[num]).val();
        let msgData = {
            type: type,
            fromId: fromId,
            body: msg
        };
        if(type == 1){
            msgData.toId = toId;
        }
        let msgStr = JSON.stringify(msgData);
        wsArray[num].send(msgStr);
    }
</script>
</html>

  b.Http测试:访问 http://localhost:8080/add?param1=2&param2=3,查看返回数据

7.参考文章

  https://www.jianshu.com/p/56216d1052d7

  https://www.bbsmax.com/A/xl56PV69zr/

猜你喜欢

转载自www.cnblogs.com/vettel0329/p/12221268.html