不是吧?还不会Socket.IO?

984 阅读3分钟

一、Socket.IO介绍

SocketIO官网

  • SocketIO是什么

Socket.IO是一个能够在客户端和服务器之间进行低延迟、双向和基于事件通信的库。

它建立在WebSocket协议之上,并提供额外的保证,如回落到HTTP长轮询或自动重新连接。

Socket.IO is a library that enables low-latency, bidirectional and event-based communication between a client and a server.

It is built on top of the WebSocket protocol and provides additional guarantees like fallback to HTTP long-polling or automatic reconnection.

  • SocketIO不是什么

Socket.IO不是WebSocket的一个实现。

Socket.IO虽然在可能的情况下使用WebSocket进行传输,但它会给每个数据包添加额外的元数据。WebSocket客户端无法成功连接到Socket.IO服务端,而Socket.IO客户端也无法连接到普通的WebSocket服务端。

  • SocketIO主流语言的实现情况:

Server端:
Client端:

二、SocketIO特性

1. HTTP long-polling fallback

2. Automatic reconnection

3. Packet buffering

4. Acknowledgements

5. Broadcasting

6. Multiplexing

三、SocketIO在Beast项目中的应用及实现

1. 前端实现(VUE Client端 使用vue-socekt.io)

const socket = io('', { transports: ['websocket'], secure: false })
Vue.use(new VueSocketIO({
  debug: process.env.NODE_ENV === 'development',
  connection: socket,
  vuex: {
    store,
    actionPrefix: 'SOCKET_',
    mutationPrefix: 'SOCKET_'
  }
}))

  sockets: {
    pod_group_info: function(data) {
      const group_id = this.group_id
      this.groupList = data.groups
      this.notSyncGroups = data.not_synced_groups
      for (let index = 0; index < this.groupList.length; index++) {
        const group = this.groupList[index]
        if (group.group_id === group_id) {
          this.currentRow = index
          break
        }
      }
    },
    pod_info: function(data) {
      this.podList = data
    }
  },



        this.sockets.subscribe('pod_group_info', function(data) {
          const group_id = this.group_id
          this.groupList = data.groups
          this.notSyncGroups = data.not_synced_groups
          for (let index = 0; index < this.groupList.length; index++) {
            const group = this.groupList[index]
            if (group.group_id === group_id) {
              this.currentRow = index
              break
            }
          }
        })
        this.sockets.subscribe('pod_info', function(data) {
          this.podList = data
        })

2. Python Server端实现(socketio)

socketio = SocketIO(logger=Config.DEBUG, engineio_logger=Config.DEBUG, cors_allowed_origins='*')


def create_socketio(app):

    from . import namespace_event
    socketio.on_namespace(namespace_event.BaseNamespace('/'))
    async_mode = 'eventlet'
    socketio.init_app(app, async_mode=async_mode)

    return app



from flask import current_app
from flask_socketio import Namespace, emit

z`
class BaseNamespace(Namespace, GrayscaleRelease, QueryMixin):
    step_name_list = ['版本编译', 'Sonar静态检查扫描', 'Cobra白盒安全扫描',
                      'Buffalo Java组件扫描', 'SIT', 'LPT', 'UAT', 'PRE', 'PRD']

    def on_assign_log_update(self, params):
        current_app.logger.info('assign log update ' + params['note'] + ' received')
        select_fields = ["created_user", "git_path", "build_summary", "status", "build_log"]
        version_id = params['version_id']
        redis_result = redis_store.get(f'socket_assign_log:{version_id}')
		pass
		#some code ...
        emit('assign_log', response, broadcast=True)

    def on_build_log_update(self, params):
        current_app.logger.info('build log update received')
        version_id = params["version_id"]
        redis_result = redis_store.get(f'socket_build_log:{version_id}')
        pass
		#some code ...
        emit('build_log', data)

    @parse_api_client
    def on_pod_log_update(self, params):
        tail_lines = params.get("tail_lines") or 100
        redis_result = redis_store.get(f'socket_pod_log:{params["pod_name"]}-{tail_lines}')
        pass
		#some code ...
        emit('pod_log', content)

    @staticmethod
    def on_release_log_update(params):
        current_app.logger.info('release log update received')
        current_app.logger.info(params)
        deploy_id = params.get("deploy_id")
        grayscale_deploy_id = params.get("grayscale_deploy_id")
        release_log_id = params.get("release_log_id")
        rollback_log_id = params.get("rollback_log_id")
        tail_lines = params["tail_lines"]
		pass
		#some code ...
        emit('release_log', content)

3. Python Client端实现(python-socketio)

def get_client(url, namespaces, path='socket.io'):
    client = SocketioClient(url, namespaces, path)
    return client


class SocketioClient:
    def __init__(self, url, namespaces, path='socket.io'):
        client = socketio.Client()

        @client.event
        def connect():
            logger.info("I'm connected!")

        @client.event
        def connect_error(data):
            logger.info("The connection failed!")

        @client.event
        def disconnect():
            logger.info("I'm disconnected!")
        try:
            client.connect(url, transports='websocket', namespaces=namespaces, socketio_path=path)
        except exceptions.SocketIOError as e:
            logger.error('连接异常')
            logger.error(e)
        self.client = client

    def send_message(self, event_name, message, namespace='/'):
        try:
            self.client.emit(event_name, data=message, namespace=namespace)
        except exceptions.SocketIOError as e:
            logger.error('消息发送异常')
            logger.error(e)

    def disconnect(self):
        self.client.disconnect()
        logger.info("I'm disconnected")

4. Golang Server端实现(go-socket.io)

package main

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	socketio "github.com/googollee/go-socket.io"
)

func main() {
	router := gin.New()

	server := socketio.NewServer(nil)

	server.OnConnect("/", func(s socketio.Conn) error {
		s.SetContext("")
		log.Println("connected:", s.ID())
		return nil
	})

	server.OnEvent("/", "notice", func(s socketio.Conn, msg string) {
		log.Println("notice:", msg)
		s.Emit("reply", "have "+msg)
	})

	server.OnEvent("/chat", "msg", func(s socketio.Conn, msg string) string {
		s.SetContext(msg)
		return "recv " + msg
	})

	server.OnEvent("/", "bye", func(s socketio.Conn) string {
		last := s.Context().(string)
		s.Emit("bye", last)
		s.Close()
		return last
	})

	server.OnError("/", func(s socketio.Conn, e error) {
		log.Println("meet error:", e)
	})

	server.OnDisconnect("/", func(s socketio.Conn, reason string) {
		log.Println("closed", reason)
	})

	go func() {
		if err := server.Serve(); err != nil {
			log.Fatalf("socketio listen error: %s\n", err)
		}
	}()
	defer server.Close()

	router.GET("/socket.io/*any", gin.WrapH(server))
	router.POST("/socket.io/*any", gin.WrapH(server))
	router.StaticFS("/public", http.Dir("../asset"))

	if err := router.Run(":8000"); err != nil {
		log.Fatal("failed run app: ", err)
	}
}

5. SocketIO相比ws的优势

  1. SocketIO自动重连的特性让前端代码实现起来非常简单,无需关注连接状态,是否断线,是否需要重连。
  2. 在支持websocket连接的同时也支持http轮询,更大的保障了连接的可靠性,同样无需做额外的编码。
  3. Namespace特性以及广播特性,能够丰富业务实现的方式。

四、一些踩坑

1. Python gunicorn部署时worker-class需要使用eventlet

2. go-socketio支持的版本较低,POSTMAN调试时需要调低指定版本

3. vue-socketio缺乏对多namespace的支持

4. CORS的处理