无需在 go 中阅读即可查看 Conn

问题描述 投票:0回答:2

我有一个服务器

net.Conn
,我想在读出字节之前先查看一下它,以检查它是否是客户端尝试使用的纯文本协议,或者 SSL/TLS。

检查http://golang.org/pkg/net/,似乎

Conn
界面没有类似的东西。我知道我可以使用
iobuf.Reader
,但如果事实证明客户端正在使用 SSL/TLS,我想通过
tls.Conn(conn, config)
获取 TLS Conn,并且
bufio.Reader
将从原始
Conn
中读取,因此
tls.Conn
中的握手将会失败。

那么有没有办法查看 Go 中的

Conn
(类似于 C/C++ 套接字中的
MSG_PEEK
)?或者,在我从底层
tls.Conn
读出前几个字节后创建一个
Conn

sockets networking go peek
2个回答
16
投票

您已经非常接近解决方案了 - 您唯一犯错的是首先从

Conn
本身读取内容。你是对的,
bufio.Reader
Peek
方法是正确的选择。诀窍是首先创建缓冲读取器,然后在缓冲读取器上调用
Peek
,而不是从原始
Conn
读取。这是一个
bufferedConn
类型,可以满足您的需要:

type bufferedConn struct {
    r        *bufio.Reader
    net.Conn // So that most methods are embedded
}

func newBufferedConn(c net.Conn) bufferedConn {
    return bufferedConn{bufio.NewReader(c), c}
}

func newBufferedConnSize(c net.Conn, n int) bufferedConn {
    return bufferedConn{bufio.NewReaderSize(c, n), c}
}

func (b bufferedConn) Peek(n int) ([]byte, error) {
    return b.r.Peek(n)
}

func (b bufferedConn) Read(p []byte) (int, error) {
    return b.r.Read(p)
}

它的作用是允许您访问所有正常的

net.Conn
方法(通过嵌入
net.Conn
- 您也可以编写包装函数,但这更容易和更清晰),并且还提供对
bufferedReader 的访问
Peek
Read
方法(重要的是,在
Read
上调用
bufferedReader
,而不是直接在
net.Conn
上调用,因为
Peek
将数据存储在缓冲区中,因此后续调用
Read
需要能够首先从该缓冲区中读取任何数据,然后再回退到底层
net.Conn
)。

考虑到当前默认缓冲区大小为 4096 字节,

newBufferedConnSize
函数可能是不必要的,但从技术上讲,如果您要依靠能够以给定大小调用
Peek
并且不让它返回错误(具体来说
ErrBufferFull
),您应该将其明确设置为至少与您想要查看的字节数一样大的大小。

Go Playground上查看。


0
投票

我在工作中也遇到过类似的情况。我们所做的是实现两个简单的 C 函数 - 然而 - 我认为 golang 实现可能更好。

#include <sys/socket.h>
#include <sys/ioctl.h>

buffered readders
ssize_t peek_socket_data(int socket_fd, void *buffer, size_t length) {
    return recv(socket_fd, buffer, length, MSG_PEEK);
}

ssize_t available_socket_data(int socket_fd) {
    int bytes_available;
    if (ioctl(socket_fd, FIONREAD, &bytes_available) == -1) {
        return -1;
    }
    return bytes_available;
}

然后是 go 函数:

// CPeekSocketData retrieves everything available on the socket without removing it.
func CPeekSocketData(fd int) ([]byte, error) {
    // First, get the amount of available data
    available := C.available_socket_data(C.int(fd))
    if available == -1 {
        return nil, fmt.Errorf("error determining available data on socket")
    }
    if available == 0 {
        return nil, fmt.Errorf("no data available on socket")

        // return []byte{}, nil // No data available
    }

    // Allocate buffer for the available data
    buffer := make([]byte, available)

    // Peek at the data
    n, err := C.peek_socket_data(C.int(fd), unsafe.Pointer(&buffer[0]), C.size_t(available))
    if n == -1 {
        return nil, fmt.Errorf("error peeking at socket data: %v", err)
    }

    return buffer[:n], nil
}

如果可行的话,我建议您使用原生实现

© www.soinside.com 2019 - 2024. All rights reserved.