Netty 整合 SpringMVC
1.导入 maven 依赖
<properties> ...... <!-- spring --> <spring.version>5.1.1.RELEASE</spring.version> <!-- jackson-json --> <jackson.version>2.9.4</jackson.version> <!-- log4j --> <slf4j.version>1.7.18</slf4j.version> <log4j.version>1.2.17</log4j.version> </properties> <dependencies> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- Jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- AOP --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> <!-- 日志相关 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <!-- netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.44.Final</version> </dependency> </dependencies>
2.创建 spring.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 自动扫描的包名 --> <context:component-scan base-package="com.wode" /> <!-- 开启AOP代理 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!--开启注解处理器 --> <context:annotation-config /> </beans>
3.创建 spring-mvc.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 开启SpringMVC注解模式 --> <mvc:annotation-driven /> <!-- 扫描web相关的bean --> <context:component-scan base-package="com.wode.controller" /> <!-- 静态资源默认servlet配置 --> <mvc:default-servlet-handler/> </beans>
4.创建 Spring 管理器
public class SpringManager { //单例 private static SpringManager instance = new SpringManager(); private ApplicationContext ctx; private XmlWebApplicationContext mvcContext; private DispatcherServlet dispatcherServlet; private SpringManager() { ctx = new ClassPathXmlApplicationContext("spring.xml"); mvcContext = new XmlWebApplicationContext(); mvcContext.setConfigLocation("classpath:spring-mvc.xml"); mvcContext.setParent(ctx); MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet"); dispatcherServlet = new DispatcherServlet(mvcContext); try { dispatcherServlet.init(servletConfig); } catch (Exception e) { e.printStackTrace(); } } public static SpringManager getInstance(){ return instance; } public ApplicationContext getSpringContext(){ return ctx; } public XmlWebApplicationContext getMvcContext(){ return mvcContext; } public DispatcherServlet getDispatcherServlet(){ return dispatcherServlet; } }
5.创建 Netty 启动类
public class NettyServer { //单例 private static NettyServer instance = new NettyServer(); private NettyServer() {} private static NettyServer getInstance(){ return instance; } public void start(int port) 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.addListener(f -> { if (f.isSuccess()) { System.out.println("服务启动成功"); } else { System.out.println("服务启动失败"); } }); //等待服务监听端口关闭 future.channel().closeFuture().sync(); } finally { //释放资源 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) { try { SpringManager.getInstance(); NettyServer.getInstance().start(8080); } catch (Exception e) { e.printStackTrace(); } }
6.创建请求分发处理器
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); } }
7.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 nettyRequest = requestVo.getRequest(); boolean isKeepAlive = HttpUtil.isKeepAlive(nettyRequest); MockHttpServletRequest servletRequest = RequestTransUtil.transRequest2Spring(nettyRequest); MockHttpServletResponse servletResponse = new MockHttpServletResponse(); try { SpringManager.getInstance().getDispatcherServlet().service(servletRequest, servletResponse); FullHttpResponse nettyResponse = RequestTransUtil.transResponse2Netty(servletResponse); ResponseUtil.sendHttpResponse(ctx, nettyResponse, isKeepAlive); } catch (Exception e) { ResponseUtil.sendHttpResponse(ctx, ResponseUtil.get500Response(), false); } } }
8.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> { //Channel属性名称:握手处理器 private static final AttributeKey<WebSocketServerHandshaker> HAND_SHAKE_ATTR = AttributeKey.valueOf("HAND_SHAKE"); public static final String WEBSOCKET_ID_ATTR = "wsId"; public static final String WEBSOCKET_STATE_ATTR = "wsState"; public static final int TYPE_OPEN = 1; public static final int TYPE_CLOSE = 2; public static final int TYPE_MSG = 3; @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String id = ctx.channel().id().asShortText(); WsClinetVo client = WsClientManager.getInstance().getClient(id); MockHttpServletRequest servletRequest = RequestTransUtil.transFrame2Spring(null, client.getUrl()); servletRequest.setAttribute(WEBSOCKET_ID_ATTR, id); servletRequest.setAttribute(WEBSOCKET_STATE_ATTR, TYPE_CLOSE); SpringManager.getInstance().getDispatcherServlet().service(servletRequest, new MockHttpServletResponse()); WsClientManager.getInstance().removeClient(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) throws Exception { String id = ctx.channel().id().asShortText(); MockHttpServletRequest servletRequest = RequestTransUtil.transRequest2Spring(request); servletRequest.setAttribute(WEBSOCKET_ID_ATTR, id); servletRequest.setAttribute(WEBSOCKET_STATE_ATTR, TYPE_OPEN); SpringManager.getInstance().getDispatcherServlet().service(servletRequest, new MockHttpServletResponse()); WsClientManager.getInstance().putClient(id, servletRequest.getRequestURI(), ctx.channel()); // 握手操作 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); //绑定属性到channel ctx.channel().attr(HAND_SHAKE_ATTR).set(handshaker); } } //处理websocket数据 private void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { // 判断是否关闭链路的指令 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().writeAndFlush(new PongWebSocketFrame(frame.content().retain())); return; } // 暂仅支持文本消息,不支持二进制消息 if (! (frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException("暂不支持该消息类型:" + frame.getClass().getName()); } // 处理消息 String id = ctx.channel().id().asShortText(); WsClinetVo client = WsClientManager.getInstance().getClient(id); MockHttpServletRequest servletRequest = RequestTransUtil.transFrame2Spring(frame, client.getUrl()); servletRequest.setAttribute(WEBSOCKET_ID_ATTR, id); servletRequest.setAttribute(WEBSOCKET_STATE_ATTR, TYPE_MSG); SpringManager.getInstance().getDispatcherServlet().service(servletRequest, new MockHttpServletResponse()); } }
c.创建客户端信息 VO 类
public class WsClinetVo { private String wsId; private String url; private Channel channel; public WsClinetVo() {} public WsClinetVo(String wsId, String url, Channel channel) { this.wsId = wsId; this.url = url; this.channel = channel; } public String getWsId() { return wsId; } public void setWsId(String wsId) { this.wsId = wsId; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Channel getChannel() { return channel; } public void setChannel(Channel channel) { this.channel = channel; } }
d.创建客户端信息管理类
public class WsClientManager { //单例 private static WsClientManager instance = new WsClientManager(); private WsClientManager(){} public static WsClientManager getInstance(){ return instance; } //socketID与用户信息的对应关系 private Map<String, WsClinetVo> clientMap = new ConcurrentHashMap<>(); //添加用户信息 public void putClient(String id, String url, Channel channel){ this.clientMap.put(id, new WsClinetVo(id, url, channel)); } //获取用户信息 public WsClinetVo getClient(String id){ return this.clientMap.get(id); } //删除用户信息 public void removeClient(String id){ this.clientMap.remove(id); } //发送消息 public void sendMsg(String id, String msg){ TextWebSocketFrame frame = new TextWebSocketFrame(msg); WsClinetVo client = clientMap.get(id); if(client == null || client.getChannel() == null){ return; } client.getChannel().writeAndFlush(frame); } }
9.创建测试 Controller
@RestController public class TestController { @RequestMapping("/add") public int add(int p1, int p2){ return p1 + p2; } @RequestMapping("/ws") public void socket(HttpServletRequest request){ String wsId = (String) request.getAttribute(WebSocketHandler.WEBSOCKET_ID_ATTR); int wsState = (int) request.getAttribute(WebSocketHandler.WEBSOCKET_STATE_ATTR); if(wsState == WebSocketHandler.TYPE_OPEN){ System.out.println("[" + wsId + "]正在连接,参数:[userId]" + request.getParameter("userId")); return; }else if(wsState == WebSocketHandler.TYPE_CLOSE){ System.out.println("[" + wsId + "]已断开"); return; } try { BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); StringBuffer buffer = new StringBuffer(); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } System.out.println("[" + wsId + "]接收消息:" + buffer.toString()); //发送消息 WsClientManager.getInstance().sendMsg(wsId, "Hello World"); }catch (Exception e){ e.printStackTrace(); } } }
10.创建工具类
a.创建请求响应转换工具类
public class RequestTransUtil { //Netty转Spring请求 public static MockHttpServletRequest transRequest2Spring(FullHttpRequest nettyRequest){ UriComponents uriComponents = UriComponentsBuilder.fromUriString(nettyRequest.uri()).build(); ServletContext servletContext = SpringManager.getInstance().getDispatcherServlet().getServletConfig().getServletContext(); MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext); servletRequest.setRequestURI(uriComponents.getPath()); servletRequest.setPathInfo(uriComponents.getPath()); servletRequest.setMethod(nettyRequest.method().name()); if (uriComponents.getScheme() != null) { servletRequest.setScheme(uriComponents.getScheme()); } if (uriComponents.getHost() != null) { servletRequest.setServerName(uriComponents.getHost()); } if (uriComponents.getPort() != -1) { servletRequest.setServerPort(uriComponents.getPort()); } for (String name : nettyRequest.headers().names()) { servletRequest.addHeader(name, nettyRequest.headers().get(name)); } ByteBuf content = nettyRequest.content(); content.readerIndex(0); byte[] data = new byte[content.readableBytes()]; content.readBytes(data); servletRequest.setContent(data); if (uriComponents.getQuery() != null) { String query = UriUtils.decode(uriComponents.getQuery(), "UTF-8"); servletRequest.setQueryString(query); } Map<String, String> paramMap = ParamUtil.getRequestParams(nettyRequest); if(! CollectionUtils.isEmpty(paramMap)){ for (Map.Entry<String, String> entry : paramMap.entrySet()) { servletRequest.addParameter(entry.getKey(), entry.getValue()); } } return servletRequest; } //WebSocket转Spring请求 public static MockHttpServletRequest transFrame2Spring(WebSocketFrame frame, String url){ ServletContext servletContext = SpringManager.getInstance().getDispatcherServlet().getServletConfig().getServletContext(); MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext); servletRequest.setRequestURI(url); servletRequest.setPathInfo(url); servletRequest.setMethod(HttpMethod.POST.name()); servletRequest.setContentType(HttpHeaderValues.TEXT_PLAIN.toString()); if(frame != null){ ByteBuf content = frame.content(); content.readerIndex(0); byte[] data = new byte[content.readableBytes()]; content.readBytes(data); servletRequest.setContent(data); } return servletRequest; } //Spring转Netty响应 public static FullHttpResponse transResponse2Netty(MockHttpServletResponse servletResponse){ HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus()); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(servletResponse.getContentAsByteArray())); for (String name : servletResponse.getHeaderNames()) { for (Object value : servletResponse.getHeaderValues(name)) { response.headers().add(name, value); } } return response; } }
b.创建请求参数工具类
public class ParamUtil { /** * 获取请求参数 */ public static Map<String, String> getRequestParams(HttpRequest request){ Map<String, String>requestParams=new HashMap<>(); // 处理get请求 if (request.method() == HttpMethod.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() == HttpMethod.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; } }
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) { ByteBufUtil.writeUtf8(response.content(), response.status().toString()); } //添加header描述length,避免客户端接收不到数据 if(StringUtils.isEmpty(response.headers().get(HttpHeaderNames.CONTENT_TYPE))){ response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN); } if(StringUtils.isEmpty(response.headers().get(HttpHeaderNames.CONTENT_LENGTH))){ response.headers().add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); } //解决跨域的问题 response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN,"*"); response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS,"*");//允许headers自定义 response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS,"GET, POST, PUT,DELETE"); response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS,"true"); // 如果是非Keep-Alive,关闭连接 if (! HttpUtil.isKeepAlive(request) || response.status().code() != 200) { response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); }else{ response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); ctx.channel().writeAndFlush(response); } } }
11.测试
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; } .msg-div{ width: 80%; margin:0 0 0 10%; height: 300px; border:solid 1px; overflow: scroll; } .msg-input-wrapper{ margin: 0 0 0 10%; } </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 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 msg-input-wrapper"> <input class="msg-input" type="text"/> </div> <div class="col"> <button id="send-btn-2">发送</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(2); $(function(){ initWebSocketFunc(userIdArray[0], 0); initWebSocketFunc(userIdArray[1], 1); $("#send-btn-1").on("click", {num: 0}, sendMsgFunc); $("#send-btn-2").on("click",{num: 1}, 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 = evt.data; $(".msg-div:eq(" + num + ")").append("接收:" + msg + "<br/>"); // $($(".msg-div")[num]).append("接收:" + msg + "<br/>"); }; // 断开 web socket 连接成功触发事件 ws.onclose = function () { console.log("连接已关闭..."); }; wsArray[num] = ws; }; let sendMsgFunc = function(e){ let num = e.data.num; let msg = $(".msg-input:eq(" + num + ")").val(); // let msg = $($(".msg-input")[num]).val(); wsArray[num].send(msg); $(".msg-div:eq(" + num + ")").append("发送:" + msg + "<br/>"); } </script> </html>
b.Http测试:访问 http://localhost:8080/add?p1=2&p2=3 测试