基於WebSocket和SpringBoot的群聊天室


  • 引入

    • 普通請求-響應方式:例如Servlet中HttpServletRequest和HttpServletResponse相互配合先接受請求、解析數據,再發出響應,處理完成后連接便斷開了,沒有數據的實時性可言。
    • Ajax輪詢:客戶端定時發送多次Ajax請求,服務器不斷響應,時間頻率極小,雖然實時性有了卓越提高,但是大多數的請求是沒有意義的。
    • WebSocket長連接:客戶端只需要向服務器發送一次Http請求,與服務器建立一個以sessioId標示的channel,便可以與服務器在自己的管道中實時通訊,連接是不斷開的。在此介紹一個基於WebSocket的框架GoEasy,非常的方便簡單,大家可以用來實現消息推送、實時聊天等功能。

      

  • Demo介紹

    • 基於WebSocket的聊天室,可以發送接受消息並實時查看在線用戶。

      

  • Maven依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu</groupId>
    <artifactId>spring-boot-websocket-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-websocket-02</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ezmorph</groupId>
            <artifactId>ezmorph</artifactId>
            <version>1.0.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • WebSocket配置文件

    • 后台基於SpringBoot非常的方便,只要編寫配置類即可,注入的實體Bean為方法名。SpringBoot推薦Thymeleaf進行html渲染,但是老師說Thymeleaf相對於Jsp等其他渲染工具性能較差,咱也不知道為什么。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class MyWebSocketConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
       registry.addViewController("/login").setViewName("login");
    }
}
  • JavaScript

    • 客戶端通過js發送socket請求建立連接,連接成功后建立管道。通過onopen、onclose、onmessage等回調函數接收服務器響應的反饋。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>聊天頁面</title>
</head>
<body>
    <div class="container">
        <div class="left">
            <div class="top"></div>
            <div class="bottom">
                <div class="content">
                    <input type="text" name="content" id="content" value="輸入文本內容">
                </div>
                <input type="button" value="發送" id="send">
            </div>
        </div>
        <div class="right"></div>
    </div>
</body>
</html>
<script th:inline="javascript">
    window.onload=function(){
        var username=[[${username}]];
        if("WebSocket" in window){
            var webSocket=new WebSocket("ws://10.7.84.48:8080/mywebsocket?username="+username);
            //type 0上線 1下線 2聊天信息 3拉取在線用戶
            webSocket.onopen=function(e){
                var data='{"type":"0","username":"'+username+'","content":""}';
                webSocket.send(data);
            }
            window.onbeforeunload=function(){
                var data='{"type":"1","username":"'+username+'","content":""}';
                webSocket.send(data);
                webSocket.close();
            }
            document.getElementById("send").onclick=function (ev) {
                var content=document.getElementById("content").value;
                var data='{"type":"2","username":"'+username+'","content":"'+content+'"}';
                webSocket.send(data);
                document.getElementsByClassName("top")[0]
                    .innerHTML+="<p><font color='red'>我:&nbsp;</font><font color='#8a2be2'>"+content+"</font></p>";
                document.getElementById("content").value="";
                var scrollDiv = document.getElementsByClassName('top')[0];
                scrollDiv.scrollTop = scrollDiv.scrollHeight;
            }
            webSocket.onmessage=function (ev) {
                var data=ev.data;
                var obj=eval('('+data+')');
                var type=obj.type;
                switch (type) {
                    case 0:
                        document.getElementsByClassName("right")[0]
                            .innerHTML+="<p id="+obj.senSessionId+"><font color='blue'>"+obj.senName+"</font></p>";
                        break;
                    case 1:
                        var id=obj.senSessionId;
                        var parent=document.getElementsByClassName("right")[0];
                        var child=document.getElementById(id);
                        parent.removeChild(child);
                        break;
                    case 2:
                        document.getElementsByClassName("top")[0]
                            .innerHTML+="<p>"+obj.time.hours+":"+obj.time.minutes+":"+obj.time.seconds+"&nbsp;<font color='#4169e1'>"+obj.senName+":&nbsp;</font><font color='#8a2be2'>"+obj.content+"</font></p>";
                        var scrollDiv = document.getElementsByClassName('top')[0];
                        scrollDiv.scrollTop = scrollDiv.scrollHeight;
                        break;
                    case 3:
                        var map=obj.map;
                        for(var key in map){
                            document.getElementsByClassName("right")[0]
                                .innerHTML+="<p id="+key+"><font color='blue'>"+map[key]+"</font></p>";
                        }
                        break;
                    default:
                }

            }
        }
    }
</script>
<style>
    .container{
        width: 700px;
        height: 500px;
        border: 1px solid black;
        margin: 0px auto;
    }
    .left{
        width: 75%;
        height: 100%;
        float: left;
    }
    .right{
        width: 20%;
        height: 100%;
        float: left;
        border-left: 1px solid black;
    }
    .top{
        width: 100%;
        height: 75%;
        overflow-y: scroll;
    }
    .bottom{
        width: 100%;
        height: 25%;
        border-top: 1px solid black;
    }
    .content{
        width: 100%;
        height: 65%;
        border-bottom: 1px solid black;
    }
    #content{
        width: 99%;
        height: 92%;
    }
    #send{
        float: right;
        width: 70px;
        height: 42px;
    }
</style>
  • WebSocket服務端代碼

import com.atguigu.springbootwebsocket02.bean.Msg;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint("/mywebsocket")
public class WebSocketListener {
    private static int onlineCount=0;

    private static synchronized int getOnlineCount(){
        return onlineCount;
    }
    private static synchronized  int addOnlineCount(){
        return ++onlineCount;
    }
    private static synchronized int subOnlineCount(){
        return --onlineCount;
    }
    //用於區分每個WebSocket session.getId()
    private Session session;
    private String username;
    private static CopyOnWriteArraySet<WebSocketListener>webSocketListeners
            =new CopyOnWriteArraySet<>();
    @OnOpen
    public void onOpen(Session session){
        this.session=session;
        webSocketListeners.add(this);
        WebSocketListener.addOnlineCount();
        try {
            this.username= URLDecoder.decode(session.getQueryString().split("=")[1],"utf8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //獲取在線用戶信息
        try {
            Map<String,String>map=new HashMap<>();
            for(WebSocketListener webSocketListener
                    :webSocketListeners){
                if(!webSocketListener.session.getId().equals(this.session.getId())){
                    map.put(webSocketListener.session.getId(),webSocketListener.username);
                }
            }
            JSONObject jm=JSONObject.fromObject(map);
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("type",3);
            jsonObject.put("map",jm);
            this.session.getBasicRemote().sendText(jsonObject.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @OnClose
    public void onClose(Session session){
        webSocketListeners.remove(this);
        WebSocketListener.subOnlineCount();
    }
    private void broadcast(String data){
        for(WebSocketListener webSocketListener
            :webSocketListeners){
            try {
                if(!webSocketListener.session.getId().equals(this.session.getId())){
                    webSocketListener.session.getBasicRemote().sendText(data);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    @OnMessage
    public void onMessage(String data,Session session){
        JSONObject jsonObject=JSONObject.fromObject(data);
        Msg msg=new Msg();
        msg.setSenName(jsonObject.getString("username"));
        msg.setSenSessionId(session.getId());
        msg.setType(Integer.parseInt(jsonObject.getString("type")));
        msg.setTime(new Date());
        msg.setContent(jsonObject.getString("content"));
        JSONObject broadcast=JSONObject.fromObject(msg);
        broadcast(broadcast.toString());
    }
}
  • 其他代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登錄頁面</title>
</head>
<body>
    <form action="/enter" method="get">
        <table border="1px">
            <tr>
                <td>User:</td>
                <td><input type="text" name="username" value="輸入用戶名"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="登錄"/></td>
            </tr>
        </table>
    </form>
</body>
</html>
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MyWebSocketHandler {

    @RequestMapping(value = "/enter",method = RequestMethod.GET)
    public String enterChat(
            Model model,
            @RequestParam(name="username",required = false)String username){
        model.addAttribute("username",username);
        return "index";
    }
}
import java.util.Date;

public class Msg {
    private String senSessionId;
    private String senName;
    private String recSessionId="";
    private String recName="";
    private Date time=new Date();
    private String content;
    private Integer type;

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public String getSenSessionId() {
        return senSessionId;
    }

    public void setSenSessionId(String senSessionId) {
        this.senSessionId = senSessionId;
    }

    public String getSenName() {
        return senName;
    }

    public void setSenName(String senName) {
        this.senName = senName;
    }

    public String getRecSessionId() {
        return recSessionId;
    }

    public void setRecSessionId(String recSessionId) {
        this.recSessionId = recSessionId;
    }

    public String getRecName() {
        return recName;
    }

    public void setRecName(String recName) {
        this.recName = recName;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Msg{" +
                "senSessionId='" + senSessionId + '\'' +
                ", senName='" + senName + '\'' +
                ", recSessionId='" + recSessionId + '\'' +
                ", recName='" + recName + '\'' +
                ", time=" + time +
                ", content='" + content + '\'' +
                ", type=" + type +
                '}';
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM