因为上一篇文章涉及此库,来学习一下。
go-socket.io 是一个在 Golang 中实现 Socket.IO 的库,它是一个实时应用程序框架。
通过websocket,实现端到端的通信,服务器只需要保存连接信息即可。这大大减小了服务器的压力。
示例代码看一下:
package main
import (
"log"
"net/http"
socketio "github.com/googollee/go-socket.io"
"github.com/googollee/go-socket.io/engineio"
"github.com/googollee/go-socket.io/engineio/transport"
"github.com/googollee/go-socket.io/engineio/transport/polling"
"github.com/googollee/go-socket.io/engineio/transport/websocket"
)
// Easier to get running with CORS. Thanks for help @Vindexus and @erkie
var allowOriginFunc = func(r *http.Request) bool {
return true
}
func main() {
server := socketio.NewServer(&engineio.Options{
Transports: []transport.Transport{
&polling.Transport{
CheckOrigin: allowOriginFunc,
},
&websocket.Transport{
CheckOrigin: allowOriginFunc,
},
},
})
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", "获取到信息 "+msg) // 触发reply事件
})
server.OnEvent("/", "notice", func(s socketio.Conn, msg string) string {
s.SetContext(msg)
return "接收 " + msg
})
// /chat 命名空间(namespace),可以理解为路由或端口,前端向此空间收发/处理信息
// msg 事件名称
server.OnEvent("/chat", "msg", func(s socketio.Conn, msg string) string {
s.SetContext(msg)
return "接收 " + 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()
http.Handle("/socket.io/", server)
http.Handle("/", http.FileServer(http.Dir("../asset")))
log.Println("Serving at localhost:8000...")
log.Fatal(http.ListenAndServe(":8000", nil))
}
前端
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
// 建立了两个处理路径A/B
var socket = io();
var s2 = io("/chat");
// A处理服务器的reply事件
socket.on('reply', function(msg){
$('#messages').append($('<li>').text(msg));
});
// 当点击按钮后,触发B的msg事件。当有返回信息,添加到消息框
$('form').submit(function(){
s2.emit('msg', $('#m').val(), function(data){
$('#messages').append($('<li>').text('ACK CALLBACK: ' + data));
});
// 触发A的notice事件
socket.emit('notice', $('#m').val());
$('#m').val('');
return false;
});
</script>
</body>
</html>
在理解的过程中,对其进行了修改
前端其实只需要一个处理端口,示例或许只是为了展示方法。
// 直接在一个处理端口上获取返回的信息,并显示在页面上。
socket.emit('notice', $('#m').val(), function(data){
$('#messages').append($('<li>').text('服务器反馈: ' + data));
});
后端
// 两种不同的信息反馈方式:服务端触发事件,或者直接返回信息
// 方法1
server.OnEvent("/", "notice", func(s socketio.Conn, msg string) {
log.Println("notice:", msg)
s.Emit("reply", "获取到信息 "+msg) // 触发reply事件,然后在前端处理服务器的reply事件。
})
// 方法2
server.OnEvent("/", "notice", func(s socketio.Conn, msg string) string {
s.SetContext(msg)
return "接收 " + msg
})
这个示例仅演示了客户端与服务端的Socket连接。
实际使用中,我往往会使用gin框架
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
socketio "github.com/googollee/go-socket.io"
)
func main() {
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()
// 把这个路由的GET和POST全权交给server
router := gin.New()
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)
}
}
关于Gin和WebSocket跨域的问题是需要处理的。
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
socketio "github.com/googollee/go-socket.io"
)
func GinMiddleware(allowOrigin string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Request.Header.Del("Origin")
c.Next()
}
}
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, msg string) {
log.Println("closed", msg)
})
go func() {
if err := server.Serve(); err != nil {
log.Fatalf("socketio listen error: %s\n", err)
}
}()
defer server.Close()
router.Use(GinMiddleware("http://localhost:3000"))
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)
}
}