如何访问所有连接的 tcp 客户端的变量?

我正在用 go 编写的宠物项目中设置一个 tcp 服务器。我希望能够维护所有连接的客户端的一部分,然后在新客户端连接到我的服务器或从我的服务器断开连接时修改它。


我现在的主要心理障碍是我是否应该声明一个包级切片,或者只是将一个切片传递给我的处理程序。


我的第一个想法是声明我的ClientList切片(我知道切片可能不是我最好的选择,但我决定暂时保留它)作为包级变量。虽然我认为这行得通,但我看到许多帖子不鼓励使用它们。


我的另一个想法是在我的主函数中声明ClientList为一个切片,然后我传递ClientList给我的HandleClient函数,所以每当客户端连接/断开连接时,我都可以调用AddClient或RemoveClient传递这个切片并添加/删除适当的客户端。


此实现如下所示。代码肯定还有其他问题,但我一直在努力思考一些看起来应该非常简单的事情。


type Client struct {

    Name string  

    Conn net.Conn

}


type ClientList []*Client


// Identify is used to set the name of the client

func (cl *Client) Identify() error {

// code here to set the client's name in the based on input from client

}


// This is not a threadsafe way to do this - need to use mutex/channels

func (cList *ClientList) AddClient(cl *Client) {

    *cList = append(*cList, cl)

}

func (cl *Client) HandleClient(cList *ClientList) {

    defer cl.Conn.Close()

    cList.AddClient(cl)

    err := cl.Identify()

    if err != nil {

        log.Println(err)

        return

    }

    for {

        err := cl.Conn.SetDeadline(time.Now().Add(20 * time.Second))

        if err != nil {

            log.Println(err)

            return

        }

        cl.Conn.Write([]byte("What command would you like to perform?\n"))

        netData, err := bufio.NewReader(cl.Conn).ReadString('\n')

        if err != nil {

            log.Println(err)

            return

        }

        cmd := strings.TrimSpace(string(netData))

        if cmd == "Ping" {

            cl.Ping() //sends a pong msg back to client

        } else {

            cl.Conn.Write([]byte("Unsupported command at this time\n"))

        }


    }

}

func main() {

    arguments := os.Args


    PORT := ":" + arguments[1]

    l, err := net.Listen("tcp4", PORT)

    if err != nil {

        fmt.Println(err)

        return

    }

从我最初的测试来看,这似乎有效。我能够打印出我的客户列表,我可以看到正在添加新客户,并且在Identify()调用之后也添加了他们的名字。


当我使用 -race 标志运行它时,我确实收到了数据竞争警告,所以我知道我需要一种线程安全的方式来处理添加的客户端。当我添加它时删除客户端也是如此。


将我的 ClientList 传递到 中是否有任何其他问题我可能会遗漏HandleClient,或者我将 ClientList 声明为包级变量可以获得任何好处?


当年话下
浏览 73回答 1
1回答

拉莫斯之舞

这种方法存在几个问题。首先,您的代码包含数据竞争:每个 TCP 连接都由一个单独的 goroutine 提供服务,并且它们都试图同时修改切片。go build -race您可能会尝试使用(或go install -race— 无论您使用什么)构建代码,并看到它因启用的运行时检查而崩溃。这个很容易修复。最直接的方法是在类型中添加一个互斥变量ClientList:type ClientList struct {  mu      sync.Mutex  clients []*Client}…并使类型的方法在改变字段时保持互斥量clients,如下所示:func (cList *ClientList) AddClient(cl *Client) {  cList.mu.Lock()  defer cList.mu.Unlock()  cList.clients = append(cList.clients, o)}(如果你曾经遇到过你的ClientList类型的典型使用模式是频繁调用只读取包含列表的方法,你可以开始使用sync.RWLock允许多个并发读取器的类型。)其次,我将“识别”客户端的部分从处理函数中分离出来。至此,在handler中,如果识别失败,handler退出,但client不会退市。我会说最好预先识别它,并且只有在认为客户没问题时才运行处理程序。此外,值得RemoveClient在处理程序主体的顶部添加一个延迟调用,以便在处理程序完成时正确地取消列出客户端。IOW,我希望看到这样的事情:func (cl *Client) HandleClient(cList *ClientList) {    defer cl.Conn.Close()    err := cl.Identify()    if err != nil {        log.Println(err)        return    }    cList.AddClient(cl)    defer cList.RemoveClient(cl)    // ... the rest of the code}
打开App,查看更多内容
随时随地看视频慕课网APP