简版在线聊天Websocket

序言

  • What is Webscoket ?
  • websocket 应用场景
  • 简版群聊实现
  • 代码例子
  • 小结

Webscoket

Websokcet 是一种单个TCP连接上进行全双工通信的协议,通过HTTP/1.1 协议的101状态码进行握手。

http://websocket.org

Websocket 应用场景

Websocket 和 http 协议都是web通讯协议,两者有何区别?先说Http,它是一种请求响应协议,这种模型决定了,只能客户端请求,服务端被动回答。如果我们有服务端主动推送给客户端的需求怎么办?比如一个股票网站,我们会选择主动轮询,也就是”拉模式“。

大家可以思考下主动轮询带来的问题是什么?

主动轮询其实会产生大量无效请求,增加了服务器压力。

由此,websocket 协议的补充,为我们带来了新的解决思路。

简版群聊实现

利用Websocket 实现一个简陋群聊功能,加深一下Websocket 理解。

  1. 假设李雷和韩梅梅都登录在线;
  2. 李雷通过浏览器发送消息转nginx 代理到Ws服务器;
  3. Ws服务器加载所有在线会话广播消息;
  4. 韩梅梅接受到消息。

代码例子

后端(shop-server)

引入pom.xml 依赖

  
      org.springframework.boot
      spring-boot-starter-websocket
  

配置类

package com.onlythinking.shop.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 

The describe

* * @author Li Xingping */ @Slf4j @Configuration public class WebSocketConfiguration { @Bean public ServerEndpointExporter endpointExporter() { return new ServerEndpointExporter(); } }

接受请求端点

package com.onlythinking.shop.websocket;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.onlythinking.shop.websocket.handler.ChatWsHandler;
import com.onlythinking.shop.websocket.handler.KfWsHandler;
import com.onlythinking.shop.websocket.handler.WsHandler;
import com.onlythinking.shop.websocket.store.WsReqPayLoad;
import com.onlythinking.shop.websocket.store.WsRespPayLoad;
import com.onlythinking.shop.websocket.store.WsStore;
import com.onlythinking.shop.websocket.store.WsUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;

/**
 * 

The describe

* * @author Li Xingping */ @Slf4j @Component @ServerEndpoint("/ws") public class WebsocketServerEndpoint { private static Map wsHandler = Maps.newConcurrentMap(); static { wsHandler.put("robot", new KfWsHandler()); wsHandler.put("chat", new ChatWsHandler()); } @OnOpen public void onOpen(Session session) { log.info("New ws connection {} ", session.getId()); WsStore.put(session.getId(), WsUser.builder().id(session.getId()).session(session).build()); respMsg(session, WsRespPayLoad.ok().toJson()); } @OnClose public void onClose(Session session, CloseReason closeReason) { WsStore.remove(session.getId()); log.warn("ws closed,reason:{}", closeReason); } @OnMessage public void onMessage(String message, Session session) { log.info("accept client messages: {}" + message); WsReqPayLoad payLoad = JSON.parseObject(message, WsReqPayLoad.class); if (StringUtils.isBlank(payLoad.getType())) { respMsg(session, WsRespPayLoad.ofError("Type is null.").toJson()); return; } WsUser wsUser = WsStore.get(session.getId()); if (null == wsUser || StringUtils.isBlank(wsUser.getUsername())) { WsStore.put(session.getId(), WsUser.builder() .id(session.getId()) .username(payLoad.getUsername()) .avatar(payLoad.getAvatar()) .session(session) .build() ); } WsHandler handler = wsHandler.get(payLoad.getType()); if (null != handler) { WsRespPayLoad resp = handler.onMessage(session, payLoad); if (null != resp) { respMsg(session, resp.toJson()); } } else { respMsg(session, WsRespPayLoad.ok().toJson()); } } @OnError public void onError(Session session, Throwable e) { WsStore.remove(session.getId()); log.error("WS Error: ", e); } private void respMsg(Session session, String content) { try { session.getBasicRemote().sendText(content); } catch (IOException e) { log.error("Ws resp msg error {} {}", content, e); } } }

聊天业务处理器

package com.onlythinking.shop.websocket.handler;

import com.onlythinking.shop.websocket.store.*;
import lombok.extern.slf4j.Slf4j;

import javax.websocket.Session;
import java.util.Date;
import java.util.List;

/**
 * 

The describe

* * @author Li Xingping */ @Slf4j public class ChatWsHandler implements WsHandler { @Override public WsRespPayLoad onMessage(Session session, WsReqPayLoad payLoad) { // 广播消息 List allSessions = WsStore.getAll(); for (WsUser s : allSessions) { WsRespPayLoad resp = WsRespPayLoad.builder() .data( WsChatResp.builder() .username(payLoad.getUsername()) .avatar(payLoad.getAvatar()) .msg(payLoad.getData()) .createdTime(new Date()) .self(s.getId().equals(session.getId())) .build() ) .build(); log.info("Broadcast message {} {} ", s.getId(), s.getUsername()); s.getSession().getAsyncRemote().sendText(resp.toJson()); } return null; } }

前端(shop-web-mgt)

引入依赖

npm install vue-native-websocket --save

添加Store

import Vue from 'vue'

const ws = {
  state: {
    wsData: {
      hasNewMsg: false,
    },
    socket: {
      isConnected: false,
      message: '',
      reconnectError: false,
    }
  },
  mutations: {
    SET_WSDATA(state, data) {
      state.wsData.hasNewMsg = data.hasNewMsg
    },
    RESET_WSDATA(state, data) {
      state.wsData.hasNewMsg = false
    },
    SOCKET_ONOPEN(state, event) {
      Vue.prototype.$socket = event.currentTarget;
      state.socket.isConnected = true
    },
    SOCKET_ONCLOSE(state, event) {
      state.socket.isConnected = false
    },
    SOCKET_ONERROR(state, event) {
      console.error(state, event)
    },
    // default handler called for all methods
    SOCKET_ONMESSAGE(state, message) {
      state.socket.message = message
    },
    // mutations for reconnect methods
    SOCKET_RECONNECT(state, count) {
      console.info(state, count)
    },
    SOCKET_RECONNECT_ERROR(state) {
      state.socket.reconnectError = true;
    },
  },
  actions: {
    AskRobot({rootGetters}, data) {
      return new Promise((resolve, reject) => {
        console.log('Ask robot msg', data);
        const payLoad = {
          type: 'robot',
          username: rootGetters.loginName,
          data: data
        };
        Vue.prototype.$socket.sendObj(payLoad)
        resolve(1)
      })
    },
    SendChatMsg({rootGetters}, data) {
      return new Promise((resolve, reject) => {
        console.log('Send chat msg', data);
        const payLoad = {
          type: 'chat',
          username: rootGetters.loginName,
          data: data
        };
        Vue.prototype.$socket.sendObj(payLoad)
        resolve(1)
      })
    },
    MessageRead({commit, state}, data) {
      commit('RESET_WSDATA', {})
    },
  }
};

export default ws

编写组件








Nginx 代理配置 nginx.conf (如有需要可添加)

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
    server 127.0.0.1:8300;
}

server {
     server_name shop-web-mgt.onlythinking.com;
     listen 443 ssl;
     location / {
         proxy_pass http://websocket;
         proxy_read_timeout 300s;
         proxy_send_timeout 300s;
         
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
     }
    ssl_certificate /etc/data/shop-web-mgt.onlythinking.com/full.pem;
    ssl_certificate_key /etc/data/shop-web-mgt.onlythinking.com/privkey.pem;
}

实现效果图

界面比较丑,因为不太擅长,请大家别见笑!!

简版在线聊天Websocket_第1张图片

简版在线聊天Websocket_第2张图片

简版在线聊天Websocket_第3张图片

项目地址

https://github.com/cuteJ/shop... (后端)

https://github.com/cuteJ/shop... (前端)

项目演示地址

http://shop-web-mgt.onlythink...

小结

该篇学习Websocket,写此Demo加深印象!

你可能感兴趣的:(websocket,前端,node.js)