一、Socket.IO介绍
-
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端:
-
- JavaScript (whose documentation can be found here on this website)
- Java: github.com/mrniko/nett…
- Java: github.com/trinopoty/s…
- Python: github.com/miguelgrinb…
- Golang: github.com/googollee/g…
Client端:
-
- JavaScript (which can be run either in the browser, in Node.js or in React Native)
- Java: github.com/socketio/so…
- C++: github.com/socketio/so…
- Swift: github.com/socketio/so…
- Dart: github.com/rikulo/sock…
- Python: github.com/miguelgrinb…
- .Net: github.com/doghappy/so…
- Rust: github.com/1c3t3a/rust…
- Kotlin: github.com/icerockdev/…
二、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的优势
- SocketIO自动重连的特性让前端代码实现起来非常简单,无需关注连接状态,是否断线,是否需要重连。
- 在支持websocket连接的同时也支持http轮询,更大的保障了连接的可靠性,同样无需做额外的编码。
- Namespace特性以及广播特性,能够丰富业务实现的方式。