go - 如何使用 Go 的 net.TCPConn 处理客户端并发
问题描述
我有一个通过 TCP 通信的旧服务器。我写了Client
处理通信。它可以通过命令行在单线程环境中正常工作,但是在处理并发调用时,由于单个共享的 TCPConn,它显然会失败。
会话在此旧服务器上的工作方式,我必须执行以下步骤:
- 打开 TCPConn
- 登录(与服务器进行身份验证)
- 调用多个单独的命令(即 AddUser、GroupAdd) - 可能有很多而且速度很慢。
- 登出
- 关闭 TCPConn
尽管有一个 sessionID,但这一切都必须通过同一个 TCP 连接发生。
这已经工作了很多年,但是现在 API 服务器变得越来越忙,如果客户端同时访问 API,TCPConn 上的通信就会变得混乱。这很明显为什么会发生这种情况。现在我正在寻找一个理想的解决方案来防止它发生。
我的第一个想法是像 SQL 驱动程序那样透明地拥有一个连接池。这里的问题是,我相信每个 SQL Query()
、QueryRow()
等都可以独立运行,因此驱动程序可以从连接池中获取 TCPConn、查询,然后将 TCPConn 返回到池中。不过,就我而言,我需要在多个查询的生命周期中保留 TCPConn。因此,它不能真正透明,因为我需要在每个服务中手动将 TCPConn 返回到池中,因为客户端无法在每次查询后返回它。
第二个想法,似乎有点粗俗,是将所有逻辑放在每个处理程序中(获取 conn,实例化Client
并实例化一个或多个Service
(s))。这似乎是多余的,并且随着处理程序数量的不断增长,它只是大量的样板。这将在每个处理程序中从池中释放一个 conn 以及在每个处理程序中释放一个Client
和Service
,然后在最后将 conn 返回到池中。
这是下面的示例代码。
client.go
package whatever
import (
"fmt"
"log"
"net"
"net/http"
)
// Response from Client
type Response []byte
type Client struct {
conn *net.TCPConn
username string
password string
sessionID string
}
// Open and Close the TCPConn
func (c *Client) open() error { return nil }
func (c *Client) close() error { return nil }
// Login and Logout will call open and close respectively. Login calls Run()
// with user/pass and gets back a sessionID which is set on the struct.
func (c *Client) Login() error { return nil }
func (c *Client) Logout() error { return nil }
// Run sends commands to the server over the Client.conn and returns a Repsonse
// byte slice.
func (c *Client) Run(cmd string) (Response, error) { return Response{}, nil }
service.go
// UserService provides services to act on users
type UserService struct {
client *Client
}
// AddUserGroup adds a user and then adds them to GroupA.
func (s *UserService) AddUserGroup() error {
err := s.Client.Login()
if err != nil {
return err
}
defer s.Client.Logout()
resp, err := s.Client.Run("CreateUser Bob 46 'Los Angeles'")
if err != nil {
return err
}
userID := resp[1]
cmd := fmt.Sprintf("GroupAdd %v GroupA", userID)
resp, _ := s.Client.Run(cmd)
if err != nil {
return err
}
return nil
}
this-works-command-line.go
func main() {
c := Client{username: "user", "password": "pass"}
svc := UserService{client: &c}
err := svc.AddUserGroup()
if err != nil {
log.Fatal(err)
}
}
this-does-not-work-api.go
type Server struct {
Svc *UserService
}
func (s *Server) AddUserGroupHandler() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := s.Svc.AddUserGroup()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
}
func main() {
c := &Client{username: "user", password: "pass"}
svc := &UserService{client: c}
server := Server{Svc: svc}
err := svc.AddUserGroup()
if err != nil {
log.Fatal(err)
}
http.Handle("/foo", server.AddUserGroupHandler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
同样,如果该端点被多次命中,则将使用相同的 TCPConn 并将调用Login()
或在处理前一个调用时调用其他东西。
我似乎找不到一个共同的模式来处理这个问题。我最后的想法是只接受传入的请求并将其保存在 DB 或 redis 中,然后让工作人员服务将其接收。
也许我只是没有使用正确的术语进行搜索。
任何帮助表示赞赏。
解决方案
推荐阅读
- swift - UIImageView 没有在 UITableViewCell 中塑造成正确的圆形
- pandas - 如何在 pandas 中使用 pivot_table 索引数据?
- swift - “Thread1:致命错误:在展开可选值时意外发现 nil”
- javascript - 有没有办法禁用 Select 2 的选项,它的值大于 Select 1 中选择的选项的值
- docusignapi - 预填充的锚标签宽度高度问题
- json - 在单个键上合并同一数组中的对象
- snowflake-cloud-data-platform - 雪花选择查询以 json 格式返回数据
- python - 预测XGboost时python中的“列表索引超出范围”错误
- python - 使用 AWS API Gateway 进行 JWT 身份验证和转发
- sql - 查询只追加表;不同的内部连接问题