Golang中HTTP客户端端口复用的实现方法
Golang中HTTP客户端端口复用的实现方法
是否有办法为HTTP请求复用某个特定端口?
或者限制使用的本地端口数量,例如100个?
2 回复
在Golang中,可以通过自定义Transport的DialContext来实现HTTP客户端的端口复用或限制本地端口数量。以下是两种实现方式:
1. 复用特定端口
package main
import (
"context"
"fmt"
"net"
"net/http"
"time"
)
func main() {
// 创建自定义Transport
transport := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 指定本地地址和端口
localAddr, _ := net.ResolveTCPAddr("tcp", "192.168.1.100:8080")
dialer := &net.Dialer{
LocalAddr: localAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
return dialer.DialContext(ctx, network, addr)
},
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
resp, err := client.Get("http://example.com")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}
2. 限制本地端口数量(100个端口池)
package main
import (
"context"
"fmt"
"net"
"net/http"
"sync"
"time"
)
type PortPool struct {
ports chan string
basePort int
maxPort int
}
func NewPortPool(basePort, maxPort, poolSize int) *PortPool {
pool := &PortPool{
ports: make(chan string, poolSize),
basePort: basePort,
maxPort: maxPort,
}
// 初始化端口池
for i := 0; i < poolSize; i++ {
port := basePort + (i % (maxPort - basePort + 1))
pool.ports <- fmt.Sprintf(":%d", port)
}
return pool
}
func (p *PortPool) GetPort() string {
return <-p.ports
}
func (p *PortPool) ReturnPort(port string) {
p.ports <- port
}
func main() {
// 创建端口池:从10000到10099共100个端口
portPool := NewPortPool(10000, 10099, 100)
transport := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 从端口池获取端口
port := portPool.GetPort()
defer portPool.ReturnPort(port)
localAddr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0"+port)
dialer := &net.Dialer{
LocalAddr: localAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
return dialer.DialContext(ctx, network, addr)
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
resp, err := client.Get("http://httpbin.org/get")
if err != nil {
fmt.Printf("Request %d failed: %v\n", i, err)
return
}
defer resp.Body.Close()
fmt.Printf("Request %d completed with status: %s\n", i, resp.Status)
}(i)
}
wg.Wait()
}
3. 使用连接池限制并发连接数
package main
import (
"context"
"fmt"
"net"
"net/http"
"sync"
"time"
)
func main() {
// 限制最多使用100个不同的本地端口
var mu sync.Mutex
portCounter := 0
maxPorts := 100
basePort := 50000
transport := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
mu.Lock()
port := basePort + (portCounter % maxPorts)
portCounter++
mu.Unlock()
localAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", port))
dialer := &net.Dialer{
LocalAddr: localAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
return dialer.DialContext(ctx, network, addr)
},
MaxConnsPerHost: 100, // 限制每个主机的最大连接数
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
}
client := &http.Client{
Transport: transport,
}
// 并发测试
var wg sync.WaitGroup
for i := 0; i < 200; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
resp, err := client.Get(fmt.Sprintf("http://httpbin.org/delay/%d", id%3))
if err != nil {
fmt.Printf("Request %d failed: %v\n", id, err)
return
}
defer resp.Body.Close()
fmt.Printf("Request %d: %s\n", id, resp.Status)
}(i)
}
wg.Wait()
}
这些实现方式通过自定义DialContext来控制HTTP客户端使用的本地端口。第一种方法固定使用特定端口,第二种方法使用端口池管理100个端口,第三种方法通过计数器循环使用100个端口范围。可以根据具体需求选择合适的实现方式。


