英文:
How to set socket option (IP_TOS) for http client in go language?
问题
我是一个对Go语言新手。我计划使用Go语言开发一个HTTP客户端/服务器。在浏览HTTP客户端包支持的功能列表时,我没有找到在包中设置套接字选项的方法(也许我只是不知道如何使用)。在调用HTTP客户端连接之前,我需要在fd中设置DSCP选项(IP_TOS)。尽管我找到了使用系统调用设置套接字选项的方法,但我没有找到从HTTP包中获取fd的方法。
在HTTP服务器端,可以设置套接字选项(IP_TOS)。
代码片段:
tcpListener,err := net.ListenTCP("tcp4", addr)
if err != nil {
//fmt.Println("error in listen", err.error())
log.Fatal("net.ListenTCP()", err)
}
//获取监听套接字的fd
f, _ := tcpListener.File()
err = syscall.SetsockoptInt(int(f.Fd()), syscall.SOL_SOCKET, syscall.IP_TOS, 128)
在HTTP客户端端,无法获取套接字fd和设置套接字选项(IP_TOS):
(我想在调用NewRequest之前设置IP_TOS)
client := &http.Client{
Transport : tr,
//Timeout: time.Duration(10) * time.Second,
}
request, err := http.NewRequest("POST", url, body)
if err != nil {
panic(err)
}
response, err := client.Do(request)
谢谢!
英文:
I am a newbie to go language. I plan to use go language for developing an http client/Server.
While browsing through the list of features supported in http client package I do not find a way to set socket option in the package
(May be I just do not know how to use it). I am in a need to set DSCP option (IP_TOS) in the fd before calling http client connection.
(Although I find syscall option to set socket options, I do not find a way to get fd from http package).
In http server side, able to set socket option (IP_TOS).
code excerpt:
tcpListener,err := net.ListenTCP("tcp4", addr)
if err != nil {
//fmt.Println("error in listen", err.error())
log.Fatal("net.ListenTCP()", err)
}
//get lisenet socket fd
f, _ := tcpListener.File()
err = syscall.SetsockoptInt(int(f.Fd()), syscall.SOL_SOCKET, syscall.IP_TOS, 128)
In http client side, not able to get socket fd and set socket option (IP_TOS):
(I want to set IP_TOS before calling NewRequest)
client := &http.Client{
Transport : tr,
//Timeout: time.Duration(10) * time.Second,
}
request, err := http.NewRequest("POST", url, body)
if err != nil {
panic(err)
}
response, err := client.Do(request)
Thanks !!
答案1
得分: 7
你可以为自己的*http.Client
创建自己的http.RoundTripper
的DialContext
:
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
err = errors.New("conn is not tcp")
return nil, err
}
f, err := tcpConn.File()
if err != nil {
return nil, err
}
err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
return nil, err
}
return conn, nil
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dial,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
c := &http.Client{
Transport: tr,
}
resp, err := c.Get("https://google.com/")
if err != nil {
log.Fatalf("GET error: %v", err)
}
log.Printf("got %q", resp.Status)
编辑: 如果你需要在连接实际发生之前设置选项,你可以在DialContext
中尝试以下代码,但这种方法不太可移植,不安全,不考虑上下文,并且可能会在以后出现问题:
tcpAddr, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, err
}
sa := &syscall.SockaddrInet4{
Port: tcpAddr.Port,
Addr: [4]byte{tcpAddr.IP[0], tcpAddr.IP[1], tcpAddr.IP[2], tcpAddr.IP[3]},
}
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
return nil, err
}
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
return nil, err
}
err = syscall.Connect(fd, sa)
if err != nil {
return nil, err
}
file := os.NewFile(uintptr(fd), "")
conn, err := net.FileConn(file)
if err != nil {
return nil, err
}
return conn, nil
编辑 2: 在Go 1.11中,你可以这样做:
dialer := &net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
log.Printf("control: %s", err)
return
}
})
},
}
// ...
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
英文:
You can create your own DialContext
for your own http.RoundTripper
of your own *http.Client
:
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
err = errors.New("conn is not tcp")
return nil, err
}
f, err := tcpConn.File()
if err != nil {
return nil, err
}
err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
return nil, err
}
return conn, nil
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dial,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
c := &http.Client{
Transport: tr,
}
resp, err := c.Get("https://google.com/")
if err != nil {
log.Fatalf("GET error: %v", err)
}
log.Printf("got %q", resp.Status)
EDIT: If you need to set the option before the connection actually happens, you can try this in your DialContext
, but this is fairly non-portable, unsafe, doesn't account for context, and will probably break sooner or later:
tcpAddr, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, err
}
sa := &syscall.SockaddrInet4{
Port: tcpAddr.Port,
Addr: [4]byte{tcpAddr.IP[0], tcpAddr.IP[1], tcpAddr.IP[2], tcpAddr.IP[3]},
}
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
return nil, err
}
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
return nil, err
}
err = syscall.Connect(fd, sa)
if err != nil {
return nil, err
}
file := os.NewFile(uintptr(fd), "")
conn, err := net.FileConn(file)
if err != nil {
return nil, err
}
return conn, nil
EDIT 2: In Go 1.11 you'll be able to do this:
dialer := &net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
if err != nil {
log.Printf("control: %s", err)
return
}
})
},
}
// ...
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论