在Go中的Socket回显服务器

huangapple go评论81阅读模式
英文:

Socket echo server in go

问题

我正在尝试在Go中实现一个简单的socket回显服务器,以下是代码:

package main

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

func echo_srv(c net.Conn, wg sync.WaitGroup) {
    defer c.Close()
    defer wg.Done()

    for {
            var msg []byte

            n, err := c.Read(msg)
            if err != nil {
                    fmt.Printf("ERROR: read\n")
                    fmt.Print(err)
                    return
            }
            fmt.Printf("SERVER: received %v bytes\n", n)

            n, err = c.Write(msg)
            if err != nil {
                    fmt.Printf("ERROR: write\n")
                    fmt.Print(err)
                    return
            }
            fmt.Printf("SERVER: sent %v bytes\n", n)
    }
}

func main() {
    var wg sync.WaitGroup

    ln, err := net.Listen("unix", "./sock_srv")
    if err != nil {
            fmt.Print(err)
            return
    }
    defer ln.Close()

    conn, err := ln.Accept()
    if err != nil {
            fmt.Print(err)
            return
    }
    wg.Add(1)
    go echo_srv(conn, wg)

    wg.Wait()
}

由于某种原因,一旦客户端连接,c.Read()就不会阻塞并打印错误消息。
所以,我的第一个问题是:c.Read()不应该阻塞直到客户端向套接字发送数据吗?

第二个问题是:在打印错误消息后,服务器不会终止。
在gdb中执行程序时,我看到的是:

(gdb) run                                                                    
Starting program: src/sockets/server/server                                  
warning: Could not load shared library symbols for linux-vdso.so.1.          
Do you need "set solib-search-path" or "set sysroot"?                        
[Thread debugging using libthread_db enabled]                                
Using host libthread_db library "/usr/lib/libthread_db.so.1".                
[New Thread 0x7fffe7806700 (LWP 28594)]                                      
[New Thread 0x7fffe7005700 (LWP 28595)]                                      
ERROR: read                                                                  
EOF^C                                                                        
Program received signal SIGINT, Interrupt.                                   
runtime.epollwait () at /usr/lib/go/src/pkg/runtime/sys_linux_amd64.s:383    
383             RET                                                          
(gdb) info goroutines                                                        
  1  waiting runtime.park                                                    
  2  syscall runtime.goexit                                                  
* 3  syscall runtime.entersyscallblock

我在Python和C中也有类似的回显服务器,它们工作正常。为了完整起见,我还发布了下面的socket客户端应用程序(它与我的C和Python服务器正常工作)。

客户端:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)

    conn, err := net.Dial("unix", "./sock_srv")
    if err != nil {
            fmt.Print(err)
            return
    }
    defer conn.Close()

    for {
            fmt.Print("Enter message to transmit: ")
            msg, err := stdin.ReadString('\n')
            if err != nil {
                    fmt.Print(err)
                    return
            }

            msg = msg[:len(msg)-1]
            if (strings.ToLower(msg) == "quit") || (strings.ToLower(msg) == "exit") {
                    fmt.Println("bye")
                    return
            }

            n, err := conn.Write([]byte(msg))
            if err != nil {
                    fmt.Print(err)
                    return
            }
            fmt.Printf("CLIENT: sent %v bytes\n", n)

            n, err = conn.Read([]byte(msg))
            if err != nil {
                    fmt.Print(err)
                    return
            }
            fmt.Printf("CLIENT: received %v bytes\n", n)

            fmt.Println("Received message:", msg)
    }
}
英文:

I'm trying to implement a simple socket echo server in go this is the code:

package main

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

func echo_srv(c net.Conn, wg sync.WaitGroup) {
    defer c.Close()
    defer wg.Done()

    for {
            var msg []byte

            n, err := c.Read(msg)
            if err != nil {
                    fmt.Printf("ERROR: read\n")
                    fmt.Print(err)
                    return
            }
            fmt.Printf("SERVER: received %v bytes\n", n)

            n, err = c.Write(msg)
            if err != nil {
                    fmt.Printf("ERROR: write\n")
                    fmt.Print(err)
                    return
            }
            fmt.Printf("SERVER: sent %v bytes\n", n)
    }
}

func main() {
    var wg sync.WaitGroup

    ln, err := net.Listen("unix", "./sock_srv")
    if err != nil {
            fmt.Print(err)
            return
    }
    defer ln.Close()

    conn, err := ln.Accept()
    if err != nil {
            fmt.Print(err)
            return
    }
    wg.Add(1)
    go echo_srv(conn, wg)

    wg.Wait()
}

For some reason as soon as a client connects, c.Read() does not block and the error message is printed.
So, my first question is: Shouldn't c.Read() block until a client sends something to the socket?

And second: After printing the error message, the server does not terminate.
This is what I see when executing the program in gdb:

(gdb) run                                                                    
Starting program: src/sockets/server/server                                  
warning: Could not load shared library symbols for linux-vdso.so.1.          
Do you need "set solib-search-path" or "set sysroot"?                        
[Thread debugging using libthread_db enabled]                                
Using host libthread_db library "/usr/lib/libthread_db.so.1".                
[New Thread 0x7fffe7806700 (LWP 28594)]                                      
[New Thread 0x7fffe7005700 (LWP 28595)]                                      
ERROR: read                                                                  
EOF^C                                                                        
Program received signal SIGINT, Interrupt.                                   
runtime.epollwait () at /usr/lib/go/src/pkg/runtime/sys_linux_amd64.s:383    
383             RET                                                          
(gdb) info goroutines                                                        
  1  waiting runtime.park                                                    
  2  syscall runtime.goexit                                                  
* 3  syscall runtime.entersyscallblock

I have similar echo servers in Python and C and they work fine. For completeness I also post the socket client application below (it works fine with my C and Python servers).

Client:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)

    conn, err := net.Dial("unix", "./sock_srv")
    if err != nil {
            fmt.Print(err)
            return
    }
    defer conn.Close()

    for {
            fmt.Print("Enter message to transmit: ")
            msg, err := stdin.ReadString('\n')
            if err != nil {
                    fmt.Print(err)
                    return
            }

            msg = msg[:len(msg)-1]
            if (strings.ToLower(msg) == "quit") || (strings.ToLower(msg) == "exit") {
                    fmt.Println("bye")
                    return
            }

            n, err := conn.Write([]byte(msg))
            if err != nil {
                    fmt.Print(err)
                    return
            }
            fmt.Printf("CLIENT: sent %v bytes\n", n)

            n, err = conn.Read([]byte(msg))
            if err != nil {
                    fmt.Print(err)
                    return
            }
            fmt.Printf("CLIENT: received %v bytes\n", n)

            fmt.Println("Received message:", msg)
    }
}

答案1

得分: 5

这是一个可工作的echo_srv给你。你还需要@jnml的建议!

  • 实际上分配一些缓冲区来接收 - 你创建了一个0字节的缓冲区!
  • 在EOF时优雅地退出
  • 只写入使用msg[:n]接收到的字节
func echo_srv(c net.Conn, wg *sync.WaitGroup) {
    defer c.Close()
    defer wg.Done()

    for {
        msg := make([]byte, 1000)

        n, err := c.Read(msg)
        if err == io.EOF {
            fmt.Printf("SERVER: received EOF (%d bytes ignored)\n", n)
            return
        } else if err != nil {
            fmt.Printf("ERROR: read\n")
            fmt.Print(err)
            return
        }
        fmt.Printf("SERVER: received %v bytes\n", n)

        n, err = c.Write(msg[:n])
        if err != nil {
            fmt.Printf("ERROR: write\n")
            fmt.Print(err)
            return
        }
        fmt.Printf("SERVER: sent %v bytes\n", n)
    }
}
英文:

Here is a working echo_srv for you. You'll need @jnml's suggestion too!

  • actually allocate some buffer to receive into - you made a 0 byte buffer!

  • exit neatly on EOF

  • only write the bytes received with msg[:n]

    func echo_srv(c net.Conn, wg *sync.WaitGroup) {
    	defer c.Close()
    	defer wg.Done()
    
    	for {
    		msg := make([]byte, 1000)
    
    		n, err := c.Read(msg)
    		if err == io.EOF {
    			fmt.Printf("SERVER: received EOF (%d bytes ignored)\n", n)
    			return
    		} else 	if err != nil {
    			fmt.Printf("ERROR: read\n")
    			fmt.Print(err)
    			return
    		}
    		fmt.Printf("SERVER: received %v bytes\n", n)
    
    		n, err = c.Write(msg[:n])
    		if err != nil {
    			fmt.Printf("ERROR: write\n")
    			fmt.Print(err)
    			return
    		}
    		fmt.Printf("SERVER: sent %v bytes\n", n)
    	}
    }
    

答案2

得分: 3

我没有检查它是否是罪魁祸首,但在“技术分析”方面,我注意到你的代码中有一个错误:你将sync.Workgroup的副本传递给了echo_srv。对副本所做的任何更改都不会对原始实例产生影响。

echo的签名更改为:

func echo_srv(c net.Conn, wg *sync.WaitGroup)

然后像这样调用它:

go echo_srv(conn, &wg)

顺便说一下:在惯用的Go代码命名中,不使用下划线(_)作为名称的一部分。惯用的名称应该是例如echoSrv

英文:

I did not check if it's the culprit, but on the "technical analysis" side I noticed one error in your code: You're passing a copy of a sync.Workgroup to echo_srv. Any changes made to the copy are not effective to the original instance.

Change the signature of echo to:

func echo_srv(c net.Conn, wg *sync.WaitGroup)

and then call it like:

go echo_srv(conn, &wg)

On a side note: Underscores (_) are not used in the middle of idiomatic Go code names. The idiomatic name would be eg. echoSrv instead.

huangapple
  • 本文由 发表于 2013年7月21日 05:16:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/17766770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定