AHK v1 做本机进程通信、局域网工具或自定义 TCP 协议时,最麻烦的通常不是发送一段文本,而是 Winsock 初始化、地址解析、连接、收发、异步消息和资源释放这些细节。Socket_v1 把这些基础工作封装成客户端、服务端和缓冲区对象。
这个库源自 thqby 的 Socket 实现,由 dbgba 移植到 AHK v1,底层使用 Windows 自带的 Winsock 2.2,不需要单独安装网络组件。
主要能力
- 创建 TCP 客户端和监听服务端,并接受连接。
- 同步发送、接收 Buffer,或直接使用 UTF-8 等编码收发文本。
- 通过
onAccept、onRead、onConnect、onClose等回调处理异步事件。 - 封装地址解析、Socket 错误信息、消息长度查询和资源清理。
- 提供
StartTLS()接口与外部 TLS 包装对象衔接。
客户端、服务端和 Buffer
Socket.Client 负责主动连接,Socket.Server 负责监听端口,服务端收到连接后通过 AcceptAsClient() 得到可收发数据的客户端对象。文本协议可以先从 SendText()、RecvText() 入门;二进制协议则使用 Socket.Buffer 和 Send()、Recv()。
库同时支持同步和 Windows 消息驱动的异步模式。同步模式更容易理解,适合短连接和一次性工具;GUI、长连接或需要同时处理多个连接时,再使用异步回调。
示例做了什么
示例全部使用 127.0.0.1,先在 19241 端口演示同步收发,再在 19242 端口演示 onAccept 和 onRead 回调。服务端收到文本后加上 echo: 返回,适合用来理解一条 TCP 消息在两端如何流动。
示例代码:Socket_v1_示例.ahk
库源码:Socket_v1.ahk
; @description simple implementation of a socket Server and Client.
; @author thqby (Original v2) / dbgba (Ported to v1)
; @date 2025/05/29
; @version 1.0.8
; @note Converted for AutoHotkey v1 compatibility by dbgba.
; Original source: https://github.com/thqby/ahk2_lib
Socket_v1_OnMessage(wParam, lParam, msg, hwnd) {
table := Socket._SocketTable()
if (!table.HasKey(wParam))
return 0
sock := table[wParam]
if (!IsObject(sock))
return 0
ev := lParam & 0xFFFF
err := (lParam >> 16) & 0xFFFF
if (ev = 1) {
sock._AsyncSelect(1, -1)
try sock.OnRead(err)
catch e
Socket._ReportCallbackError(sock, "OnRead", e)
try sock._AsyncSelect(1)
catch e
Socket._ReportCallbackError(sock, "OnRead monitor", e)
} else if (ev = 2) {
try sock.OnWrite(err)
catch e
Socket._ReportCallbackError(sock, "OnWrite", e)
} else if (ev = 4) {
try sock.OnOOB(err)
catch e
Socket._ReportCallbackError(sock, "OnOOB", e)
} else if (ev = 8) {
try sock.OnAccept(err)
catch e
Socket._ReportCallbackError(sock, "OnAccept", e)
} else if (ev = 16) {
try sock.OnConnect(err)
catch e
Socket._ReportCallbackError(sock, "OnConnect", e)
} else if (ev = 32) {
try sock.OnClose(err)
catch e
Socket._ReportCallbackError(sock, "OnClose", e)
} else if (ev = 64) {
try sock.OnQOS(err)
catch e
Socket._ReportCallbackError(sock, "OnQOS", e)
}
return 0
}
class Socket {
static TYPE := {STREAM: 1, DGRAM: 2, RAW: 3, RDM: 4, SEQPACKET: 5}
static AF := {UNSPEC: 0, UNIX: 1, INET: 2, IPX: 6, APPLETALK: 16, NETBIOS: 17, INET6: 23, IRDA: 26, BTH: 32}
static IPPROTO := {ICMP: 1, IGMP: 2, RFCOMM: 3, TCP: 6, UDP: 17, ICMPV6: 58, RM: 113}
static MSG := {OOB: 1, PEEK: 2, DONTROUTE: 4, WAITALL: 8, INTERRUPT: 0x10, PUSH_IMMEDIATE: 0x20, PARTIAL: 0x8000}
_State() {
static state
if (!IsObject(state)) {
state := Object()
state.WmSocket := 0
state.WsaStarted := 0
state.MonitorCount := 0
state.SocketTable := Object()
}
return state
}
Init() {
state := Socket._State()
if (state.WsaStarted)
return
h := DllCall("LoadLibrary", "Str", "ws2_32.dll", "Ptr")
VarSetCapacity(WSAData, 408, 0)
err := DllCall("ws2_32\WSAStartup", "UShort", 0x0202, "Ptr", &WSAData, "Int")
if (err)
throw Socket._Error(err, -1)
if (NumGet(WSAData, 2, "UShort") != 0x0202)
throw Exception("Winsock version 2.2 not available", -1)
state.WsaStarted := 1
}
Cleanup() {
state := Socket._State()
if (state.WsaStarted) {
DllCall("ws2_32\WSACleanup")
state.WsaStarted := 0
}
}
GetLastError() {
return DllCall("ws2_32\WSAGetLastError", "Int")
}
_Error(err := "", what := -1) {
if (err = "")
err := Socket.GetLastError()
msg := Socket._FormatError(err)
if (msg = "")
msg := "Winsock error " err
return Exception(msg, what, err)
}
_ReportCallbackError(sock, stage, e) {
local msg
msg := Socket._ErrorMessage(e)
OutputDebug, % "socket " stage ": " msg
}
_ErrorMessage(e) {
local msg
if (!IsObject(e))
return e
try msg := e.Message
catch ex
msg := ""
if (msg = "")
msg := "<no message>"
return msg
}
_FormatError(err) {
pstr := 0
if (DllCall("FormatMessage", "UInt", 0x1300, "Ptr", 0, "UInt", err, "UInt", 0, "Ptr*", pstr, "UInt", 0, "Ptr", 0) && pstr) {
msg := StrGet(pstr)
DllCall("LocalFree", "Ptr", pstr)
return RTrim(msg, "`r`n ")
}
return ""
}
_WM_SOCKET() {
state := Socket._State()
if (!state.WmSocket)
state.WmSocket := DllCall("RegisterWindowMessage", "Str", "WM_AHK_SOCKET", "UInt")
return state.WmSocket
}
_SocketTable() {
return Socket._State().SocketTable
}
_EventNameToFlag(name) {
StringLower, name, name
if (name = "onread")
return 1
if (name = "onoob")
return 4
if (name = "onaccept")
return 8
if (name = "onconnect")
return 16
if (name = "onclose")
return 32
if (name = "onqos")
return 64
return 0
}
_HasHandler(obj, eventName) {
name := SubStr(eventName, 1, 2) = "On" ? "on" SubStr(eventName, 3) : eventName
if (obj.HasKey(name)) {
handler := obj[name]
if (IsObject(handler))
return 1
if (handler != "" && IsFunc(handler))
return 1
}
try className := obj.__Class
catch e
className := ""
if (className != "" && className != "Socket.base" && className != "Socket.Client" && className != "Socket.Server" && className != "Socket.AcceptedClient") {
if (IsFunc(className "." name) || IsFunc(className "." eventName))
return 1
}
return 0
}
_CallHandler(obj, name, args*) {
local handler, className, bound
try handler := obj[name]
catch e
handler := ""
if (IsObject(handler))
return handler.Call(obj, args*)
if (handler != "" && IsFunc(handler))
return %handler%(obj, args*)
try className := obj.__Class
catch e
className := ""
if (className != "" && className != "Socket.base" && className != "Socket.Client" && className != "Socket.Server" && className != "Socket.AcceptedClient" && IsFunc(className "." name)) {
bound := ObjBindMethod(obj, name)
return bound.Call(args*)
}
}
_Ptr(value) {
if IsObject(value) {
try return value.Ptr + 0
try return value.ptr + 0
}
return value + 0
}
_Size(value) {
if IsObject(value) {
try return value.Size + 0
try return value.size + 0
}
return 0
}
_WSAAddressToString(addr, addrlen) {
VarSetCapacity(buf, 4096, 0)
s := 2048
if (!DllCall("ws2_32\WSAAddressToStringW", "Ptr", addr, "UInt", addrlen, "Ptr", 0, "Ptr", &buf, "UInt*", s, "Int"))
return StrGet(&buf)
return ""
}
_AddrInfoFromPtr(ptr) {
if (!ptr)
return 0
ai := new Socket.AddrInfo()
ai.ptr := ptr
ai._owns := false
return ai
}
class Buffer {
__New(size := 0, fill := "") {
if (size < 0)
size := 0
capacity := size ? size : 1
this.SetCapacity("__buf", capacity)
ObjRawSet(this, "__capacity", capacity)
ObjRawSet(this, "__size", size)
if (size && fill != "")
DllCall("RtlFillMemory", "Ptr", this.Ptr, "UPtr", size, "UChar", fill)
}
Ptr {
get {
return this.GetAddress("__buf")
}
}
Size {
get {
return ObjRawGet(this, "__size")
}
set {
if (value < 0)
value := 0
capacity := ObjRawGet(this, "__capacity")
if (value > capacity) {
this.SetCapacity("__buf", value)
ObjRawSet(this, "__capacity", value)
}
ObjRawSet(this, "__size", value)
return value
}
}
}
class AddrInfo {
__New(host := "", port := 0, family := 0) {
Socket.Init()
this.ptr := 0
this._owns := true
this._buf := ""
if (host = "" && port = "") {
} else if (family != 1) {
hintsPtr := 0
if (family) {
VarSetCapacity(hints, 48, 0)
NumPut(family, hints, 4, "Int")
hintsPtr := &hints
}
addr := 0
err := DllCall("ws2_32\GetAddrInfoW", "WStr", host, "WStr", "" port, "Ptr", hintsPtr, "Ptr*", addr, "Int")
if (err)
throw Socket._Error(err, -1)
this.ptr := addr
} else {
VarSetCapacity(buf, 158, 0)
NumPut(1, buf, 48, "UShort")
StrPut(host, &buf + 50, "CP0")
NumPut(0, buf, 0, "Int")
NumPut(1, buf, 4, "Int")
NumPut(0, buf, 8, "Int")
NumPut(0, buf, 12, "Int")
NumPut(110, buf, 16, "UPtr")
NumPut(0, buf, 16 + A_PtrSize, "Ptr")
NumPut(&buf + 48, buf, 16 + 2 * A_PtrSize, "Ptr")
this._buf := buf
this.ptr := this.GetAddress("_buf")
}
}
__Delete() {
if (this._owns && this.ptr && this.family != 1)
DllCall("ws2_32\FreeAddrInfoW", "Ptr", this.ptr)
}
flags {
get {
return NumGet(this.ptr + 0, 0, "Int")
}
}
family {
get {
return NumGet(this.ptr + 0, 4, "Int")
}
}
socktype {
get {
return NumGet(this.ptr + 0, 8, "Int")
}
}
protocol {
get {
return NumGet(this.ptr + 0, 12, "Int")
}
}
addrlen {
get {
return NumGet(this.ptr + 0, 16, "UPtr")
}
}
canonname {
get {
p := NumGet(this.ptr + 0, 16 + A_PtrSize, "Ptr")
return p ? StrGet(p) : ""
}
}
addr {
get {
return NumGet(this.ptr + 0, 16 + 2 * A_PtrSize, "Ptr")
}
}
next {
get {
p := NumGet(this.ptr + 0, 16 + 3 * A_PtrSize, "Ptr")
return Socket._AddrInfoFromPtr(p)
}
}
addrstr {
get {
if (this.family = 1)
return StrGet(this.addr + 2, "CP0")
return Socket._WSAAddressToString(this.addr, this.addrlen)
}
}
}
class base {
__New(family := "", socktype := "", protocol := "") {
Socket.Init()
this.Ptr := -1
this.addr := ""
this._monitorFlags := 0
if (family != "")
this._CreateSocket(family, socktype, protocol)
}
__Delete() {
this.Close()
}
__Set(name, value) {
flag := Socket._EventNameToFlag(name)
if (flag) {
if (!IsObject(value) && value != "" && IsFunc(value))
value := Func(value)
ObjRawSet(this, name, value)
this.UpdateMonitoring()
return value
}
ObjRawSet(this, name, value)
return value
}
_CreateSocket(family, socktype, protocol) {
if (this.Ptr != -1)
this.Close()
this.Ptr := DllCall("ws2_32\socket", "Int", family, "Int", socktype, "Int", protocol, "Ptr")
if (this.Ptr = -1)
throw Socket._Error(Socket.GetLastError())
}
Close() {
if (this.Ptr = -1 || this.Ptr = "")
return
this.UpdateMonitoring(-1)
DllCall("ws2_32\closesocket", "Ptr", this.Ptr, "Int")
this.Ptr := -1
}
Send(addrInfo, buf, size := "") {
sz := this._send(addrInfo, buf, size)
if (sz < 0)
throw Socket._Error(Socket.GetLastError())
return sz
}
_send(addrInfo, buf, size := "") {
ptr := Socket._Ptr(buf)
if (size = "")
size := Socket._Size(buf)
return DllCall("ws2_32\sendto", "Ptr", this.Ptr, "Ptr", ptr, "Int", size, "Int", 0, "Ptr", addrInfo.addr, "Int", addrInfo.addrlen, "Int")
}
_recv(buf, size := "", flags := 0, from := 0, ByRef len := 0) {
ptr := Socket._Ptr(buf)
if (size = "")
size := Socket._Size(buf)
if (from)
sz := DllCall("ws2_32\recvfrom", "Ptr", this.Ptr, "Ptr", ptr, "Int", size, "Int", flags, "Ptr", from, "Int*", len, "Int")
else
sz := DllCall("ws2_32\recvfrom", "Ptr", this.Ptr, "Ptr", ptr, "Int", size, "Int", flags, "Ptr", 0, "Ptr", 0, "Int")
if (sz < 0)
throw Socket._Error(Socket.GetLastError())
return sz
}
GetLastError() {
return Socket.GetLastError()
}
MsgSize(timeout := 0) {
FIONBIO := 0x8004667E
FIONREAD := 0x4004667F
wasAsync := this.HasKey("_async_select")
if (!wasAsync) {
one := 1
if (timeout || timeout := 0xFFFFFFFF)
DllCall("ws2_32\ioctlsocket", "Ptr", this.Ptr, "UInt", FIONBIO, "UInt*", one, "Int")
}
endTick := A_TickCount + timeout
size := 0
Loop {
DllCall("ws2_32\ioctlsocket", "Ptr", this.Ptr, "UInt", FIONREAD, "UInt*", size, "Int")
if (size)
break
if (A_TickCount >= endTick)
break
if (this._IsClosed())
break
Sleep, 10
}
if (!wasAsync) {
zero := 0
DllCall("ws2_32\ioctlsocket", "Ptr", this.Ptr, "UInt", FIONBIO, "UInt*", zero, "Int")
}
return size
}
_IsClosed() {
VarSetCapacity(peek, 1, 0)
r := DllCall("ws2_32\recv", "Ptr", this.Ptr, "Ptr", &peek, "Int", 1, "Int", 2, "Int")
if (r = 0)
return true
if (r < 0) {
err := Socket.GetLastError()
return (err != 10035)
}
return false
}
UpdateMonitoring(start := true) {
state := Socket._State()
flags := 0
if (start > 0) {
if (Socket._HasHandler(this, "onRead"))
flags |= 1
if (Socket._HasHandler(this, "onOOB"))
flags |= 4
if (Socket._HasHandler(this, "onAccept"))
flags |= 8
if (Socket._HasHandler(this, "onConnect"))
flags |= 16
if (Socket._HasHandler(this, "onClose"))
flags |= 32
if (Socket._HasHandler(this, "onQOS"))
flags |= 64
}
if (flags) {
table := Socket._SocketTable()
if (!state.MonitorCount)
OnMessage(Socket._WM_SOCKET(), "Socket_v1_OnMessage", 255)
if (!table.HasKey(this.Ptr)) {
state.MonitorCount += 1
}
table[this.Ptr] := this
ObjRawSet(this, "_async_select", true)
this._DefineAsyncMethods()
this._AsyncSelect(flags, 0)
return
}
if (this.HasKey("_async_select"))
this.Delete("_async_select")
this._AsyncSelect(0, 0)
table := Socket._SocketTable()
if (table.HasKey(this.Ptr)) {
table.Delete(this.Ptr)
state.MonitorCount -= 1
if (state.MonitorCount <= 0) {
state.MonitorCount := 0
OnMessage(Socket._WM_SOCKET(), "")
}
}
if (start > -1) {
zero := 0
DllCall("ws2_32\ioctlsocket", "Ptr", this.Ptr, "UInt", 0x8004667E, "UInt*", zero, "Int")
}
}
_AsyncSelect(newFlags, mode := 1) {
flags := this.HasKey("_monitorFlags") ? this._monitorFlags : 0
if (mode > 0)
newFlags := flags | newFlags
else if (mode < 0)
newFlags := flags & ~newFlags
if (mode && flags = newFlags)
return
if (DllCall("ws2_32\WSAAsyncSelect", "Ptr", this.Ptr, "Ptr", A_ScriptHwnd, "UInt", Socket._WM_SOCKET(), "UInt", newFlags, "Int"))
throw Socket._Error(Socket.GetLastError())
this._monitorFlags := newFlags
}
OnRead(err) {
return Socket._CallHandler(this, "onRead", err)
}
OnOOB(err) {
return Socket._CallHandler(this, "onOOB", err)
}
OnAccept(err) {
return Socket._CallHandler(this, "onAccept", err)
}
OnConnect(err) {
return Socket._CallHandler(this, "onConnect", err)
}
OnClose(err) {
return Socket._CallHandler(this, "onClose", err)
}
OnQOS(err) {
return Socket._CallHandler(this, "onQOS", err)
}
OnWrite(err) {
if (!this.HasKey("_queue"))
return
queue := this._queue
index := queue.index
max := queue.MaxIndex()
while (++index <= max) {
params := queue[index]
queue.Delete(index)
sz := this._send(params[1], params[2])
if (sz < 0) {
queue[index] := params
err := Socket.GetLastError()
break
}
}
if (--index && index >= (queue.MaxIndex() >> 1)) {
queue.RemoveAt(1, index)
queue.index := 0
} else {
queue.index := index
}
if (err && err != 10035)
throw Socket._Error(err)
}
_DefineAsyncMethods() {
return 0
}
}
class Client extends Socket.base {
__New(host, port := "", socktype := 1, protocol := 0) {
Socket.Init()
this.Ptr := -1
this.addr := ""
this._monitorFlags := 0
this.isConnected := 1
if IsObject(host)
this.addrinfo := host
else
this.addrinfo := new Socket.AddrInfo(host, port, port = "" ? 1 : 0)
this._socktype := socktype
this._protocol := protocol
err := this.ReConnect(false)
if (err && err != 10035)
throw Socket._Error(err, -1)
}
ReConnect(next := false) {
this.isConnected := 0
if (!next) {
this._ai := this.addrinfo
this._last_family := -1
} else {
this._ai := IsObject(this._ai) ? this._ai.next : 0
}
Loop {
ai := this._ai
if (!IsObject(ai))
return 10061
if (this._last_family != ai.family && this.Ptr != -1)
this.Close()
if (this.Ptr = -1) {
this.Ptr := DllCall("ws2_32\socket", "Int", ai.family, "Int", this._socktype, "Int", this._protocol, "Ptr")
if (this.Ptr = -1)
return Socket.GetLastError()
this._last_family := ai.family
}
this.addr := ai.addrstr
if (Socket._HasHandler(this, "onConnect"))
this.UpdateMonitoring()
if (!DllCall("ws2_32\connect", "Ptr", this.Ptr, "Ptr", ai.addr, "UInt", ai.addrlen, "Int")) {
this.UpdateMonitoring()
this.isConnected := 1
return 0
}
err := Socket.GetLastError()
if (err = 10035)
return err
this._ai := ai.next
if (this.Ptr != -1)
this.Close()
}
}
OnConnect(err) {
if (!err) {
this.isConnected := 1
return Socket._CallHandler(this, "onConnect", err)
}
if (err = 10061) {
err := this.ReConnect(true)
if (err = 10035)
return
}
throw Socket._Error(err)
}
_DefineAsyncMethods() {
if (this.HasKey("_queue"))
return
queue := []
queue.index := 0
this._queue := queue
}
_send(buf, size := "") {
ptr := Socket._Ptr(buf)
if (size = "")
size := Socket._Size(buf)
return DllCall("ws2_32\send", "Ptr", this.Ptr, "Ptr", ptr, "Int", size, "Int", 0, "Int")
}
Send(buf, size := "") {
if (this.HasKey("_queue")) {
queue := this._queue
if (!queue.MaxIndex()) {
sz := this._send(buf, size)
if (sz < 0) {
err := Socket.GetLastError()
if (err != 10035)
throw Socket._Error(err)
this._AsyncSelect(2)
} else {
return sz
}
}
queue.Push([buf, size])
return 0
}
sz := this._send(buf, size)
if (sz < 0)
throw Socket._Error(Socket.GetLastError())
return sz
}
SendText(text, encoding := "UTF-8") {
StringUpper, encKey, encoding
isUtf16 := (encKey = "UTF-16" || encKey = "CP1200")
need := StrPut(text, encoding)
size := isUtf16 ? (need - 1) * 2 : need - 1
capacity := isUtf16 ? need * 2 : need
buf := new Socket.Buffer(capacity)
buf.Size := size
StrPut(text, buf.Ptr, need, encoding)
return this.Send(buf, size)
}
_recv(buf, size := "", flags := 0) {
ptr := Socket._Ptr(buf)
if (size = "")
size := Socket._Size(buf)
sz := DllCall("ws2_32\recv", "Ptr", this.Ptr, "Ptr", ptr, "Int", size, "Int", flags, "Int")
if (sz < 0)
throw Socket._Error(Socket.GetLastError())
return sz
}
Recv(timeout := 0, flags := 0) {
size := this.MsgSize(timeout)
if (!size)
return 0
buf := new Socket.Buffer(size)
this._recv(buf, size, flags)
return buf
}
RecvText(encoding := "UTF-8", timeout := 0, flags := 0) {
buf := this.Recv(timeout, flags)
if IsObject(buf)
return StrGet(buf.Ptr, buf.Size, encoding)
return ""
}
StartTLS(tls) {
return tls.wrapSocket(this)
}
}
class Server extends Socket.base {
__New(port := "", host := "0.0.0.0", socktype := 1, protocol := 0, backlog := 4) {
Socket.Init()
this.Ptr := -1
this.addr := ""
this._monitorFlags := 0
ai := new Socket.AddrInfo(host, port, port = "" ? 1 : 0)
this.addrinfo := ai
last_family := -1
ptr := -1
Loop {
if (!IsObject(ai))
break
if (last_family != ai.family) {
if (ptr != -1) {
DllCall("ws2_32\closesocket", "Ptr", ptr, "Int")
this.Ptr := -1
}
ptr := DllCall("ws2_32\socket", "Int", ai.family, "Int", socktype, "Int", protocol, "Ptr")
if (ptr = -1) {
ai := ai.next
continue
}
last_family := ai.family
this.Ptr := ptr
}
reuse := 1
DllCall("ws2_32\setsockopt", "Ptr", ptr, "Int", 0xFFFF, "Int", 4, "Int*", reuse, "Int", 4, "Int")
if (!DllCall("ws2_32\bind", "Ptr", ptr, "Ptr", ai.addr, "UInt", ai.addrlen, "Int")
&& !DllCall("ws2_32\listen", "Ptr", ptr, "Int", backlog, "Int")) {
this.addr := ai.addrstr
this.UpdateMonitoring()
this._listenOK := true
break
}
ai := ai.next
}
if (this._listenOK)
return this
throw Socket._Error(Socket.GetLastError(), -1)
}
_accept(ByRef addr := "") {
addrlen := 128
VarSetCapacity(addrbuf, addrlen, 0)
ptr := DllCall("ws2_32\accept", "Ptr", this.Ptr, "Ptr", &addrbuf, "Int*", addrlen, "Ptr")
if (ptr = -1)
throw Socket._Error(Socket.GetLastError())
if (NumGet(addrbuf, 0, "UShort") != 1)
addr := Socket._WSAAddressToString(&addrbuf, addrlen)
else
addr := this.addr
return ptr
}
AcceptAsClient(clientType := "") {
ptr := this._accept(addr)
sock := new Socket.AcceptedClient(ptr, addr)
sock.UpdateMonitoring()
return sock
}
}
class AcceptedClient extends Socket.Client {
__New(ptr, addr := "") {
Socket.Init()
this.Ptr := ptr
this.addr := addr
this._monitorFlags := 0
this.isConnected := 1
this._socktype := 1
this._protocol := 0
}
}
}
需要先说清楚的边界
这是 Socket 传输层封装,不会自动提供 HTTP、WebSocket、JSON 消息边界或 TLS 证书验证逻辑。TCP 也是字节流,正式协议必须自行设计长度、分隔符或消息头,不能假设一次 Send 永远对应一次 Recv。
StartTLS() 需要传入外部 TLS 包装对象,库本身不包含完整 TLS 实现。把服务端开放到局域网或公网前,也要自行处理鉴权、输入长度、异常连接和防火墙规则。

评论(0)