1. 封装websocket

1.1.1. 协议

websocket是个二进制协议,需要先通过Http协议进行握手,从而协商完成从Http协议向websocket协议的转换。一旦握手结束,当前的TCP连接后续将采用二进制websocket协议进行双向双工交互,自此与Http协议无关。

可以通过这篇知乎了解一下websocket协议的基本原理:《WebSocket 是什么原理?为什么可以实现持久连接?》

下面代码是封装的websocket

目录结构:

-impl

—connection.go

-main.go

-client.html

connection.go文件代码

  1. package impl
  2. import (
  3. "errors"
  4. "sync"
  5. "github.com/gorilla/websocket"
  6. )
  7. type Connection struct {
  8. wsConn *websocket.Conn
  9. //读取websocket的channel
  10. inChan chan []byte
  11. //给websocket写消息的channel
  12. outChan chan []byte
  13. closeChan chan byte
  14. mutex sync.Mutex
  15. //closeChan 状态
  16. isClosed bool
  17. }
  18. //初始化长连接
  19. func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
  20. conn = &Connection{
  21. wsConn: wsConn,
  22. inChan: make(chan []byte, 1000),
  23. outChan: make(chan []byte, 1000),
  24. closeChan: make(chan byte, 1),
  25. }
  26. //启动读协程
  27. go conn.readLoop()
  28. //启动写协程
  29. go conn.writeLoop()
  30. return
  31. }
  32. //读取websocket消息
  33. func (conn *Connection) ReadMessage() (data []byte, err error) {
  34. select {
  35. case data = <-conn.inChan:
  36. case <-conn.closeChan:
  37. err = errors.New("connection is closed")
  38. }
  39. return
  40. }
  41. //发送消息到websocket
  42. func (conn *Connection) WriteMessage(data []byte) (err error) {
  43. select {
  44. case conn.outChan <- data:
  45. case <-conn.closeChan:
  46. err = errors.New("connection is closed")
  47. }
  48. return
  49. }
  50. //关闭连接
  51. func (conn *Connection) Close() {
  52. //线程安全的Close,可重入
  53. conn.wsConn.Close()
  54. //只执行一次
  55. conn.mutex.Lock()
  56. if !conn.isClosed {
  57. close(conn.closeChan)
  58. conn.isClosed = true
  59. }
  60. conn.mutex.Unlock()
  61. }
  62. func (conn *Connection) readLoop() {
  63. var (
  64. data []byte
  65. err error
  66. )
  67. for {
  68. if _, data, err = conn.wsConn.ReadMessage(); err != nil {
  69. goto ERR
  70. }
  71. //如果数据量过大阻塞在这里,等待inChan有空闲的位置!
  72. select {
  73. case conn.inChan <- data:
  74. case <-conn.closeChan:
  75. //closeChan关闭的时候
  76. goto ERR
  77. }
  78. }
  79. ERR:
  80. conn.Close()
  81. }
  82. func (conn *Connection) writeLoop() {
  83. var (
  84. data []byte
  85. err error
  86. )
  87. for {
  88. select {
  89. case data = <-conn.outChan:
  90. case <-conn.closeChan:
  91. goto ERR
  92. }
  93. if err = conn.wsConn.WriteMessage(websocket.TextMessage, data); err != nil {
  94. goto ERR
  95. }
  96. }
  97. ERR:
  98. conn.Close()
  99. }

main.go文件代码

  1. package main
  2. import (
  3. "net/http"
  4. "time"
  5. "github.com/gorilla/websocket"
  6. "github.com/student/1330/impl"
  7. )
  8. var (
  9. upgrade = websocket.Upgrader{
  10. //允许跨域
  11. CheckOrigin: func(r *http.Request) bool {
  12. return true
  13. },
  14. }
  15. )
  16. func wsHandler(w http.ResponseWriter, r *http.Request) {
  17. var (
  18. //websocket 长连接
  19. wsConn *websocket.Conn
  20. err error
  21. conn *impl.Connection
  22. data []byte
  23. )
  24. //header中添加Upgrade:websocket
  25. if wsConn, err = upgrade.Upgrade(w, r, nil); err != nil {
  26. return
  27. }
  28. if conn, err = impl.InitConnection(wsConn); err != nil {
  29. goto ERR
  30. }
  31. go func() {
  32. var (
  33. err error
  34. )
  35. for {
  36. if err = conn.WriteMessage([]byte("heartbeat")); err != nil {
  37. return
  38. }
  39. time.Sleep(time.Second * 1)
  40. }
  41. }()
  42. for {
  43. if data, err = conn.ReadMessage(); err != nil {
  44. goto ERR
  45. }
  46. if err = conn.WriteMessage(data); err != nil {
  47. goto ERR
  48. }
  49. }
  50. ERR:
  51. conn.Close()
  52. }
  53. func main() {
  54. //http标准库
  55. http.HandleFunc("/ws", wsHandler)
  56. http.ListenAndServe("0.0.0.0:5555", nil)
  57. }

client.html文件代码

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <script>
  6. window.addEventListener("load", function(evt) {
  7. var output = document.getElementById("output");
  8. var input = document.getElementById("input");
  9. var ws;
  10. var print = function(message) {
  11. var d = document.createElement("div");
  12. d.innerHTML = message;
  13. output.appendChild(d);
  14. };
  15. document.getElementById("open").onclick = function(evt) {
  16. if (ws) {
  17. return false;
  18. }
  19. ws = new WebSocket("ws://localhost:5555/ws");
  20. ws.onopen = function(evt) {
  21. print("OPEN");
  22. }
  23. ws.onclose = function(evt) {
  24. print("CLOSE");
  25. ws = null;
  26. }
  27. ws.onmessage = function(evt) {
  28. print("RESPONSE: " + evt.data);
  29. }
  30. ws.onerror = function(evt) {
  31. print("ERROR: " + evt.data);
  32. }
  33. return false;
  34. };
  35. document.getElementById("send").onclick = function(evt) {
  36. if (!ws) {
  37. return false;
  38. }
  39. print("SEND: " + input.value);
  40. ws.send(input.value);
  41. return false;
  42. };
  43. document.getElementById("close").onclick = function(evt) {
  44. if (!ws) {
  45. return false;
  46. }
  47. ws.close();
  48. return false;
  49. };
  50. });
  51. </script>
  52. </head>
  53. <body>
  54. <table>
  55. <tr><td valign="top" width="50%">
  56. <p>Click "Open" to create a connection to the server,
  57. "Send" to send a message to the server and "Close" to close the connection.
  58. You can change the message and send multiple times.
  59. </p>
  60. <form>
  61. <button id="open">Open</button>
  62. <button id="close">Close</button>
  63. <input id="input" type="text" value="Hello world!">
  64. <button id="send">Send</button>
  65. </form>
  66. </td><td valign="top" width="50%">
  67. <div id="output"></div>
  68. </td></tr></table>
  69. </body>
  70. </html>