一、概述

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="width: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="width: 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-width: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-width: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略大了点,不贴了,直接加群找我要吧

Socket、Http、WebSocket?强大的Netty几行语句就帮你实现!的更多相关文章

  1. TCP UDP socket http webSocket 之间的关系

    ---恢复内容开始--- OSI&TCP/IP模型 要弄清tcp udp socket http websocket之间的关系,首先要知道经典的OSI七层模型,与之对应的是TCP/IP的四层模 ...

  2. C# Socket 实现WebSocket服务器端

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.N ...

  3. Cygwin(类UNIX模拟环境)&CURL(强大的http命令行工具)

    前言: 需要我用curl试下能否发送post请求调起公司的仿真系统(目前) 跟着大佬的脚步,亲测一把~ 感谢大佬的提供的博客和指导 @咩神 个人博客园及来源地址 Cygwin(类UNIX模拟环境) 一 ...

  4. Socket与WebSocket以及http与https重新总结

    Socket与WebSocket以及http与https重新总结 一.Socket 网络中的Socket是一个抽象的接口 ,而是为了方便使用TCP或UDP而抽象出来的一层 ,可以理解为网络中连接的两端 ...

  5. Http、Socket、WebSocket之间联系与区别

    WebSocket和Socket区别 可以把WebSocket想象成HTTP(应用层),HTTP和Socket什么关系,WebSocket和Socket就是什么关系. HTTP 协议有一个缺陷:通信只 ...

  6. [Java][Android][Process] 暴力的服务能够解决一切,暴力的方式运行命令行语句

    不管是在Java或者Android中运行命令行语句殊途同归都是创建一个子进程运行调用可运行文件运行命令.类似于Windows中的CMD一样. 此时你有两种方式运行:ProcessBuilder与Run ...

  7. 通过sql 向数据库插入多行语句

    我们知道通过insert into 表名(列名) values(值)是向表中插入一条语句,可是当我们需要向数据库插入多条语句时,应该怎么做呢? 可以通过如下格式的sql 语句来实现一次向数据库插入多行 ...

  8. Python 1基础语法一(注释、行与缩进、多行语句、空行和代码组)

    一.注释Python中单行注释以 # 开头,实例如下: # 第一个注释 print ("Hello, Python!") # 第二个注释 输出结果为: ============== ...

  9. websocket 进阶!netty框架实现websocket达到高并发

    引言: 在前面两篇文章中,我们对原生websocket进行了了解,且用demo来简单的讲解了其用法.但是在实际项目中,那样的用法是不可取的,理由是tomcat对高并发的支持不怎么好,特别是tomcat ...

  10. socket.io websocket

    不能不知道的事: 在Http协议中,客户端向服务器端发送请求,服务器端收到请求再进行回应,整个过程中,服务器端是被动方,客户端是主动方: websoket是H5的一种基于TCP的新通信协议,它与Htt ...

随机推荐

  1. Java日期时间API系列35-----Jdk8中java.time包中的新的日期时间API类应用,微秒和纳秒等更精确的时间格式化和解析。

    通过Java日期时间API系列1-----Jdk7及以前的日期时间类中得知,Java8以前除了java.sql.Timestamp扩充纳秒,其他类最大只精确到毫秒:Java8 time包所有相关类都支 ...

  2. 数据库运维实操优质文章文档分享(含Oracle、MySQL等) | 2024年8月刊

    本文为大家整理了墨天轮数据社区2024年8月发布的优质技术文章/文档,主题涵盖Oracle.MySQL.PostgreSQL等主流数据库系统以及国产数据库的技术实操,从基础的安装配置到复杂的故障排查, ...

  3. c#传统读取配置文件

    using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; namespace C ...

  4. 1. react项目【前端】+C#【后端】从0到1

    1.创建前端基础框架 1.1 前端创建 软件: 1.1.1 npx create-react-app pc ps:pc 是文件名 : 1.1.2 npm start 启动项目 2.创建后端基础框架 软 ...

  5. vue3中没有 this 环境变量了

    因为 api setup 在 beforecreate 之前执行,所以 this 是 undefined : setup 不能是一个 async 函数 ,因为返回值不是 对象了 ,而是 promise ...

  6. C# 并发控制框架:单线程环境下实现每秒百万级调度

    前言 在工业自动化和机器视觉领域,对实时性.可靠性和效率的要求越来越高.为了满足这些需求,我们开发了一款专为工业自动化运动控制和机器视觉流程开发设计的 C# 并发流程控制框架. 该框架不仅适用于各种工 ...

  7. OpenFunction v1.0.0 发布:集成 WasmEdge,支持 Wasm 函数和更完整的 CI/CD

    OpenFunction 是一个开源的云原生 FaaS(Function as a Service,函数即服务)平台,旨在帮助开发者专注于业务逻辑的研发.今天,我们非常高兴地宣布 OpenFuncti ...

  8. C++内存模型实践探索

    前言 C++对象模型是个常见.且复杂的话题,本文基于Itanium C++ ABI通过程序实践介绍了几种 简单C++继承 场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局.本文展 ...

  9. 带你了解nginx功能

    关于Nginx 简介 功能 基本的HTTP服务器功能 其他HTTP服务器功能 邮件代理服务器功能 TCP / UDP代理服务器功能 架构和可扩展性 适用平台 简介 Nginx (engine x) 是 ...

  10. C++刷题小知识点

    数据结构定义 struct ListNode { int val; ListNode *next; ListNode() : val(0), next(nullptr) {} ListNode(int ...