一个TCP长连接设备管理后台工程(二)

qiuzhiqian · · 567 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

后端模型

graph BT
A(终端A) --> TCPServer
B(终端B) --> TCPServer
C(终端C) --> TCPServer
TCPServer --> Postgresql
Postgresql --> HTTPServer
HTTPServer --> D(ClientA)
HTTPServer --> E(ClientB)
HTTPServer --> F(ClientC)

后端需要设计两个服务器,一个TCP,一个HTTP。TCP主要处理与终端的长连接交互,一个TCP连接对应一台终端设备,终端设备唯一标识使用IMEI。HTTP处理与前端的交互,前端需要获取所有可用的终端设备列表,向指定的终端发送命令。所以,为了方便从ip找到对应终端,然后从对应终端找到对应的conn,我们就需要维护一个map:

type Terminal struct {
    authkey   string
    imei      string
    iccid     string
    vin       string
    tboxver   string
    loginTime time.Time
    seqNum    uint16
    phoneNum  string
    Conn      net.Conn
}

var connManger map[string]*Terminal

至于为什么要定义成指针的形式,是因为定义成指针后我们可以直接修改map中元素结构体中对应的变量,而不需要重新定义一个元素再赋值。

var connManager map[string]*Terminal
connManager = make(map[string]*Terminal)
connManager["127.0.0.1:11000"]=&Terminal{}
connManager["127.0.0.1:11001"]=&Terminal{}

...

//此处能够轻松的修改对应的phoneNum修改
connManager["127.0.0.1:11001"].phoneNum = "13000000000"

相反,下面的这段代码修改起来就要繁琐不少:

var connManager map[string]Terminal
connManager = make(map[string]Terminal)
connManager["127.0.0.1:11000"]=Terminal{}
connManager["127.0.0.1:11001"]=Terminal{}

...
//此处会报错
connManager["127.0.0.1:11001"].phoneNum = "13000000000"

//此处修改需要定义一个临时变量,类似于读改写的模式
term,ok:=connManager["127.0.0.1:11001"]
term.phoneNum = "13000000000"
connManager["127.0.0.1:11001"]=term

上面的代码一处会报错

cannot assign to struct field connManager["127.0.0.1:11001"].phoneNum in map

从上面的对比就可以看到,确实是定义成指针更加方便了。

TCP的长连接模型

TCP的长连接我们选择这样的一种方式:

  • 每个连接分配一个读Goroutine
  • 写数据按需分配

如果熟悉socket的话,就知道socket一个服务器创建的基本步骤:

  1. 创建socket
  2. listen
  3. accept

其中accept一般需要轮循调用。golang也基本是同样的流程。

一个简单的TCP服务器示例:

package main

import (
    "fmt"
    "net"
)

type Terminal struct {
    authkey  string
    imei     string
    iccid    string
    vin      string
    tboxver  string
    phoneNum string
    Conn     net.Conn
}

var connManager map[string]*Terminal

func recvConnMsg(conn net.Conn) {
    addr := conn.RemoteAddr()

    var term *Terminal = &Terminal{
        Conn: conn,
    }
    term.Conn = conn
    connManager[addr.String()] = term

    defer func() {
        delete(connManager, addr.String())
        conn.Close()
    }()

    for {
        tempbuf := make([]byte, 1024)
        n, err := conn.Read(tempbuf)

        if err != nil {
            return
        }

        fmt.Println("rcv:", tempbuf[:n])
    }
}

func TCPServer(addr string) {
    connManager = make(map[string]*Terminal)
    listenSock, err := net.Listen("tcp", addr)
    if err != nil {
        return
    }
    defer listenSock.Close()

    for {
        newConn, err := listenSock.Accept()
        if err != nil {
            continue
        }

        go recvConnMsg(newConn)
    }
}

func main() {
    TCPServer(":19903")
}

以下是用来测试的客户端代码:

package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    conn, err := net.Dial("tcp", ":19903")
    if err != nil {
        return
    }

    defer conn.Close()

    var n int = 0
    n, err = conn.Write([]byte("123456"))
    if err != nil {
        return
    }

    fmt.Println("len:", n)

    for {
        time.Sleep(time.Second * 3)
    }
}

测试结果:

$ ./server 
rcv: [49 50 51 52 53 54]

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:qiuzhiqian

查看原文:一个TCP长连接设备管理后台工程(二)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

567 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传