Golang中客户端无法向TCP服务器发送数据?

9

我有一个TCP服务器和客户端,简单的TCP服务器只会接收传入的数据并将其打印出来,而客户端将不断创建套接字连接并在循环中向TCP服务器发送数据。

我得到的信息是,如果TCP连接被正确关闭,这个过程应该继续进行而不会崩溃。

但是,在从客户端到服务器接收了一定量的数据之后,客户端会因错误而崩溃。

total times data send: 16373

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10d7594]

goroutine 1 [running]:
main.sendData()

/Users/apple/Desktop/Personal/umbrellaserver/src/tests/clinet.go:178 
+0xb4
main.main()

/Users/apple/Desktop/Personal/umbrellaserver/src/tests/clinet.go:170 
+0x2a
exit status 2

Server.go

package main

import (
    "bufio"
    "fmt"
    "net"
    "sync"
)

var wg sync.WaitGroup
var count = 0
var timeX string = ""

var connQueue = make(chan string)

func main() {
    tcpListner := startTCPConnection()
    incomingTCPListener(tcpListner)
}

//startTCPConnection
func startTCPConnection() net.Listener {
    tcpListner, tcpConnectonError := net.Listen("tcp", "localhost:3000")
    if tcpConnectonError != nil {
        print(tcpConnectonError)
        return 
    }
    return tcpListner
}

//incomingTCPListener
func incomingTCPListener(tcpListner net.Listener) {

    for {
        incomingConnection, incomingConnectionError := tcpListner.Accept()
        if incomingConnectionError != nil {
            print(incomingConnectionError)
            return
        }
        wg.Add(1)
        go processIncomingRequest(incomingConnection)
        wg.Wait()
    }
}

//processIncomingRequest
func processIncomingRequest(connection net.Conn) {

    defer connection.Close()

    var scanner = bufio.NewScanner(connection)

    var blob = ""
    for scanner.Scan() {
        fmt.Println("sadd")
        text := scanner.Text()
        blob += text
    }
    print(blob)
    count++
    fmt.Println("totalCount", count)
    wg.Done()
}

Client.go

package main

import (
    "fmt"
    "net"
)

var count = 0

func testJSON2() string {
    return `Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.`
}

func main() {
    for i := 0; i < 1000000; i++ {
        sendData()
    }

}

func sendData() {

    connection, connectionError := net.Dial("tcp", "localhost:3000")
    defer connection.Close()

    if connectionError != nil {
        fmt.Println(connectionError)
        return 
    }
    newmessage := testJSON2()
    connection.Write([]byte(newmessage + "\n"))
    count++
    fmt.Println(count)
}

有没有办法避免这个崩溃并使其持续运行? 我完全是Go的新手,如果我犯了任何愚蠢的错误,我很抱歉。


@ThunderCat 我尝试了删除所有wg并打印错误和返回,但仍然出现相同的错误。如果您能告诉我在这里我做错了什么,那就太好了。 - Albi
你正在运行哪个版本的 Go 和什么操作系统? - superfell
@superfell 我目前正在运行于Mac OS Mojave和Linux 16.4两个系统。 - Albi
我在OSX 10.14.3和go 1.11.4上运行良好,但如果Dial失败,则会从connection.Close()事件中看到恐慌。 - superfell
请查看 https://play.golang.org/p/1Yez0wttdvO。 - Charlie Tumahai
显示剩余6条评论
3个回答

12
首先,如果你想要将内容打印到stderr(即你的打印调用),我建议使用fmt库。
fmt.Fprintln(os.Stderr, "hello world")

为什么:因为打印函数不能保证在语言中保持不变。1
其次,通常惯例是给错误命名时只需使用 err 这样的名称,而不必拼写出像 tcpConnectionError 这样的错误名称。
第三点,由于在connection, connectionError := net.Dial("tcp", "localhost:3000")中使用了tcp连接,服务器会同时监听ipv6和ipv4。我注意到至少在一些Windows机器上,连接会打开两个连接,一个是ipv4,一个是ipv6,然后放弃ipv6连接,选择ipv4连接。
最后,当TCP连接关闭时,端口不能立即重用,因为操作系统必须等待TIME_WAIT间隔(最大段寿命,MSL)的持续时间。
您的客户端代码正在打开大量非常短暂的TCP连接,并且根据您的临时端口范围,您的代码可能会崩溃或不崩溃。从16373的数量来看,您使用的是默认范围。2
>> sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535

最后,

如果您想要避免由于端口耗尽而导致的崩溃:
1. 增加短暂端口范围
2. 使用Docker,容器位于自己的网络上,因此使用不同的端口集,从而绕过了有限的端口范围。
3. 在客户端代码中引入定时器,以每x秒/分钟模拟连接

package main

import (
    "fmt"
    "net"
    "time"
)

var count = 0

func testJSON2() string {
    return `Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.`
}

func main() {
    max := 1000

    timer1 := time.NewTicker(5 * time.Second)

    i := 0
    for range timer1.C {
        sendData()
        if i == max {
            timer1.Stop()
        }
        i++
    }
}

func sendData() {

    connection, connectionError := net.Dial("tcp", "localhost:3000")
    fmt.Println(connection.LocalAddr())

    if connectionError != nil {
        fmt.Println(connectionError)
        return
    }
    newmessage := testJSON2()
    connection.Write([]byte(newmessage + "\n"))
    count++
    fmt.Println(count)

    err := connection.Close()
    if err != nil {
        fmt.Println(err)
    }
}

参考文献
^1: https://tip.golang.org/pkg/builtin/#print
^2: Mac上短暂端口的范围是多少?
^3: https://www.fromdual.com/huge-amount-of-time-wait-connections


1

package main

import (
 "bufio"
 "fmt"
 "net"
 "sync"
)

var wg sync.WaitGroup
var count = 0
var timeX string = ""

var connQueue = make(chan string)

func main() {
 tcpListner := startTCPConnection()
 incomingTCPListener(tcpListner)
    wg.Wait()
}

//startTCPConnection
func startTCPConnection() net.Listener {
 tcpListner, tcpConnectonError := net.Listen("tcp", "localhost:3000")
 if tcpConnectonError != nil {
  print(tcpConnectonError)
        // return 
        log.Fatal(tcpConnectonError)
 }
 return tcpListner
}

//incomingTCPListener
func incomingTCPListener(tcpListner net.Listener) {

 for {
  incomingConnection, incomingConnectionError := tcpListner.Accept()
  if incomingConnectionError != nil {
   print(incomingConnectionError)
            return
  }
  wg.Add(1)
  go processIncomingRequest(incomingConnection)
  // wg.Wait()
 }
}

//processIncomingRequest
func processIncomingRequest(connection net.Conn) {

 defer connection.Close()

 var scanner = bufio.NewScanner(connection)

 var blob = ""
 for scanner.Scan() {
  fmt.Println("sadd")
  text := scanner.Text()
  blob += text
 }
 print(blob)
 count++
 fmt.Println("totalCount", count)
 wg.Done()
}

问题出在 server.go 文件中。 我猜你在 incomingTCPListener() 函数里调用了 wg.Wait() 导致端口不足。 另外,startTCPConnection() 函数里的裸返回语句会导致编译错误。

非常感谢,我已经能够解决我的代码中的一些问题。 我会检查它并让你知道。 非常感谢你的努力和帮助。 - Albi
仍然崩溃(接收到的数据计数为16375)恐慌:运行时错误:无效的内存地址或空指针解引用 [信号SIGSEGV:分段违规代码=0x1 addr=0x0 pc=0x10d7534]goroutine 1 [正在运行]: main.sendData() /Users/apple/Desktop/umbrellaServer/src/clientTest.go:24 +0xb4 main.main() /Users/apple/Desktop/umbrellaServer/src/clientTest.go:16 +0x2a 退出状态2 Albins-MBP:src apple$ - Albi

1
if connectionError != nil {
        fmt.Println(connectionError)
        return 
    }
defer connection.Close()

在进行错误检查后,defer connection.Close() 应该放在后面,因为如果 dial 返回错误,则 connection 变量可能为 nil。


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