【问题标题】:Making Golang TCP server concurrent使 Golang TCP 服务器并发
【发布时间】:2018-12-17 15:48:00
【问题描述】:

Go 新手并试图使 TCP 服务器并发。我发现了多个这样的例子,包括this one,但我想弄清楚为什么我对非并发版本所做的一些更改不起作用。

这是我开始的原始示例代码

package main
import "bufio"
import "fmt"
import "log"
import "net"
import "strings" // only needed below for sample processing

func main() {
  fmt.Println("Launching server...")
  fmt.Println("Listen on port")
  ln, err := net.Listen("tcp", "127.0.0.1:8081")
  if err != nil {
      log.Fatal(err)
  }
  defer ln.Close()

  fmt.Println("Accept connection on port")
  conn, err := ln.Accept()
  if err != nil {
      log.Fatal(err)
  }

  fmt.Println("Entering loop")
  // run loop forever (or until ctrl-c)
  for {
    // will listen for message to process ending in newline (\n)
    message, _ := bufio.NewReader(conn).ReadString('\n')
    // output message received
    fmt.Print("Message Received:", string(message))
    // sample process for string received
    newmessage := strings.ToUpper(message)
    // send new string back to client
    conn.Write([]byte(newmessage + "\n"))
  }
}

上述方法有效,但不是并发的。

这是我修改后的代码

package main
import "bufio"
import "fmt"
import "log"
import "net"
import "strings" // only needed below for sample processing

func handleConnection(conn net.Conn) {
  fmt.Println("Inside function")
  // run loop forever (or until ctrl-c)
  for {
    fmt.Println("Inside loop")
    // will listen for message to process ending in newline (\n)
    message, _ := bufio.NewReader(conn).ReadString('\n')
    // output message received
    fmt.Print("Message Received:", string(message))
    // sample process for string received
    newmessage := strings.ToUpper(message)
    // send new string back to client
    conn.Write([]byte(newmessage + "\n"))
  }

}

func main() {
  fmt.Println("Launching server...")
  fmt.Println("Listen on port")
  ln, err := net.Listen("tcp", "127.0.0.1:8081")
  if err != nil {
      log.Fatal(err)
  }
  //defer ln.Close()

  fmt.Println("Accept connection on port")
  conn, err := ln.Accept()
  if err != nil {
      log.Fatal(err)
  }
  fmt.Println("Calling handleConnection")
  go handleConnection(conn)

}

我的代码基于我发现的其他几个并发服务器示例,但是当我运行上述代码时,服务器似乎退出而不是运行 handleConnection 函数

正在启动服务器...

监听端口

接受端口上的连接

调用handleConnection

感谢任何反馈,因为我发现并使用相同的方法测试了类似的代码示例,同时调用函数来处理连接,工作正常;所以,想知道我修改后的代码与我看到的其他示例有什么不同,因为它们对我来说似乎是一样的。

我不确定是否是问题所在,但我尝试评论关闭延迟调用。这没有帮助。

谢谢。

【问题讨论】:

    标签: go tcp concurrency


    【解决方案1】:

    您的main 函数在接受新连接后立即返回,因此您的程序在连接处理之前退出。由于您可能还希望接收多个单一连接(否则将没有并发),您应该将其放入 for 循环中。

    您还在 for 循环的每次迭代中创建一个新的缓冲读取器,这将丢弃所有缓冲数据。您需要在 for 循环之外执行此操作,我在这里通过创建一个新的 bufio.Scanner 进行演示,这是一种读取换行符分隔文本的更简单方法。

    import (
        "bufio"
        "fmt"
        "log"
        "net"
        "strings"
    )
    
    func handleConnection(conn net.Conn) {
        defer conn.Close()
        scanner := bufio.NewScanner(conn)
        for scanner.Scan() {
            message := scanner.Text()
            fmt.Println("Message Received:", message)
            newMessage := strings.ToUpper(message)
            conn.Write([]byte(newMessage + "\n"))
        }
    
        if err := scanner.Err(); err != nil {
            fmt.Println("error:", err)
        }
    }
    
    func main() {
        ln, err := net.Listen("tcp", "127.0.0.1:8081")
        if err != nil {
            log.Fatal(err)
        }
    
        fmt.Println("Accept connection on port")
    
        for {
            conn, err := ln.Accept()
            if err != nil {
                log.Fatal(err)
            }
            fmt.Println("Calling handleConnection")
            go handleConnection(conn)
        }
    }
    

    【讨论】:

    • 谢谢。解决方案正是我想要的。跟进。收到的消息:client1-line1 收到的消息:调用 handleConnection 收到的消息:client2-line1 收到的消息:收到的消息:client1-line2 收到的消息:服务器似乎正在添加额外的空消息。知道为什么吗?
    • @Francisco1844:服务器代码很简单,没有地方可以添加额外的消息。您可能正在从用于测试的客户端发送空行。
    【解决方案2】:

    您看到这种行为的原因是您的 main 方法退出了,即使您的 go 例程仍在运行。确保阻止 main 方法来实现您想要实现的目标。

    可能会在主目录中添加类似这样的内容:

    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    <-c // This will block until you manually exists with CRl-C
    

    你也可以恢复你的延迟

    【讨论】:

      【解决方案3】:

      当你使用go func() 语法运行函数时,你正在执行一个新的 goroutine 而不会阻塞主要的 goroutine。但是,当主 goroutine 完成时,程序将退出,所以简而言之,只要您希望子 goroutine 执行,您就需要阻塞主 goroutine。

      我经常发现自己在检查 go 标准库中是如何解决类似问题的。例如,http 包中的 Server.Serve() 执行类似的操作。这是提取的版本(缩短,点击链接查看完整版本):

      func (srv *Server) Serve(l net.Listener) error {
         defer l.Close()
      
         ctx := context.Background() 
         for {
            rw, e := l.Accept()
            if e != nil {
              select {
              case <-srv.getDoneChan():
                  return ErrServerClosed
              default:
              }
              if ne, ok := e.(net.Error); ok && ne.Temporary() {
                  // handle the error
              }
              return e
            }
            c := srv.newConn(rw)
            c.setState(c.rwc, StateNew) // before Serve can return
            go c.serve(ctx)
         }
      }
      

      要停止上述函数,我们可以关闭监听器(例如通过中断信号),这反过来会在Accept() 上产生错误。上述实现检查serv.GetDoneChan() 通道是否返回一个值作为预期错误和服务器关闭的指示符。

      【讨论】:

        【解决方案4】:

        这就是你想要的

        服务器

        package main
        
        import (
            "bufio"
        )
        import "fmt"
        import "log"
        import "net"
        import "strings" // only needed below for sample processing
        
        func handleConnection(conn net.Conn) {
            fmt.Println("Inside function")
            // will listen for message to process ending in newline (\n)
            message, _ := bufio.NewReader(conn).ReadString('\n')
            // output message received
            fmt.Print("Message Received:", string(message))
            // sample process for string received
            newmessage := strings.ToUpper(message)
            // send new string back to client
            conn.Write([]byte(newmessage + "\n"))
            conn.Close()
        
        }
        
        func main() {
            fmt.Println("Launching server...")
            fmt.Println("Listen on port")
            ln, err := net.Listen("tcp", "127.0.0.1:8081")
            if err != nil {
                log.Fatal(err)
            }
        
            fmt.Println("Accept connection on port")
            for {
                conn, err := ln.Accept()
                if err != nil {
                    log.Fatal(err)
                }
                fmt.Println("Calling handleConnection")
                go handleConnection(conn)
            }
        
        
        }
        

        客户

        package main
        
        import (
            "bufio"
            "fmt"
            "net"
        )
        
        func main() {
            addr, _ := net.ResolveTCPAddr("tcp", ":8081")
            conn, err := net.DialTCP("tcp", nil, addr)
            if err != nil {
                panic(err.Error())
            }
            fmt.Fprintf(conn, "From the client\n")
            message, _ := bufio.NewReader(conn).ReadString('\n')
            fmt.Print(message)
            conn.Close()
        }
        

        【讨论】:

          猜你喜欢
          • 2013-02-06
          • 1970-01-01
          • 2019-09-15
          • 2019-01-29
          • 2021-08-28
          • 2020-09-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多