- 内网服务器是一个只能通信外网,不对外开放任何端口
- 服务端的端口号是部署在外网的
- 服务端如何告诉内网服务器消息
简单来说,就是服务端使用一个长连接的socket,然后内网服务器连接这个IP+端口号进行长连接。
服务端发现有人来连接,就可以通过这个连接进行消息发送
以下是让AI写的一个tcp+http服务的代码
package main
import (
"bufio"
"fmt"
"log"
"net"
"net/http"
"sync"
"time"
)
// Client 代表一个连接的客户端
type Client struct {
ID string
Conn net.Conn
Outgoing chan []byte // 用于向客户端发送数据
}
// 全局客户端管理器
var (
clients = make(map[string]*Client)
clientsMu sync.RWMutex
nextID = 1
nextIDMu sync.Mutex
)
// 生成唯一客户端 ID
func generateClientID() string {
nextIDMu.Lock()
id := fmt.Sprintf("client-%d", nextID)
nextID++
nextIDMu.Unlock()
return id
}
// TCP 服务端:处理客户端连接
func tcpServer() {
listener, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatal("TCP server failed:", err)
}
defer listener.Close()
log.Println("TCP server listening on :8081")
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Accept error:", err)
continue
}
clientID := generateClientID()
client := &Client{
ID: clientID,
Conn: conn,
Outgoing: make(chan []byte, 100), // 带缓冲,避免阻塞
}
// 注册客户端
clientsMu.Lock()
clients[clientID] = client
clientsMu.Unlock()
log.Printf("New client connected: %s\n", clientID)
// 启动读写 goroutine
go handleClientRead(client)
go handleClientWrite(client)
}
}
// 从客户端读取数据(可选:用于心跳或日志)
func handleClientRead(client *Client) {
reader := bufio.NewReader(client.Conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
log.Printf("Client %s disconnected: %v", client.ID, err)
cleanupClient(client)
return
}
log.Printf("Received from %s: %s", client.ID, msg)
}
}
// 向客户端写入数据
func handleClientWrite(client *Client) {
defer func() {
client.Conn.Close()
}()
for msg := range client.Outgoing {
_, err := client.Conn.Write(msg)
if err != nil {
log.Printf("Write to %s failed: %v", client.ID, err)
cleanupClient(client)
return
}
}
}
// 清理断开的客户端
func cleanupClient(client *Client) {
clientsMu.Lock()
delete(clients, client.ID)
close(client.Outgoing)
clientsMu.Unlock()
log.Printf("Client %s removed", client.ID)
}
// HTTP 服务:提供 Web 页面和 API
func httpServer() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
clientsMu.RLock()
defer clientsMu.RUnlock()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>TCP Client Control</title></head>
<body>
<h2>Send Command to Client</h2>
<form method="POST" action="/send">
<label>Client:
<select name="client_id" required>
<option value="">-- Select --</option>
%s
</select>
</label><br><br>
<label>Command:<br>
<textarea name="command" rows="4" cols="50" placeholder="Enter command (will append \\n)" required></textarea>
</label><br><br>
<button type="submit">Send</button>
</form>
</body>
</html>
`, buildClientOptions())
})
http.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
clientID := r.FormValue("client_id")
command := r.FormValue("command")
if clientID == "" || command == "" {
http.Error(w, "Missing client_id or command", http.StatusBadRequest)
return
}
clientsMu.RLock()
client, exists := clients[clientID]
clientsMu.RUnlock()
if !exists {
http.Error(w, "Client not found", http.StatusNotFound)
return
}
// 发送命令(确保以 \n 结尾,便于客户端读取)
if command[len(command)-1] != '\n' {
command += "\n"
}
select {
case client.Outgoing <- []byte(command):
log.Printf("Sent to %s: %s", clientID, command)
fmt.Fprintf(w, "✅ Sent to %s: %s", clientID, command)
default:
http.Error(w, "Client send buffer full", http.StatusInternalServerError)
}
})
log.Println("HTTP server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 构建客户端下拉选项
func buildClientOptions() string {
clientsMu.RLock()
defer clientsMu.RUnlock()
var options string
for id := range clients {
options += fmt.Sprintf("<option value=\"%s\">%s</option>\n", id, id)
}
return options
}
func main() {
go tcpServer()
httpServer()
}
浙公网安备 33010602011771号