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 測試