收到新消息时,golang net.Conn可以收到通知吗?

时间:2018-12-26 19:15:03

标签: sockets go unix-socket

我尝试实现的目标

我正在实现一个客户端,它发送和接收unixpackets并与已经用C ++实现的服务器交互。
到目前为止,客户端可以成功接收数据包并将其发送到服务器。但是我使用了for循环来不断调用 Recv 来查看是否有新消息。

我做了什么

我实现了Packet类,以便客户端和服务器可以遵循相同的规则来编码和解码消息。传输是一种包装 net.Conn 的结构。客户端由传输,接收数据包和发送数据包组成。
我使用了 for循环继续调用 Recv 来查看是否有新消息到达 net.conn

type Packet struct {
    Length      uint32
    Data        []byte
    ReadOffset  uint32
    WriteOffset uint32
}

type Transport struct {
    url   string
    conn  net.Conn
}

// connect to url
func (transport *Transport) Dial(url string) error {
    c, err := net.Dial("unixpacket", url)
    if err != nil {
        return err
    }
    transport.url = url
    transport.conn = c
    return nil
}

// sending Packet
func (transport *Transport) Send(p *Packet) bool {
    readBuffer := (p.Data)[p.ReadOffset : p.WriteOffset]
    _, err := transport.conn.Write(readBuffer)
    if err != nil {
        return false
    }
    return true
}

// receiving Packet
func (transport *Transport) Recv(p *Packet) bool {
    writeBuffer := (p.Data)[p.WriteOffset :]
    _, err := transport.conn.Read(writeBuffer)
    if err != nil {
        return false
    }
    return true
}

type Client struct {
    Transport  *Transport
    RecvPacket *Packet
    SendPacket *Packet
}

// keep checking transport
func (c *Client) ReadFromTransport() {
    for {
        time.Sleep(20 * time.Millisecond)
        if c.Transport.Recv(c.RecvPacket) == false {
            continue
        }
    }
}

问题

是否有一种方法可以让新消息到达 net.conn 时通知net.conn而不是使用for循环?

2 个答案:

答案 0 :(得分:3)

net.conn.Read自动阻塞,直到连接上有可用数据为止。您不必循环就可以等待数据出现。

具有一个简单的套接字服务器(在python3中只是为了清除语言障碍)

import socket, os, sys

addr='/tmp/a_unix_socket'

if os.path.exists(addr):
  os.unlink(addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

sock.bind(addr)
sock.listen(1)

while True:
  print("Waiting for connection", file=sys.stderr)
  conn, client_addr = sock.accept()
  data =conn.recv(128)
  print("Recived {}".format(data))
  conn.sendall(b'thank you')
  conn.close()

我可以这样做一个golang客户:

package main

import (
  "net"
  "fmt"
)

func main() {
  conn, err := net.Dial("unix", "/tmp/a_unix_socket")
  if err != nil {
    panic(err)
  }
  conn.Write([]byte("here is data"))
  result := make([]byte, 128)
  _, err = conn.Read(result)
  if err != nil {
    panic(err)
  }
  fmt.Println("Response: ", string(result))
  conn.Close()
}

服务器端说:

$ python3 ./t.py
Waiting for connection
Recived b'here is data'
Waiting for connection

客户端说:

$ go run t.go
Response:  thank you

net.Conn此处未配置读取超时,因此它愿意等待尽可能长的时间。

让我也提一下:

if err != nil {
    return false
}

这隐藏了无法区分的“假”响应背后的错误。在发生不可恢复的错误(假设服务器关闭连接)的情况下,客户端将无限期循环(在此过程中占用大量CPU)。如果需要公开错误,请返回它们。如果您想懒于进行概念验证,请根据需要panic(),但不要对所有它们返回相同的结果,否则您将无法正确处理单个错误情况。在您的情况下,这是一个有争议的问题,因为无论如何您都不需要for循环。

答案 1 :(得分:2)

  

是否有一种方法可以让新消息到达net.conn时通知net.conn而不是使用for循环?

否,不在标准包装中。

从net.Conn中的

Read()ing循环是检测消息到达的常用方法。如果您需要执行多个并发任务,则可以将循环封装在写入通道的goroutine中,并包装for循环以及从该通道读取数据的选择。

除了标准软件包之外,您还可以使用https://github.com/tidwall/evio

等项目探索事件驱动的IO