我有一个服务器
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
?
您已经非常接近解决方案了 - 您唯一犯错的是首先从
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上查看。
我在工作中也遇到过类似的情况。我们所做的是实现两个简单的 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
}
如果可行的话,我建议您使用原生实现