使Golang TCP服务器并发

时间:2018-07-09 21:37:17

标签: go tcp concurrency

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

希望您能得到与我使用相同方法找到并测试的相似代码示例(并发调用函数以处理连接)有效的任何反馈;因此,我想知道修改后的代码与我看到的其他示例有什么不同,因为它们对我来说似乎是相同的。

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

谢谢。

4 个答案:

答案 0 :(得分:5)

您的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)
    }
}

答案 1 :(得分:2)

看到此行为的原因是,即使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

您还可以恢复延期

答案 2 :(得分:2)

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

我经常发现自己正在检查go standard库中如何解决类似的问题。例如,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()通道是否返回一个值,以指示预期到错误并且服务器已关闭。

答案 3 :(得分:0)

这就是你想要的

服务器

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()
}