Netty是目前最流行的由JBOSS提供的一个Java开源框架NIO框架,Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
相比JDK原生NIO,Netty提供了相对十分简单易用的API,非常适合网络编程。Netty是完全基于NIO实现的,所以Netty是异步的。
Mina同样也是一款优秀的NIO框架,而且跟Netty是出自同一个人之手,但是Netty要晚一点,优点更多一些,想了解更多可以直接搜索mina和netty比较。
使用Netty,我们可以作为Socket服务器,也可以用来做Http服务器,同时也可以支持WebSocket,这里,我们将这三种方式都详细介绍一下。
**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a
href="https://jq.qq.com/?_wv=1027&k=52sgH1J"
target="_blank">
加入我们的java学习圈,点击即可加入
</a>
,共同学习,节约学习时间,减少很多在学习中遇到的难题。**
只需要引入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的常用配置:
启动的时候,new一个子类,调用start方法即可。
有了上面的NettyServiceTemplate,我们可以用几行代码构建一个tcp服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。
下面的ChannelHandler数组包含:
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,稍微复杂点,复杂只是相对于返回值的控制,cookie的控制,session的控制,因为netty只负责帮你解析了http的所有信息,并没有像sevlet容器那样还给你控制session了啥的。
我这里只用netty做json数据转换,用来提供rest服务,如果想了解更多,可以看下我用netty实现的一个类似Spring容器的工具:https://gitee.com/ffch/teaboot, 这个工具解析html、rest等,提供了简单的session控制、security控制。代码属于早期内容,尚待维护。
这个服务器是专门面向rest服务的。有了上面的NettyServiceTemplate,我们可以用几行代码构建一个http服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。
ChannelHandler数组中:
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; } }
请求解码器就是转换下数据,变成字符串。
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); } }
响应编码器中,定义了连接的配置信息,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(); } } }
这里的业务处理器很简单,就打印返回。
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,相对于http还稍微简单点。
Netty对WebSocket做了完全控制,你需要做的只是对WebSocket的用户进行控制,能根据用户找到相应的通道即可。
有了上面的NettyServiceTemplate,我们可以用几行代码构建一个WebSocket服务器。实现父类的抽象方法createHandlers,传递ChannelHandler数组。
ChannelHandler数组中:
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; } }
下面是用websocket做聊天室的逻辑:
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(); } }
用一个并发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(); } }
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(); } } }
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; } }
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; } } }
<!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); } function gs(d) { var t = document.getElementById(d); if (t) { return t.style; } else { return null; } } function gs2(d, a) { if (d.currentStyle) { var curVal = d.currentStyle[a] } else { var curVal = document.defaultView .getComputedStyle(d, null)[a] } return curVal; } function ChatHidden() { gs("ChatBody").display = "none"; } function ChatShow() { gs("ChatBody").display = ""; } function ChatClose() { gs("main").display = "none"; } function ChatNew(userId) { gs("main").display = ""; chatUser = userId; $("#ChatUsers").html(chatUser); $('.emotion').qqFace({ id : 'facebox', assign:'saytext', path: './img/arclist/' //表情存放的路径 }); } function ChatClear(obj) { $("#ChatContent").html(""); } function ChatRead() { if(document.getElementById(chatUser)){ document.getElementById(chatUser).setAttribute('src', './img/users.png'); } } function ChatSend(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; function move(e) { if (!e) e = window.event; if (dragok) { d.style.left = dx + e.clientX - x + "px"; d.style.top = dy + e.clientY - y + "px"; return false; } } function down(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; return false; } } function up() { dragok = false; document.onmousemove = null; } document.onmousedown = down; document.onmouseup = up; })(); } </script> <body> <div id="main" class="dragclass" onclick="ChatRead()" style="left: 400px; top: 200px;"> <div id="ChatUsers" style="width:100px; padding:3px; font-size:15px;float:left; display:inline"></div> <div id="ChatHead"> <a href="#" onclick="ChatHidden();">-</a> <a href="#" onclick="ChatShow();">+</a> <a href="#" onclick="ChatClose();">x</a> </div> <div id="ChatBody"> <div id="ChatContent"></div> <div id="ChatBtn"> <form action="" name="chat" method="post"> <textarea name="ChatValue" id="saytext" rows="3" style="width: 350px"></textarea> <input name="Submit" type="button" value="发送" onclick="ChatSend(this.form);" /> <input name="ClearMsg" type="button" value="清空记录" onclick="ChatClear(this.form);" /> <input type="button" class="emotion" value="表情"> <input id="ChatFile" type="file" name="myfiles" multiple> </form> </div> </div> </div> <div id="modalAddUser" class="modal modal-fixed-footer" style="max-width:400px;max-height:400px"> <div class="modal-content"> <h4>生成用户名</h4> <div class="row center"> <input class="browser-default searchInput" placeholder="请输入用户名" style="margin-top:50px;margin-left:20px;max-width:300px" id="catoryAddText" type="text" > </div> <div class="row center"> <a class="waves-effect waves-light btn" id="userAddBtn" style="color:white;"><i class="material-icons" style="font-size:1.1rem">添用户</i></a> </div> </div> <div class="modal-footer"> <a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">关闭</a> </div> </div> <div align="left" style="margin-top: 50px;margin-left: 20px;"> <p>欢迎您, <span id="userName">匿名用户</span> </p> <a id="addUser" class="btn waves-effect waves-light white cyan-text" style="border-radius: 40px;">添加用户</a> <p id="content"></p> </div> <script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script> <script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script> <script src="./js/websocket.js"></script> <script src="./js/ajaxfileupload.js"></script> <script src="./js/jquery-browser.js"></script> <script src="./js/jquery.qqFace.js"></script> <script> function getUser(){ $.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); } }); } function addUser(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); } }); } function userList(){ $.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略大了点,不贴了,直接加群找我要吧
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句