websocket实现聊天室应用,包括文字和图片上传

5 篇文章 1 订阅
4 篇文章 0 订阅

谈websocket之前,我们可以先复习一下web通信的一些概念。

推(PUSH)和拉(PULL):

推和拉是Web通信的两种方式,从定义上来说它们最主要的区别在于方向性,推是服务器主动向客户端推送消息,拉则是客户端主动发请求向服务器获取数据。

优缺点及适用场景:

1、拉模式是最常用、最成熟的一种方式,它依托了非常成熟的HTTP协议,有大量的工具类和库可以使用,请求/响应模式几乎每个程序员都知道,出现问题也容易找到相应的解决方案。

2、拉模式的限制特别少,任何一款浏览器,只要能称为浏览器的,不管多古老都支持拉,推的话像websocket是H5才支持,IE9都不支持,不仅是客户端,服务端也是有限制的,要较新的服务器才支持websocket,如Tomcat 7.0.5,WebLogic 12c ,IIS 7.0...以上的版本才“真正”支持websocket。

3、对于一般请求,拉模式占用的资源也少,比如请求个列表或者明细,服务器处理完并返回数据后,连接就断开了,简洁明了不占用连接资源。所以拉适合大多数一般的请求场景,但对于后台数据变化需要实时更新的,比如股票市场、网页游戏和聊天实等程序,拉就不适合了。

长轮询和长连接

在websocket出现之前,要实现服务器推,有两种方式,分别是长轮询和长连接。

长轮询:

轮询(Polling):客户端(浏览器)设一个定时器或死循环不停地向服务端发送查询请求,轮询的结果可能是服务端有更新就返回新的数据,也可能时服务端根本就没更新,得到一个空的信息。但是不管结果如何,客户端在下一个时间节点都会进行新的一轮请求。    

短轮询: 服务端对每次请求立即返回结果。

长轮询: 长轮询是对短轮询的改善,服务端接收到请求后先查看数据,如果有更新立即返回,如果没有则把请求挂起一段时间,在这段时间内有更新就返回,如果一直没更新直到超时,服务器会主动向客户端发送本次轮询无新信息的正常响应,并断开连接,这种响应也被称之为“心跳”。再有就是如果网络故障导致轮询的意外中断,此时浏览器将收到错误信息。

由上可知,对于客户端来说,不管是长轮询还是短轮询,客户端的动作都是一样的,就是死循环不停的去请求。即一种轮询方式是否为长轮询,是根据服务端的处理方式来决定的,与客户端没有关系。

长连接:

HTTP长连接:HTTP分为长连接和短连接,它们外在表现形式的区别就是请求/响应头中是否有Connection:keep-alive。内在的区别就是TCP连接是否会被复用,即多个HTTP请求是否可以复用同一个TCP连接,可以即长连接,HTTP长连接可以节省了很多TCP连接建立和断开的消耗。比如请求一个网页,现在的网页大多不仅仅只有HTML,一般还包含了CSS、JS、图片等一系列资源,如果用短连接就需要建立几个甚至几十个TCP连接,长连接复用一个就好了,HTTP1.1以后默认都是长连接了。

实现服务器推的长连接:基于iframe流的方式,其实现原理主要是在页面隐藏一个iframe(display:none),前台设定iframe的src属性为对一个长连接的请求,服务器返回对页面函数的调用。使用iframe流方式的话浏览器会一直转圈(进度条一直没完)。这种iframe流的长连接实现服务器推的模式比较少见,与ajax长轮询稍有区别。一般ajax轮询使用XMLHttpRequest 对象发送请求,在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时使用回调函数对返回的数据进行处理。而iframe服务端返回的是JS脚本,客户端得到该脚本后就自动执行。简单的说,ajax返回的是数据而后再使用回调函数处理该数据,iframe返回的是JS脚本(脚本内可能包含数据),浏览器自动执行。

如:

iframe流实现方式:

服务端代码:

[java]  view plain  copy
  1. while(true){  
  2.    while(!condition){<span style="font-family:Arial, Helvetica, sans-serif;">//模拟阻塞,等待条件成熟,推送信息</span>  
  3. <span style="white-space:pre;"> </span>Thread.sleep(1000);<span style="font-family:Arial, Helvetica, sans-serif;">   </span>  
  4.    }  
  5.    num++;  
  6.    PrintWriter pw = resp.getWriter();    
  7.    pw.println("<script>parent.changeNumber("+num+")</script>");  
  8.    pw.flush();   
  9. }  


可以看到,服务端使用的是PrintWriter,等于说是将

[java]  view plain  copy
  1. <script>parent.changeNumber("+num+")</script>  

写在浏览器里面

客户端代码:

[html]  view plain  copy
  1. <iframe id="polling" src="IframeCometServlet" style="display:none;"></iframe>   
  2. function changeNumber(num) {  //服务端返回的字符串调用的方法  
  3.     document.getElementById("number").innerHTML = num;    
  4. }  


注意changeNumber(num)这个函数,与服务端返回的一致(服务端返回的为什么要带一个parent.前缀?因为是iframe接收啊,而changeNumber是定义在外面的)。

ajax长轮询方式:

服务端代码:

[java]  view plain  copy
  1. @RequestMapping(value = "/list")  
  2.    public String list(HttpServletRequest request) {  
  3.        while(condition){//等待条件成熟  
  4.           //do something  
  5.    break;  
  6. }  
  7. return "something";  
  8.    }  

客户端代码(该代码要在循环里面才能达到轮询效果):

[html]  view plain  copy
  1. $.ajax({  
  2.         type: "POST",  
  3.         url: "/web/goods/list",    
  4.         data: JSON.stringify({data:'hello'}),  
  5.         contentType: "application/json; charset=utf-8",  
  6.         dataType: "json",  
  7.         success: function (result) {  
  8.           //do something  
  9.         }  
  10.   });  


通过长轮询和长连接可以看到,它们都并不是真正的服务器推,只是变相的拉而已,这是对HTTP体系的妥协。

由此引出真正的服务器推技术:WebSocket

WebSocket

WebSocket是什么?

WebSocket和HTTP一样,是基于TCP的应用层通信协议,通过维持一条持久的连接以实现浏览器与服务器全双工通信,是随着H5一起出来的。

WebSocket和HTTP有什么关系么?

Websocket其实是一个新协议,除了最开始借用了HTTP来完成握手,后面跟HTTP基本没有关系了。

传统的HTTP要不断的建立、关闭连接(这里的关闭并不是说关闭TCP连接,而是关闭HTTP连接),而且由于HTTP是无状态性的,每次都要重新传输identity info(鉴别信息),来告诉服务端你是谁,当然,一般只是带个JSESSIONID之类的,服务端通过这个就知道你的其他信息了。WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在断掉 WebSocket 连接前,都不需要客户端和服务端重新发起连接请求,即只需一次HTTP握手,整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的无状态性。

WebSocket握手

WebSocket的握手过程就是建立WebSocket连接的过程,握手是借用了HTTP的。


总体步骤大致如下:

Step 1: 建立TCP连接(这一步是一切的基础,HTTP也一样)。

Step 2: 浏览器借助一个WebSocket 客户端对象,连接类似 ws://yourdomain:port/path或wss://yourdomain:port/path的URL,WebSocket 客户端对象会自动解析并识别为要建立WebSocket 连接,从而发送HTTP Get 请求,并为请求自动添加一些供WebSocket使用的HTTP Headers字段。注意,连接头变成了ws,而不是一般的http,这是协议的意思,ws代表websocket。

Step 3: Server收到HTTP请求后,会把Step 1的TCP连接的应用层协议从HTTP转变为WebSocket。至此,HTTP部分就退出舞台了,WebSocket开始接管一切。

WebSocket握手报文(HTTP)分析:

请求

[html]  view plain  copy
  1. GET /chat HTTP/1.1  
  2. Host: 127.0.0.1:8080  
  3. Upgrade: websocket  
  4. Connection: Upgrade  
  5. Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==  
  6. Sec-WebSocket-Version: 13  
  7. Origin: http://localhost:8080  

响应

[html]  view plain  copy
  1. HTTP/1.1 101 Switching Protocols  
  2. Upgrade: websocket  
  3. Connection: Upgrade  
  4. Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=  
  5. Server:Apache-Coyote/1.1  
  6. Date:Sat, 17 Jun 2017 15:35:41 GMT  

以上报文和一般的HTTP有很多不同的地方:

1、请求和响应都有Upgrade: websocket和Connection: Upgrade,这是协议转换的意思,也就是请求时告诉服务器,本次连接需要的是websocket通信协议,而不是一般的HTTP,响应也带有该响应头表示转换成功。

一般响应成功的响应头是 HTTP/1.1 200 OK、HTTP/1.1 404 NOT FOUND等,此处是HTTP/1.1 101 Switching Protocols,同样101也是HTTP的一种响应状态,表示协议转换成功。

2、Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==和Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=

这是websocket握手的检验,也即测试服务器是否提供websocket服务,如果提供,Sec-WebSocket-Key和Sec-WebSocket-Accept必然是有对应关系的,具体的关系如下:

Sec-WebSocket-Key拼接上258EAFA5-E914-47DA-95CA-C5AB0DC85B11(这串magic string是写在协议里面的,所有的websocket底层实现都要拼接该串)

得到对拼接结果

JkTukkPc4Rha+nIkbYhEkQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

使用SHA-1(160数位)进行哈希操作,对哈希后的结果base64 进行编码,就可以得到结果

RD69G0BS8RPH/GbY6rBsZI75pjk=

以上操作不仅仅是服务端要做,客户端也同样做了该操作,然后将自己计算得到的结果和服务器传来的Sec-WebSocket-Accept作比较,如果相等则没问题,握手成功,如果不一致,则握手失败。

3、Origin用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接,所有来源于浏览器的请求都会带上该请求头。如,已授权站点www.mydomain.com,并且产生cookie,用户没关闭授权站点的时候点击另外的www.hack.com下面的链接,则该连接可以带着授权的cookie去做很多事。如果有了Origin,并且在服务端setAllowedOrigins("www.mydomain.com")则可以避免。

4、Sec-WebSocket-Version: 13 ,表明了websocket的版本,以前各厂商混战的时候什么样的版本都有,还好现在已经统一了,用13版即可。

代码以及部署

我的程序部署于tomcat7.0.5,基于Spring 4.3.7,需要使用支持websocket的IE10、Chrome和Firefox等较高级别版本的浏览器打开。

程序主要分为6个部分:2个配置xml、3个java文件、1个jsp。

前台主要用H5提供的websocket对象,它存在于顶层对象window下,凡是支持H5的浏览器都实现了该对象。

后台主要用Spring提供的websocket配置类WebSocketConfigurer、拦截器HttpSessionHandshakeInterceptor和处理器AbstractWebSocketHandler来实现。

如下:

web.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
  3.   <display-name>WebsocketTest</display-name>  
  4.     
  5.     <servlet>  
  6.         <servlet-name>springmvc</servlet-name>  
  7.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  8.         <init-param>  
  9.         <param-name>contextConfigLocation</param-name>  
  10.         <!-- <param-value>classpath:spring-mvc.xml</param-value> --><!-- 类路径 -->  
  11.         <param-value>/WEB-INF/spring-mvc.xml</param-value> <!-- 相对路径 -->  
  12.         </init-param>  
  13.     </servlet>  
  14.     <servlet-mapping>  
  15.         <servlet-name>springmvc</servlet-name>  
  16.         <url-pattern>*.sc</url-pattern>  
  17.     </servlet-mapping>  
  18.   
  19.   <welcome-file-list>  
  20.     <welcome-file>index.html</welcome-file>  
  21.     <welcome-file>index.htm</welcome-file>  
  22.     <welcome-file>index.jsp</welcome-file>  
  23.     <welcome-file>default.html</welcome-file>  
  24.     <welcome-file>default.htm</welcome-file>  
  25.     <welcome-file>default.jsp</welcome-file>  
  26.   </welcome-file-list>  
  27. </web-app>  


spring-mvc.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <beans xmlns="http://www.springframework.org/schema/beans"    
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"    
  4.     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"    
  5.     xmlns:context="http://www.springframework.org/schema/context"    
  6.     xsi:schemaLocation="    
  7.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd    
  8.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd    
  9.             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd    
  10.             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">    
  11.    <context:component-scan base-package="com.websocket"></context:component-scan>    
  12. </beans>    


WebSocketConfig.java

[java]  view plain  copy
  1. package com.websocket;  
  2.   
  3. import org.springframework.context.annotation.Bean;  
  4. import org.springframework.context.annotation.Configuration;  
  5. import org.springframework.web.socket.config.annotation.EnableWebSocket;  
  6. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;  
  7. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;  
  8.   
  9.   
  10. /** 
  11.  * @author wuwenhai 
  12.  * @since JDK1.6 
  13.  * @history 2017-6-3 wuwenhai 新建 
  14.  * 配置websocket入口,允许访问的域、注册Handler、SockJs支持和拦截器 
  15.  */  
  16. @Configuration  //配置类    
  17. @EnableWebSocket  //声明支持websocket    
  18. public class WebSocketConfig implements WebSocketConfigurer{    
  19.     
  20.     @Override    
  21.     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {    
  22.     <span style="white-space:pre;">   </span>String[] allowsOrigins={"http://localhost:8080"};  
  23.     <span style="white-space:pre;">   </span>//addHandler注册和路由的功能,当客户端发起websocket连接,把/path交给对应的handler处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。  
  24.     <span style="white-space:pre;">   </span>//setAllowedOrigins(String[] domains),允许指定的域名或IP(含端口号)建立长连接,默认只有本地。如果不限时使用"*"号,如果指定了域名,则必须要以http或https开头。  
  25.     <span style="white-space:pre;">   </span>//addInterceptors,顾名思义就是为handler添加拦截器,可以在调用handler前后加入自定义的逻辑代码。  
  26.         registry.addHandler(ChatRoom(), "/chat.sc").setAllowedOrigins("*").addInterceptors(handshakeInterceptor());   
  27.         //允许客户端使用SockJS    
  28.         //SockJS 是一个浏览器上运行的 JavaScript 库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持。  
  29.         registry.addHandler(ChatRoom(), "/sockjs/chat.sc").addInterceptors(handshakeInterceptor()).withSockJS();    
  30.     }    
  31.     
  32.     @Bean    
  33.     public HandshakeInterceptor handshakeInterceptor(){    
  34.         return new HandshakeInterceptor();    
  35.     }    
  36.       
  37.     @Bean    
  38.     public ChatRoom ChatRoom(){    
  39.         return new ChatRoom();    
  40.     }  
  41.     
  42. }    
  43.     

HandshakeInterceptor.java

[java]  view plain  copy
  1. package com.websocket;  
  2.   
  3. import java.util.Map;  
  4. import java.util.Random;  
  5. import org.springframework.http.server.ServerHttpRequest;  
  6. import org.springframework.http.server.ServerHttpResponse;  
  7. import org.springframework.web.socket.WebSocketHandler;  
  8. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;  
  9.   
  10. /** 
  11.  * @author wuwenhai 
  12.  * @since JDK1.6 
  13.  * @history 2017-6-3 wuwenhai 新建 
  14.  * 握手拦截器,在连接时做一些事情 
  15.  */  
  16. public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{    
  17.     @Override    
  18.     public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> attributes) throws Exception {    
  19.         //attributes是session里面的所有属性的map表示  
  20.         attributes.put("user", getRandomNickName());  
  21.         return super.beforeHandshake(request, response, handler, attributes);    
  22.     }   
  23.       
  24.     @Override    
  25.     public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {    
  26.         super.afterHandshake(request, response, wsHandler, ex);    
  27.     }          
  28.       
  29.     //给每个进来的人(session)随机分配个昵称,这里没做控制,所以聊天室内的昵称可能发生重复  
  30.     public String getRandomNickName(){  
  31.         String[] nickNameArray={"Captain America","Deadpool","Hawkeye","Hulk","Iron Man","Spider Man","Thor","Wolverine","Black Panther","Colossus"};  
  32.         Random random=new Random();  
  33.         return nickNameArray[random.nextInt(10)];  
  34.     }  
  35. }    


ChatRoom.java

[html]  view plain  copy
  1. package com.websocket;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.nio.ByteBuffer;  
  8. import java.text.SimpleDateFormat;  
  9. import java.util.ArrayList;  
  10. import java.util.Collections;  
  11. import java.util.Date;  
  12. import java.util.List;  
  13. import org.apache.log4j.Logger;  
  14. import org.springframework.web.socket.BinaryMessage;  
  15. import org.springframework.web.socket.CloseStatus;  
  16. import org.springframework.web.socket.TextMessage;  
  17. import org.springframework.web.socket.WebSocketSession;  
  18. import org.springframework.web.socket.handler.AbstractWebSocketHandler;  
[html]  view plain  copy
  1. /**  
  2.  * @author wuwenhai  
  3.  * @since JDK1.6  
  4.  * @history 2017-6-3 wuwenhai 新建  
  5.  * 聊天室代码  
  6.  */  
  7. public class ChatRoom extends AbstractWebSocketHandler{  
  8.   
  9.     public final static List<WebSocketSession> sessionList = Collections.synchronizedList(new ArrayList<WebSocketSession>());  
  10.     SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");  
  11.     Logger logger = Logger.getLogger(this.getClass());  
  12.     FileOutputStream output;  
  13.     @Override    
  14.     public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {    
  15.         System.out.println("Connection established..."+webSocketSession.getRemoteAddress());    
  16.         System.out.println(webSocketSession.getAttributes().get("user")+" Login");  
  17.         webSocketSession.sendMessage(new TextMessage("I'm "+(webSocketSession.getAttributes().get("user"))));  
  18.         sessionList.add(webSocketSession);  
  19.     }    
  20.       
  21.     @Override    
  22.     public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) throws Exception {    
  23.         System.out.println("Connection closed..."+webSocketSession.getRemoteAddress()+" "+status);   
  24.         System.out.println(webSocketSession.getAttributes().get("user")+" Logout");  
  25.         sessionList.remove(webSocketSession);  
  26.     }  
  27.       
  28.     @Override  
  29.     public void handleTextMessage(WebSocketSession websocketsession, TextMessage message)  
  30.     {  
  31.         String payload=message.getPayload();  
  32.         String textString;  
  33.         try {  
  34.             if(payload.endsWith(":fileStart")){  
  35.                 output=new FileOutputStream(new File("D:\\images\\"+payload.split(":")[0]));  
  36.             }else if(payload.endsWith(":fileFinishSingle")){  
  37.                 output.close();  
  38.                 String fileName=payload.split(":")[0];  
  39.                 for(WebSocketSession session:sessionList){  
  40.                     if(session.getId().equals(websocketsession.getId())){  
  41.                         textString=" I ("+format.format(new Date())+")<br>";  
  42.                     }else{  
  43.                         textString=websocketsession.getAttributes().get("user")+" ("+format.format(new Date())+")<br>";  
  44.                     }  
  45.                     TextMessage textMessage = new TextMessage(textString);   
  46.                     session.sendMessage(textMessage);   
  47.                     sendPicture(session,fileName);  
  48.                 }  
  49.             }else if(payload.endsWith(":fileFinishWithText")){  
  50.                 output.close();  
  51.                 String fileName=payload.split(":")[0];  
  52.                 for(WebSocketSession session:sessionList){  
  53.                     sendPicture(session,fileName);  
  54.                 }  
  55.             }else{  
  56.                 for(WebSocketSession session: sessionList){  
  57.                     if(session.getId().equals(websocketsession.getId())){  
  58.                         textString=" I ("+format.format(new Date())+")<br>"+payload;  
  59.                     }else{  
  60.                         textString=websocketsession.getAttributes().get("user")+" ("+format.format(new Date())+")<br>"+payload;  
  61.                     }  
  62.                     TextMessage textMessage = new TextMessage(textString);   
  63.                     session.sendMessage(textMessage);   
  64.                 }  
  65.             }  
  66.         } catch (IOException e) {  
  67.             e.printStackTrace();  
  68.         }  
  69.     }  
  70.     
  71.     @Override  
  72.     public void handleBinaryMessage(WebSocketSession session, BinaryMessage message)  
  73.     {  
  74.         ByteBuffer buffermessage.getPayload();  
  75.         try {  
  76.             output.write(buffer.array());  
  77.         } catch (IOException e) {  
  78.             e.printStackTrace();  
  79.         }  
  80.     }  
  81.       
  82.     @Override    
  83.     public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {    
  84.         if(webSocketSession.isOpen()){    
  85.             webSocketSession.close();    
  86.         }    
  87.         System.out.println(throwable.toString());    
  88.         System.out.println("WS connection error,close..."+webSocketSession.getRemoteAddress());    
  89.     }    
  90.     
  91.     @Override    
  92.     public boolean supportsPartialMessages() {    
  93.         return true;    
  94.     }    
  95.         
  96.     public void sendPicture(WebSocketSession session,String fileName){  
  97.         FileInputStream input;  
  98.         try {  
  99.             File file=new File("D:\\images\\"+fileName);  
  100.             input = new FileInputStream(file);  
  101.             byte bytes[] = new byte[(int) file.length()];   
  102.             input.read(bytes);  
  103.             BinaryMessage byteMessage=new BinaryMessage(bytes);  
  104.             session.sendMessage(byteMessage);  
  105.             input.close();  
  106.         } catch (Exception e) {  
  107.             e.printStackTrace();  
  108.         }  
  109.     }  
  110. }  


chatRoom.jsp

[html]  view plain  copy
  1. <!DOCTYPE html>    
  2. <html>    
  3. <head>    
  4. <meta charset="UTF-8">    
  5. <title>Insert title here</title>    
  6. <script type="text/javascript" src="js/sockjs.min.js"></script>    
  7. <script type="text/javascript" src="js/jquery.js"></script>    
  8. <script type="text/javascript">    
  9.     var url = "127.0.0.1:8080/websocket";    
  10.     var websocket = null;    
  11.     if ('WebSocket' in window) {    
  12.         websocket = new WebSocket("ws://" + url + "/chat.sc");    
  13.     } else {    
  14.         websocket = new SockJS("http://" + url + "/sockjs/chat.sc");    
  15.     }    
  16.     websocket.onopen = onOpen;    
  17.     websocket.onmessage = onMessage;    
  18.     websocket.onerror = onError;    
  19.     websocket.onclose = onClose;    
  20.     
  21.     function onOpen(openEvent) {    
  22.         document.getElementById("plane").innerHTML = document.getElementById("plane").innerHTML+ "OPEN<br/>";   
  23.     }    
  24.     
  25.     function onMessage(event) {    
  26.         if(typeof event.data =='string'){  
  27.             var element = document.createElement("p");  
  28.             element.innerHTML=event.data;  
  29.             document.getElementById("plane").appendChild(element);  
  30.         }else{  
  31.             var reader = new FileReader();  
  32.             reader.onload=function(eve){  
  33.                  if(eve.target.readyState==FileReader.DONE)  
  34.                  {  
  35.                     var img = document.createElement("img");  
  36.                     img.src=this.result;  
  37.                     document.getElementById("plane").appendChild(img);  
  38.                  }  
  39.              };  
  40.              reader.readAsDataURL(event.data);  
  41.         }  
  42.     }    
  43.     function onError() {    
  44.     }    
  45.     function onClose(event) {    
  46.         console.log(event.reason)  
  47.         document.getElementById("plane").innerHTML = document.getElementById("plane").innerHTML+ "CLOSE<br/>";    
  48.     }    
  49.     
  50.     function doSend() {    
  51.         if (websocket.readyState == 1) {  //0-CONNECTING;1-OPEN;2-CLOSING;3-CLOSED  
  52.             var msg = document.getElementById("message").value;  
  53.             if(msg){  
  54.                 websocket.send(msg);   
  55.             }  
  56.             sendFile(msg);  
  57.             document.getElementById("message").value="";  
  58.         } else {    
  59.             alert("connect fail!");    
  60.         }    
  61.     }    
  62.         
  63.     function sendFile(isWithText){  
  64.         var inputElement = document.getElementById("file");  
  65.         var fileList = inputElement.files;  
  66.         var file=fileList[0];  
  67.         if(!file) return;  
  68.         websocket.send(file.name+":fileStart");  
  69.         var reader = new FileReader();  
  70.         //以二进制形式读取文件  
  71.         reader.readAsArrayBuffer(file);  
  72.         //文件读取完毕后该函数响应  
  73.         reader.onload = function loaded(evt) {  
  74.             var blob = evt.target.result;  
  75.             //发送二进制表示的文件  
  76.             websocket.send(blob);  
  77.             if(isWithText){  
  78.                 websocket.send(file.name+":fileFinishWithText");  
  79.             }else{  
  80.                 websocket.send(file.name+":fileFinishSingle");  
  81.             }  
  82.             console.log("finnish");  
  83.         }  
  84.         inputElement.outerHTML=inputElement.outerHTML; //清空<input type="file">的值  
  85.     }  
  86.       
  87.     function disconnect(){    
  88.         if (websocket != null) {    
  89.             websocket.close();    
  90.             websocket = null;    
  91.         }    
  92.     }    
  93.         
  94.     function reconnect(){    
  95.         if (websocket != null) {    
  96.             websocket.close();    
  97.             websocket = null;    
  98.         }    
  99.         if ('WebSocket' in window) {    
  100.             websocket = new WebSocket("ws://" + url + "/chat.sc");    
  101.         } else {    
  102.             websocket = new SockJS("http://" + url + "/sockjs/chat.sc");    
  103.         }    
  104.         websocket.onopen = onOpen;    
  105.         websocket.onmessage = onMessage;    
  106.         websocket.onerror = onError;    
  107.         websocket.onclose = onClose;    
  108.     }    
  109.     function clean(){  
  110.         document.getElementById("plane").innerHTML = "";    
  111.     }  
  112. </script>    
  113. </head>    
  114. <body>    
  115.     <div>    
  116.         <button id="disconnect" onclick="disconnect()">disconnect</button>    
  117.         <button id="send" onclick="doSend()">send</button>    
  118.         <button id="reconnect" onclick="reconnect()">reconnect</button>    
  119.         <button id="clean" onclick="clean()">clean</button>   
  120.         <!-- <br>  
  121.         <input type="button" value="sendFile" onclick="sendFile()"/> -->  
  122.         <input type="file" id="file" />  
  123.     </div>    
  124.     <div>    
  125.        <input id="message"  type="text" style="width: 350px"></input>    
  126.     </div>    
  127.     <div id="plane"></div>    
  128. </body>    
  129. </html>    


部署成功后,访问http://localhost:8080/websocket/view/chatRoom.jsp 可以得到效果如下图所示,分别是聊天室内3个人的聊天页面:


 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命无须向死而生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值