zoukankan      html  css  js  c++  java
  • Socket、Http、WebSocket?强大的Netty几行语句就帮你实现!

    一、概述

    Netty是目前最流行的由JBOSS提供的一个Java开源框架NIO框架,Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

    相比JDK原生NIO,Netty提供了相对十分简单易用的API,非常适合网络编程。Netty是完全基于NIO实现的,所以Netty是异步的。

    Mina同样也是一款优秀的NIO框架,而且跟Netty是出自同一个人之手,但是Netty要晚一点,优点更多一些,想了解更多可以直接搜索mina和netty比较。

    使用Netty,我们可以作为Socket服务器,也可以用来做Http服务器,同时也可以支持WebSocket,这里,我们将这三种方式都详细介绍一下。

    如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以 加入我们的java学习圈,点击即可加入 ,共同学习,节约学习时间,减少很多在学习中遇到的难题。

    二、依赖Jar包

    只需要引入Netty的jar包,为了方便json转换,我这里引入fastjson。

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.17.Final</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.40</version>
    </dependency>

    三、通用的监听配置

    不论是Socket、http还是websocket,都是基于tcp的,因此,netty需要配置EventLoopGroup做监听。

    package cn.pomit.springwork.nettynew.server;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandler;
    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 javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    public abstract class NettyServiceTemplate {
        static private EventLoopGroup bossGroup = new NioEventLoopGroup();
        static private EventLoopGroup workerGroup = new NioEventLoopGroup();
    
        abstract protected ChannelHandler[] createHandlers();
    
        abstract public int getPort();
    
        abstract public String getName();
    
        @PostConstruct
        public void start() throws Exception {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelHandler[] handlers = createHandlers();
                            for (ChannelHandler handler : handlers) {
                                ch.pipeline().addLast(handler);
                            }
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128).option(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true).childOption(ChannelOption.SO_REUSEADDR, true);
    
            ChannelFuture cf = b.bind(getPort()).await();
            // cf.channel().closeFuture().await();
            if (!cf.isSuccess()) {
                System.out.println("无法绑定端口:" + getPort());
                throw new Exception("无法绑定端口:" + getPort());
            }
    
            System.out.println("服务[{" + getName() + "}]启动完毕,监听端口[{" + getPort() + "}]");
        }
    
        @PreDestroy
        public void stop() {
            bossGroup.shutdownGracefully().syncUninterruptibly();
            workerGroup.shutdownGracefully().syncUninterruptibly();
            System.out.println("服务[{" + getName() + "}]关闭。");
        }
    }
    

    这里的配置,都是netty的常用配置:

    • option和childOption是配置连接属性的。
    • ChannelHandler是必须的,这里通过抽象方法,由子类负责配置。

    启动的时候,new一个子类,调用start方法即可。

    四、Netty的Socket监听

    有了上面的NettyServiceTemplate,我们可以用几行代码构建一个tcp服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。

    下面的ChannelHandler数组包含:

    1. 换行分割解码器
    2. 字符串解码器
    3. 字符串编码器
    4. 自定义处理器,写自己逻辑用。

    StringTcpServer :

    package cn.pomit.springwork.nettynew.server.tcp;
    
    import cn.pomit.springwork.nettynew.handler.tcp.StringTcpServerHandler;
    import cn.pomit.springwork.nettynew.server.NettyServiceTemplate;
    import io.netty.channel.ChannelHandler;
    import io.netty.handler.codec.DelimiterBasedFrameDecoder;
    import io.netty.handler.codec.Delimiters;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class StringTcpServer extends NettyServiceTemplate {
        private int port = 8088;
        private String name = "String Server";
    
        public StringTcpServer(int port) {
            this.port = port;
        }
    
        @Override
        protected ChannelHandler[] createHandlers() {
            return new ChannelHandler[] { 
                    new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()),
                    new StringDecoder(), 
                    new StringEncoder(),
                    new StringTcpServerHandler() };
        }
    
        @Override
        public int getPort() {
            return port;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        public void setPort(int port) {
            this.port = port;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    

    这样就是一个tcp服务器了,StringTcpServerHandler特别简单,打印并返回:

    package cn.pomit.springwork.nettynew.handler.tcp;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    public class StringTcpServerHandler extends SimpleChannelInboundHandler<String> {
        String charset = "UTF-8";
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println("内容:" + msg);
            ctx.writeAndFlush("返回内容:" + msg);
        }
    }

    五、Netty的Http监听

    Netty的http,稍微复杂点,复杂只是相对于返回值的控制,cookie的控制,session的控制,因为netty只负责帮你解析了http的所有信息,并没有像sevlet容器那样还给你控制session了啥的。

    我这里只用netty做json数据转换,用来提供rest服务,如果想了解更多,可以看下我用netty实现的一个类似Spring容器的工具:https://gitee.com/ffch/teaboot, 这个工具解析html、rest等,提供了简单的session控制、security控制。代码属于早期内容,尚待维护。

    5.1 Http服务器

    这个服务器是专门面向rest服务的。有了上面的NettyServiceTemplate,我们可以用几行代码构建一个http服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。

    ChannelHandler数组中:

    1. HttpResponseEncoder是netty自己的响应编码器,报文级别
    2. HttpRequestDecoder是netty自己的请求解码器,报文级别
    3. HttpObjectAggregator是完全的解析Http POST请求用的。
    4. HttpMsgResponseEncoder是自定义的响应编码器,为的是对响应做简单的控制。应用级别
    5. HttpMsgRequestDecoder是自定义的请求解码器,是对http请求转换为json。应用级别
    6. JsonHttpServerHandler是自定义逻辑处理器,写自己业务用。

    JsonHttpServer :

    package cn.pomit.springwork.nettynew.server.http;
    
    import cn.pomit.springwork.nettynew.coder.http.HttpMsgRequestDecoder;
    import cn.pomit.springwork.nettynew.coder.http.HttpMsgResponseEncoder;
    import cn.pomit.springwork.nettynew.handler.http.JsonHttpServerHandler;
    import cn.pomit.springwork.nettynew.server.NettyServiceTemplate;
    import io.netty.channel.ChannelHandler;
    import io.netty.handler.codec.http.HttpObjectAggregator;
    import io.netty.handler.codec.http.HttpRequestDecoder;
    import io.netty.handler.codec.http.HttpResponseEncoder;
    
    public class JsonHttpServer extends NettyServiceTemplate {
        int port = 8888;
        String name = "Json Server";
        private String charset = "UTF-8";
        private int timeout = 60;
    
        public JsonHttpServer(int port) {
            this.port = port;
        }
    
        @Override
        protected ChannelHandler[] createHandlers() {
            return new ChannelHandler[] { 
                    new HttpResponseEncoder(), 
                    new HttpRequestDecoder(),
                    new HttpObjectAggregator(1048576), 
                    new HttpMsgResponseEncoder(charset, timeout),
                    new HttpMsgRequestDecoder(charset), 
                    new JsonHttpServerHandler() };
        }
    
        @Override
        public int getPort() {
            return port;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
    }
    
    

    5.2 自定义的请求解码器

    请求解码器就是转换下数据,变成字符串。

    HttpMsgRequestDecoder:

    package cn.pomit.springwork.nettynew.coder.http;
    
    import java.nio.charset.Charset;
    import java.util.List;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToMessageDecoder;
    import io.netty.handler.codec.http.FullHttpRequest;
    import io.netty.handler.codec.http.HttpObject;
    
    public class HttpMsgRequestDecoder extends MessageToMessageDecoder<HttpObject>{
        private String charset;
    
        public HttpMsgRequestDecoder(String charset) {
            super();
            this.charset = charset;
        }
    
        @Override
        protected void decode(ChannelHandlerContext ctx, HttpObject in,
                List<Object> out) throws Exception {
            FullHttpRequest request = (FullHttpRequest) in;
    
            ByteBuf buf = request.content();
            String jsonStr = buf.toString(Charset.forName(charset));
            out.add(jsonStr);
        }
    }

    5.3 自定义的响应编码器

    响应编码器中,定义了连接的配置信息,http头信息、内容类型等。

    HttpMsgResponseEncoder:

    package cn.pomit.springwork.nettynew.coder.http;
    
    import java.util.List;
    
    import cn.pomit.springwork.nettynew.model.http.HttpResponseMsg;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToMessageEncoder;
    import io.netty.handler.codec.http.DefaultFullHttpResponse;
    import io.netty.handler.codec.http.HttpHeaderNames;
    import io.netty.handler.codec.http.HttpHeaderValues;
    import io.netty.handler.codec.http.HttpResponseStatus;
    import io.netty.handler.codec.http.HttpVersion;
    
    public class HttpMsgResponseEncoder extends MessageToMessageEncoder<HttpResponseMsg> {
        private String charset;
        private int timeout;
    
        public HttpMsgResponseEncoder(String charset, int timeout) {
            super();
            this.charset = charset;
            this.timeout = timeout;
        }
    
        @Override
        protected void encode(ChannelHandlerContext ctx, HttpResponseMsg message, List<Object> out) {
            try {
                DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(message.getResCode()),
                        Unpooled.wrappedBuffer(message.getMessage().getBytes(charset)));
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, message.getResType()+";charset=" + charset);
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
    
                // 强制keep-alive
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                response.headers().set("Keep-Alive", "timeout=" + timeout);
    
                out.add(response);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    

    5.4 业务处理器

    这里的业务处理器很简单,就打印返回。

    JsonHttpServerHandler:

    package cn.pomit.springwork.nettynew.handler.http;
    
    import cn.pomit.springwork.nettynew.model.http.HttpResponseMsg;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    public class JsonHttpServerHandler extends SimpleChannelInboundHandler<String> {
        String charset = "UTF-8";
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println("post内容:" + msg);
            HttpResponseMsg hrm = new HttpResponseMsg();
            hrm.setResType(HttpResponseMsg.ResType.JSON.getValue());
            hrm.setResCode(HttpResponseMsg.ResCode.OK.getValue());
            hrm.setMessage(msg);
            ctx.writeAndFlush(hrm);
        }
    
    }
    

    六、Netty的WebSocket监听

    Netty的WebSocket,相对于http还稍微简单点。

    Netty对WebSocket做了完全控制,你需要做的只是对WebSocket的用户进行控制,能根据用户找到相应的通道即可。

    6.1 WebSocket服务器

    有了上面的NettyServiceTemplate,我们可以用几行代码构建一个WebSocket服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。

    ChannelHandler数组中:

    1. HttpServerCodec是netty自己的http解码器,报文级别
    2. ChunkedWriteHandler是用于大数据的分区传输。
    3. HttpObjectAggregator是完全的解析Http消息体请求用的。
    4. WebSocketServerProtocolHandler是配置websocket的监听地址。
    5. WebSocketServerHandler是自定义逻辑处理器,写自己业务用。

    WebSocketServer :

    package cn.pomit.springwork.nettynew.server.websocket;
    
    import cn.pomit.springwork.nettynew.handler.websocket.WebSocketServerHandler;
    import cn.pomit.springwork.nettynew.server.NettyServiceTemplate;
    import io.netty.channel.ChannelHandler;
    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;
    
    public class WebSocketServer extends NettyServiceTemplate {
        int port = 9999;
        String name = "WebSocket";
    
        public WebSocketServer(int port) {
            this.port = port;
        }
    
        @Override
        protected ChannelHandler[] createHandlers() {
            return new ChannelHandler[] { 
                    new HttpServerCodec(), 
                    new ChunkedWriteHandler(),
                    new HttpObjectAggregator(1048576), 
                    new WebSocketServerProtocolHandler("/ws"),
                    new WebSocketServerHandler() };
        }
    
        @Override
        public int getPort() {
            return port;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
    }
    
    

    6.2 WebSocket的聊天室逻辑

    下面是用websocket做聊天室的逻辑:

    • 使用MessageDTO 做消息的传递实体;
    • WebSocketUser存储了每个连接上来的WebSocket用户,保存对应段分Channel。
    • 前端要求填入用户后,模拟登录,并返回用户列表。前端根据用户列表选择人发送信息。
    • 根据MessageDTO中的发送人和接收人,找到对应的Channel并发送消息。

    WebSocketServerHandler:

    package cn.pomit.springwork.nettynew.handler.websocket;
    
    import java.util.List;
    
    import com.alibaba.fastjson.JSONObject;
    
    import cn.pomit.springwork.nettynew.model.websocket.MessageDTO;
    import cn.pomit.springwork.nettynew.model.websocket.WebSocketUser;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
    
    public class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
            MessageDTO messageDTO = JSONObject.parseObject(msg.text(), MessageDTO.class);
            if (messageDTO.getMessageType().equals(MessageDTO.Type.TYPE_NEW.getMessageType())) {
                WebSocketUser.add(messageDTO.getFromUserName(), ctx.channel());
                messageDTO.setTargetUserName(messageDTO.getFromUserName());
                messageDTO.setFromUserName("SYSTEM");
                messageDTO.setMessage(JSONObject.toJSONString(WebSocketUser.getUserList()));
                ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(messageDTO)));
            } else {
                List<Channel> webUsers = WebSocketUser.getSessionByUserName(messageDTO.getTargetUserName());
                if (webUsers == null || webUsers.size() == 0) {
                    System.out.print("发送给" + messageDTO.getTargetUserName() + ",当前无session");
                    MessageDTO messageDTOError = new MessageDTO();
                    messageDTOError.setFromUserName("SYSTEM");
                    messageDTOError.setTargetUserName(messageDTO.getFromUserName());
                    messageDTOError.setMessageType(MessageDTO.Type.TYPE_ERROR.getMessageType());
                    messageDTOError.setMessage("发送失败!");
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(messageDTOError)));
                    return;
                }
                System.out.print("发送给" + messageDTO.getTargetUserName() + ",当前session个数为:" + webUsers.size());
    
                for (int i = 0; i < webUsers.size(); i++) {
                    Channel session = webUsers.get(i);
                    if (!session.isOpen()) {
                        WebSocketUser.removeWebSocketSession(messageDTO.getTargetUserName(), session);
                    }
    
                    session.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(messageDTO)));
                }
            }
    
        }
    
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            System.out.println("用户:" + ctx.channel().id().asLongText() + "上线");
        }
    
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            System.out.println("用户下线: " + ctx.channel().id().asLongText());
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.channel().close();
        }
    }
    

    6.3 用户信息保存

    用一个并发map保存所有用户和对应的Channel。

    WebSocketUser:

    package cn.pomit.springwork.nettynew.model.websocket;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import io.netty.channel.Channel;
    
    public class WebSocketUser {
        private static Map<String, List<Channel>> userNameWebsession = new ConcurrentHashMap<>();
    
        public static void add(String userName, Channel webSocketSession) {
            userNameWebsession.computeIfAbsent(userName, v -> new ArrayList<Channel>()).add(webSocketSession);
        }
    
        /**
         * 根据昵称拿WebSocketSession
         * 
         * @param nickName
         * @return
         */
        public static List<Channel> getSessionByUserName(String userName) {
            return userNameWebsession.get(userName);
        }
    
        /**
         * 移除失效的WebSocketSession
         * 
         * @param webSocketSession
         */
        public static void removeWebSocketSession(String userName, Channel webSocketSession) {
            if (webSocketSession == null)
                return;
            List<Channel> webSessoin = userNameWebsession.get(userName);
            if (webSessoin == null || webSessoin.isEmpty())
                return;
            webSessoin.remove(webSocketSession);
        }
    
        public static Set<String> getUserList() {
            return userNameWebsession.keySet();
        }
    }
    

    七、过程中用到的其他实体、启动类及页面

    7.1 启动类

    TestApp:

    package cn.pomit.springwork.nettynew;
    
    import cn.pomit.springwork.nettynew.server.http.JsonHttpServer;
    import cn.pomit.springwork.nettynew.server.tcp.StringTcpServer;
    import cn.pomit.springwork.nettynew.server.websocket.WebSocketServer;
    
    public class TestApp {
    
        public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException{        
            StringTcpServer stringTcpServerTest = new StringTcpServer(8088);
            JsonHttpServer jsonHttpServer = new JsonHttpServer(8880);
            WebSocketServer webSocketServer = new WebSocketServer(9999);
            try {
                stringTcpServerTest.start();
                jsonHttpServer.start();
                webSocketServer.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    

    7.2 Http响应实体

    HttpResponseMsg:

    package cn.pomit.springwork.nettynew.model.http;
    
    public class HttpResponseMsg {
        public enum ResType {  
            HTML("text/html"),
            JSON("application/json"),
            JS("application/javascript"),
            PNG("image/png"),
            JPG("image/jpg");
            String value = null;
            ResType(String value) {
                this.value = value;
            }
            public String getValue() {
                return value;
            }
        }  
    
        public enum ResCode {  
            NOT_FOUND(404),
            OK(200),
            INTERNAL_ERROR(500);
            int value = 200;
            ResCode(int value) {
                this.value = value;
            }
            public int getValue() {
                return value;
            }
        }  
        public int resCode;
    
        public String resType;
    
        public String message;
    
        public int getResCode() {
            return resCode;
        }
    
        public void setResCode(int resCode) {
            this.resCode = resCode;
        }
    
        public String getResType() {
            return resType;
        }
    
        public void setResType(String resType) {
            this.resType = resType;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
    }

    7.3 WebSocket传递实体

    MessageDTO:

    package cn.pomit.springwork.nettynew.model.websocket;
    
    public class MessageDTO {
        private String fromUserName;
        private String targetUserName;
        private String message;
        private String messageType;
    
        public String getFromUserName() {
            return fromUserName;
        }
    
        public void setFromUserName(String fromUserName) {
            this.fromUserName = fromUserName;
        }
    
        public String getTargetUserName() {
            return targetUserName;
        }
    
        public void setTargetUserName(String targetUserName) {
            this.targetUserName = targetUserName;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public String getMessageType() {
            return messageType;
        }
    
        public void setMessageType(String messageType) {
            this.messageType = messageType;
        }
    
        public static enum Type {
            TYPE_NEW("0000"), TYPE_TEXT("1000"), TYPE_BYTE("1001"), TYPE_ERROR("1111");
            private String messageType;
    
            Type(String messageType) {
                this.messageType = messageType;
            }
    
            public String getMessageType() {
                return messageType;
            }
    
            public void setMessageType(String messageType) {
                this.messageType = messageType;
            }
    
        }
    }
    

    7.4 WebSocket前端页面

    <!DOCTYPE html>
    <html>
    
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>品茗IT-WebSocket测试</title>
    
        <!-- CSS  -->
        <link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
        <link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
    
      <style>
    body { text-align:left; margin:0; font:normal 12px Verdana, Arial;
    background:#FFEEFF } form { margin:0; font:normal 12px Verdana,
    Arial; } table,input { font:normal 12px Verdana, Arial; }
    a:link,a:visited{ text-decoration:none; color:#333333; } a:hover{
    text-decoration:none; color:#FF6600 } #main { width:400px;
    position:absolute; left:600px; top:100px; background:#EFEFFF;
    text-align:left; filter:Alpha(opacity=90) } #ChatHead {
    text-align:right; padding:3px; border:1px solid #003399;
    background:#DCDCFF; font-size:20px; color:#3366FF; cursor:move; }
    #ChatHead a:link,#ChatHead a:visited, { font-size:14px;
    font-weight:bold; padding:0 3px } #ChatBody { border:1px solid
    #003399; border-top:none; padding:2px; } #ChatContent {
    height:200px; padding:6px; overflow-y:scroll; word-break: break-all
    }#ChatBtn { border-top:1px solid #003399; padding:2px }
    
      </style>
    </head>
    <script type="text/javascript">var ws = null;
    var curUser=null;
    var chatUser = null;
    var imgName = null;
    var fileImgSize = 0;
    window.onbeforeunload = function()
    {
        disconnect(ws);
    }
    
    functiongs(d) {
        var t = document.getElementById(d);
        if (t) {
            return t.style;
        } else {
            returnnull;
        }
    }
    functiongs2(d, a) {
        if (d.currentStyle) {
            var curVal = d.currentStyle[a]
        } else {
            var curVal = document.defaultView
                    .getComputedStyle(d, null)[a]
        }
        return curVal;
    }
    functionChatHidden() {
        gs("ChatBody").display = "none";
    }
    functionChatShow() {
        gs("ChatBody").display = "";
    }
    functionChatClose() {
        gs("main").display = "none";
    }
    functionChatNew(userId) {
        gs("main").display = "";
        chatUser = userId;
        $("#ChatUsers").html(chatUser);
        $('.emotion').qqFace({
    
            id : 'facebox', 
    
            assign:'saytext', 
    
            path: './img/arclist/'//表情存放的路径
    
        });
    }
    functionChatClear(obj) {
        $("#ChatContent").html("");
    }
    
    functionChatRead() {
        if(document.getElementById(chatUser)){
            document.getElementById(chatUser).setAttribute('src', './img/users.png');
        }
    }
    
    functionChatSend(obj) {
        var o = obj.ChatValue;
        var msg = replace_em(o.value);
        if (o.value.length > 0) {
            $("#ChatContent").append(
                    "<p align="right"><strong>" + curUser + "(我) :</strong>" + msg
                            + "</p>");
            var number = $("#ChatContent").scrollTop();
            number += 16;
            $("#ChatContent").scrollTop(number);
            if(ws!=null){
                var json={"fromUserName":curUser,"targetUserName":chatUser,"message":o.value,"messageType":"1000"};
                // encodeURI(o.value)console.log(json);
                ws.send(JSON.stringify(json));
            }
            o.value = '';
        }
    
        var img = obj.ChatFile;
        if (img.value.length > 0){
            $("#ChatContent").append(
                    "<p align="right"><strong>" + nickName + "(我) :</strong>" + img.value
                            + "</p><br/>");
    
            imgName = nickName+'(我)';
            fileImgSize = img.files.length;
            //alert(fileImgSize);
            $.ajaxFileUpload({
                //处理文件上传操作的服务器端地址(可以传参数,已亲测可用)
                url:'im/fileUpload?userId='+muserId,
                secureuri:true,                       //是否启用安全提交,默认为false 
                fileElementId:'ChatFile',           //文件选择框的id属性
                dataType:'text',                       //服务器返回的格式,可以是json或xml等
                success:function(data, status){        //服务器响应成功时的处理函数//$("#ChatContent").append("<p align="right">" + data + "</p><br/>");
                },
                error:function(data, status, e){ //服务器响应失败时的处理函数
                    $("#ChatContent").append('<p align="right">图片上传失败,请重试!!</p><br/>');
                    imgName = msgUser;
                }
            });
        }
    }
        if (document.getElementById) {
            (function() {
                if (window.opera) {
                    document.write("<input type='hidden' id='Q' value=' '>");
                }
    
                var n = 500;
                var dragok = false;
                var y, x, d, dy, dx;
    
                functionmove(e) {
                    if (!e)
                        e = window.event;
                    if (dragok) {
                        d.style.left = dx + e.clientX - x + "px";
                        d.style.top = dy + e.clientY - y + "px";
                        returnfalse;
                    }
                }
    
                functiondown(e) {
                    if (!e)
                        e = window.event;
                    var temp = (typeof e.target != "undefined") ? e.target
                            : e.srcElement;
                    if (temp.tagName != "HTML" | "BODY"
                            && temp.className != "dragclass") {
                        temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                                : temp.parentElement;
                    }
                    if ('TR' == temp.tagName) {
                        temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                                : temp.parentElement;
                        temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                                : temp.parentElement;
                        temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                                : temp.parentElement;
                    }
    
                    if (temp.className == "dragclass") {
                        if (window.opera) {
                            document.getElementById("Q").focus();
                        }
                        dragok = true;
                        temp.style.zIndex = n++;
                        d = temp;
                        dx = parseInt(gs2(temp, "left")) | 0;
                        dy = parseInt(gs2(temp, "top")) | 0;
                        x = e.clientX;
                        y = e.clientY;
                        document.onmousemove = move;
                        returnfalse;
                    }
                }
    
                functionup() {
                    dragok = false;
                    document.onmousemove = null;
                }
    
                document.onmousedown = down;
                document.onmouseup = up;
    
            })();
        }
    
    </script>
    <body>
    <divid="main"class="dragclass"onclick="ChatRead()"style="left: 400px; top: 200px;">
            <divid="ChatUsers"style="100px; padding:3px; font-size:15px;float:left; display:inline"></div>
            <divid="ChatHead">
                <ahref="#"onclick="ChatHidden();">-</a> <ahref="#"onclick="ChatShow();">+</a> <ahref="#"onclick="ChatClose();">x</a>
            </div>
            <divid="ChatBody">
                <divid="ChatContent"></div>
                <divid="ChatBtn">
                    <formaction=""name="chat"method="post">
                        <textareaname="ChatValue"id="saytext"rows="3"style=" 350px"></textarea>
                        <inputname="Submit"type="button"value="发送"onclick="ChatSend(this.form);" />
                        <inputname="ClearMsg"type="button"value="清空记录"onclick="ChatClear(this.form);" />
                        <inputtype="button"class="emotion"value="表情">
                        <inputid="ChatFile"type="file"name="myfiles"multiple>   
                    </form>
                </div>
            </div>
    </div>
    <divid="modalAddUser"class="modal modal-fixed-footer"style="max-400px;max-height:400px">
        <divclass="modal-content">
          <h4>生成用户名</h4>
              <divclass="row center">
                 <inputclass="browser-default searchInput"placeholder="请输入用户名"style="margin-top:50px;margin-left:20px;max-300px"id="catoryAddText"type="text" >
    
             </div>
             <divclass="row center">                                  
                 <aclass="waves-effect waves-light btn"id="userAddBtn"style="color:white;"><iclass="material-icons"style="font-size:1.1rem">添用户</i></a>
             </div>
        </div>
        <divclass="modal-footer">
          <ahref="#!"class=" modal-action modal-close waves-effect waves-green btn-flat">关闭</a>
        </div>
    </div>
    <divalign="left"style="margin-top: 50px;margin-left: 20px;">
        <p>欢迎您,
            <spanid="userName">匿名用户</span>
        </p>
        <aid="addUser"class="btn waves-effect waves-light white cyan-text"style="border-radius: 40px;">添加用户</a>
        <pid="content"></p>
    </div>
    <scriptsrc="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
    <scriptsrc="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
    <scriptsrc="./js/websocket.js"></script>
    <scriptsrc="./js/ajaxfileupload.js"></script>
    <scriptsrc="./js/jquery-browser.js"></script>
    <scriptsrc="./js/jquery.qqFace.js"></script>
    <script>functiongetUser(){
        $.ajax({
            type : "get",
            url : "../im/user",
            dataType : "json",
            data : {} ,
            success : function(data) {
                if(data.errorCode == "0000"){
                    $("#userName").html(data.data);
                    curUser = data.data;
                }
            },
            error : function(XMLHttpRequest, textStatus, errorThrown) {
                alert(errorThrown);
            }
        });
    }    
    functionaddUser(userName){
        $.ajax({
            type : "post",
            url : "../im/setUser",
            dataType : "json",
            data : {"userName":userName} ,
            success : function(data) {
                if(data.errorCode == "0000"){
                    $("#userName").html(userName);
                    curUser = data.data;
                }
            },
            error : function(XMLHttpRequest, textStatus, errorThrown) {
                alert(errorThrown);
            }
        });
    }
    functionuserList(){
        $.ajax({
            type : "get",
            url : "../im/userList",
            dataType : "json",
            data : {} ,
            success : function(data) {
                if(data.errorCode == "0000"){
                    var content = "";
                    for(var i =0;i<data.data.length;i++){
                        var userId = data.data[i];
                        content += "<img src="./img/msgget.gif" id=""
                            + userId
                            + "" alt="" style="cursor: pointer" width='40px' "
                            + "onclick="ChatNew('"+userId+"')" />"
                            + userId
                            + "<br><br>";
                    }
                    $("#content").append(content);
                }
            },
            error : function(XMLHttpRequest, textStatus, errorThrown) {
                alert(errorThrown);
            }
        });
    }
    
    window.onbeforeunload = function(){
        disconnect(ws);
    }
    $(function () {
        $('.modal').modal({
            dismissible: true, // 点击模态外面模态消失关闭
            opacity: 0.1, // 相对于背景的不透明度
            in_duration: 300, // 显示特效的时间
            out_duration: 200, // 消失特效时间
            starting_top: '80%', // 启动时的样式属性
            ending_top: '20%', // 结束时的样式属性
            ready: function(modal, trigger) { // 模态加载完成触发事件
    
            },
            complete: function() { 
    
            } // 关闭时触发的事件
        });
        getUser();
        $("#addUser").click(function() {
            $('#modalAddUser').modal('open');    
        });
    
        $("#userAddBtn").click(function() {
            var catory = $('#catoryAddText').val();
            addUser(catory);
        });
        userList();
        if (ws == null) {
            var url = getUrl();
            //alert("url:"+url);  if (!url) {  
                return;  
            }  
            console.log(url);
            ws = new WebSocket(url);  
            connect(ws);
            ChatClose();
        }
    });
    </script>
    </body>
    
    </html>

    js略大了点,不贴了,直接加群找我要吧

  • 相关阅读:
    SQL 2008 数据库只读 修改
    java List 简单使用
    鼠标右键菜单 删除
    SQL distinct
    日系插画学习笔记(五):日系角色脸部画法-1头部
    日系插画学习笔记(四):基础人体结构
    日系插画学习笔记(三):光影与结构
    日系插画学习笔记(二):结构与透视
    日系插画学习笔记(一):SAI软件基础
    spring mvc 静态资源版本控制
  • 原文地址:https://www.cnblogs.com/exmyth/p/14170765.html
Copyright © 2011-2022 走看看