TCP接受和Go并发模型

TCP接受和Go并发模型,tcp,concurrency,go,Tcp,Concurrency,Go,查看net.TCPListener。考虑到Go并发范例,我们希望将此系统功能实现为一个通道,这样您就可以从Listen()函数或类似的函数中获得chan*net.Conn 但似乎Accept()是一种方式,它只是阻塞,就像系统Accept一样。但它是残废的,因为: 没有合适的select()可用于它,因为go更喜欢频道 无法为服务器套接字设置阻止选项 所以我做了一些类似的事情: acceptChannel = make(chan *Connection) go func()

查看
net.TCPListener
。考虑到Go并发范例,我们希望将此系统功能实现为一个通道,这样您就可以从
Listen()
函数或类似的函数中获得
chan*net.Conn

但似乎Accept()是一种方式,它只是阻塞,就像系统Accept一样。但它是残废的,因为:

  • 没有合适的select()可用于它,因为go更喜欢频道
  • 无法为服务器套接字设置阻止选项
所以我做了一些类似的事情:

    acceptChannel = make(chan *Connection)
    go func() {
      for {
       rw, err := listener.Accept()
       if err != nil { ... handle error ... close(acceptChannel) ... return }
       s.acceptChannel <-&Connection{tcpConn: rw, .... }
      }
    }()
acceptChannel=make(chan*连接)
go func(){
为了{
rw,err:=listener.Accept()
如果err!=nil{…handle error…close(acceptChannel)…return}

s、 acceptChannel您的代码很好。您甚至可以进一步替换:

s.acceptChannel <-&Connection{tcpConn: rw, .... }
如注释中所述,例程不是系统线程,它们是由Go运行时管理的轻量级线程。当您为每个连接创建例程时,您可以轻松使用阻塞操作,这更容易实现。Go运行时会为您选择例程,因此您要寻找的行为只是在其他地方,你看不到它,但它无处不在

现在,如果您需要更复杂的东西,并且根据我们的对话,实现类似于带超时的select的东西,那么您将完全按照您的建议执行:将所有新连接推送到一个通道,并使用计时器对其进行多路复用。这似乎是一种前进的方式

请注意,如果其中一个接受程序出现故障,则无法关闭接受通道,因为另一个接受程序在写入时会惊慌失措

我的(更完整的)示例:

newConns:=make(chan net.Conn)
//对于每个侦听器,生成以下例程
go func(l网络侦听器){
为了{
c、 错误:=l.Accept()
如果错误!=零{
//处理错误(例如,指示接受器已关闭)

newConns goroutines不是线程。但是,它们是处理语言中并发操作的正确方法,例如等待来自多个套接字的Accept。您可能应该观看此视频并尝试重新思考频道,因为您不了解频道的用途或如何使用频道来控制程序流……频道可以作为一种阻塞机制,除了其主要用途是将数据传入和传出goroutine之外。将连接传入通道没有任何意义。更明智的应用是将通道传入goroutine中调用的方法,在那里打开连接,然后将数据发送回通道上的调用方。可能存在阻塞选择那里…@evanmcdonnal-你能给我一个你建议的方法的例子吗?我不明白你在说什么。我正在尝试接受()从多个套接字和其他通道recv.Go不是Node.js。您可以生成50k goroutine,并且阻塞I/O是根据异步I/O实现的。供将来参考,而不是对语言X的工作方式不符合您认为的方式表示怀疑;您只需解释您想要做什么,就可以得到更好的响应询问如何在语言X中使用它。这样做的目的不是为这里的每个连接生成一个连接。这当然会发生,就像在任何具有任何语言的并发模型的接受/处理网络服务器中一样。目标是能够一致地处理所有阻塞操作。例如,我可能希望能够多路传输使用带有通道信号的计时器goroutine的accept()-如果这些N个服务器套接字中的任何一个在timemilliss秒内没有发生接受,请执行此代码…以目前的方式使用accept(),或者按照您的建议在accept上立即生成处理程序,这都是不可能的套接字fds上的“正常”类型的select(),具有定时等待。但这在Go中不可用,因为通道是首选的。因此我尝试包装accept()在频道中。有意义吗?@BadZen实现这一点的方法是在“走后选择”例行程序中。你使用计时器,在频道过期时发送。在“选择”中,你在频道接收的情况下调用你的方法。因此,无论在什么情况下,经过一段时间后,你都将在频道“doCleanUp”上发送,这将调用你的方法“doCleanUp”,这可能会例如,杀死所有go例程并将一些状态写入磁盘,然后退出。如果我今天有时间,我将编写一些人为的示例,但基本上你应该在异步方法中选择,然后调用它。在这两种情况下,你都需要一个退出/中止通道来监听/发送on@BadZen,我添加了一个更完整的示例,它可能类似于您所说的准备好了,但这会让我的回答更清楚。我的示例更多地针对多个侦听器,每个侦听器都有一个接受程序,因此一个接受失败并不表示另一个侦听器失败。您是否建议为一个侦听器使用多个接受程序?我认为,在这个用例中,这不是很有用,一个就足够了。无论如何,关于恢复,我认为这不是一个好的解决方案,如果你知道它可能会由于竞争条件而发生,并且你会为一个非常特定的用例(例如,一个并且只有一个侦听器)实现它。如果你可以轻松地以正确的方式实现它,那么没有理由这么做。
go handleConnection(&Connection{tcpConn: rw, .... })
newConns := make(chan net.Conn)

// For every listener spawn the following routine
go func(l net.Listener) {
    for {
        c, err := l.Accept()
        if err != nil {
            // handle error (and then for example indicate acceptor is down)
            newConns <- nil
            return
        }
        newConns <- c
    }
}(listener)

for {
    select {
    case c := <-newConns:
        // new connection or nil if acceptor is down, in which case we should
        // do something (respawn, stop when everyone is down or just explode)
    case <-time.After(time.Minute):
        // timeout branch, no connection for a minute
    }
}