使Golang TCP服务器并发化

9

我刚接触Go语言并尝试将TCP服务器做成并发的。我找到了多个相关示例,包括这个,但我正在尝试弄清楚为什么对非并发版本所做的某些更改不起作用。

以下是我参考的原始代码:

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

如果您能提供任何反馈,我将不胜感激。我找到并测试了使用相同方法并发调用处理连接函数的类似代码示例,它们可以正常工作。因此,我想知道我的修改后的代码与其他示例之间有什么不同,因为在我看来它们似乎是相同的。
我不确定这是否是问题,但我尝试注释掉defer调用close。那没有帮助。
谢谢。
4个回答

11

你的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
@Francisco1844:服务器代码非常简单,没有额外添加消息的地方。你可能是在使用测试客户端发送空行。 - JimB

4
您看到这种行为的原因是主方法退出,即使您的go例程仍在运行。请确保阻止主方法以实现您想要的目标。
可以尝试在主函数中添加以下内容:
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c // This will block until you manually exists with CRl-C

同时您还可以恢复您的延期。

2
当你使用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() 通道是否返回一个值作为指示符,表明预期发生错误并关闭服务器。

1
这是您想要的。
服务器。
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()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接