有时我需要的不是在现有 HTTP 服务里增加长连接,而是让 AHK 直接连接一个 WebSocket 地址,或者临时启动一个服务端做消息转发和协议测试。WebSockets_v1 把客户端和服务端放在同一套接口里,适合这种更独立的通信场景。

这套库面向 AutoHotkey v1,底层依赖 Socket_v1。它实现了 ws 握手、文本和二进制帧、客户端掩码、分片消息、Ping/Pong 与关闭流程。需要先说明的是:当前版本只接受 ws:// 地址,不支持 wss://,因此更适合本机、受控局域网和开发测试。

客户端怎么用

new WebSockets.Client(url) 会建立 TCP 连接并完成 WebSocket 握手,连接成功后 readyState 为 1。onMessage 接收文本,onData 接收 Buffer,SendText() 和 SendData() 分别发送两种数据,WsClose() 用于按协议关闭连接。

构造函数还可以附加请求头,适合传递已有服务要求的简单标识。不过自定义请求头不等于安全认证,密钥和令牌仍应妥善管理,不能硬编码在准备公开的脚本里。

服务端怎么用

WebSockets.Server 监听指定主机和端口,完成握手后通过 onClientConnect 交出客户端对象。每个连接都可以单独设置 onMessage 和 onData,实现回声、广播、状态推送或自定义二进制协议。关闭服务时应同时关闭 clients 集合中的连接。

文本和二进制不要混着处理

SendText() 使用 UTF-8,适合 JSON、命令和普通消息;SendData() 接收带 Ptr 和 Size 的 Buffer,适合图片片段、结构体或自定义协议。二进制接收端必须先检查 Size,再用 NumGet 或复制内存读取,不能对不足长度的数据直接取值。

什么时候不该用它

如果目标服务只有 wss://,当前库不能直接连接,不应通过关闭证书验证之类的方式硬绕。公网生产连接还需要 TLS、鉴权、重连退避、心跳、消息上限和更完整的异常恢复,这些不能只靠基础回声示例解决。

如果项目本身已经使用 HttpServer_v1,并希望在同一服务中升级 WebSocket,请优先看 HttpServer.WebSocket_v1;如果只是做独立 ws 客户端、局域网通信或协议调试,这套客户端与服务端合一的封装更直接。

完整 GUI 回声示例

下面的示例会在 127.0.0.1:18136 启动服务端,再创建客户端连接自己。窗口可以发送文本和 4 字节二进制数据、重连并查看双方日志。它只监听本机,不会自动开放公网端口。

 

本文移植自 thqby 的 ahk2_lib,由本站适配为 AutoHotkey v1 版本,原作者及项目版权归其所有。
源项目地址:https://github.com/thqby/ahk2_lib

 

打包库和示例的下载地址:

蓝奏云:https://wwbtw.lanzouu.com/iIQzh3wuc3md  提取码:ahk6

百度网盘:https://pan.baidu.com/s/1Pbl1OSHo7TPsrEs6AvalPA?pwd=ahk6  提取码:ahk6

 

 

demo代码片段展示:

#Requires AutoHotkey v1.1
#NoEnv
#SingleInstance Force
#Persistent
SetBatchLines, -1

#Include <WebSockets_v1>

port := 18136
url := "ws://127.0.0.1:" port "/echo"

Gui, New, +Resize +MinSize520x320, WebSockets_v1 示例
Gui, Margin, 12, 12
Gui, Font, s10, Microsoft YaHei UI
Gui, Add, Text, xm ym w620, % "本地 WebSocket 回声服务:" url
Gui, Add, Button, xm y+8 w120 h30 gWSV1_SendText, 发送文本
Gui, Add, Button, x+8 yp w120 h30 gWSV1_SendBinary, 发送二进制
Gui, Add, Button, x+8 yp w120 h30 gWSV1_Reconnect, 重连客户端
Gui, Add, Button, x+8 yp w120 h30 gWSV1_CloseAll, 关闭
Gui, Add, Edit, xm y+10 w620 h260 HwndWSV1_hLog ReadOnly -Wrap
Gui, Show
WSV1_State().hLog := WSV1_hLog
WSV1_State().logText := ""

try {
	WSV1_Server := new WebSockets.Server(port, "127.0.0.1")
	WSV1_Server.onClientConnect := Func("WSV1_ServerClientConnect")
	WSV1_Log("服务已启动。")
	SetTimer, WSV1_StartClient, -300
} catch e {
	WSV1_Log("启动失败:" WSV1_ExceptionText(e))
}
return

WSV1_StartClient:
try {
	WSV1_Client := new WebSockets.Client(url)
	WSV1_Client.onMessage := Func("WSV1_ClientMessage")
	WSV1_Client.onData := Func("WSV1_ClientData")
	WSV1_Log("客户端已连接。")
	WSV1_Client.SendText("你好,WebSockets_v1。")
} catch e {
	WSV1_Log("客户端连接失败:" WSV1_ExceptionText(e))
}
return

WSV1_SendText:
try {
	if (!IsObject(WSV1_Client) || WSV1_Client.readyState != 1) {
		WSV1_Log("客户端未连接。")
		return
	}
	WSV1_Client.SendText("当前时间:" A_Hour ":" A_Min ":" A_Sec)
	WSV1_Log("客户端发送文本。")
} catch e {
	WSV1_Log("发送文本失败:" WSV1_ExceptionText(e))
}
return

WSV1_SendBinary:
try {
	if (!IsObject(WSV1_Client) || WSV1_Client.readyState != 1) {
		WSV1_Log("客户端未连接。")
		return
	}
	buf := new WebSockets.Buffer(4)
	buf.Size := 4
	NumPut(0x12345678, buf.Ptr + 0, 0, "UInt")
	WSV1_Client.SendData(buf)
	WSV1_Log("客户端发送二进制:0x12345678。")
} catch e {
	WSV1_Log("发送二进制失败:" WSV1_ExceptionText(e))
}
return

WSV1_Reconnect:
try {
	if (IsObject(WSV1_Client))
		WSV1_Client.WsClose()
	SetTimer, WSV1_StartClient, -300
	WSV1_Log("正在重连客户端。")
} catch e {
	WSV1_Log("重连失败:" WSV1_ExceptionText(e))
}
return

WSV1_CloseAll:
GuiClose:
Esc::
try {
	if (IsObject(WSV1_Client))
		WSV1_Client.WsClose()
}
try {
	if (IsObject(WSV1_Server))
		WSV1_Server.Close()
}
ExitApp
return

WSV1_ServerClientConnect(server, client) {
	client.onMessage := Func("WSV1_ServerMessage")
	client.onData := Func("WSV1_ServerData")
	WSV1_Log("服务端接受连接:" client.addr)
}

WSV1_ServerMessage(client, msg) {
	WSV1_Log("服务端收到文本:" msg)
	client.SendText("服务端回声:" msg)
}

WSV1_ServerData(client, buf) {
	local value
	value := buf.Size >= 4 ? NumGet(buf.Ptr + 0, 0, "UInt") : 0
	WSV1_Log("服务端收到二进制:" buf.Size " 字节,值=0x" Format("{:08X}", value))
	client.SendData(buf)
}

WSV1_ClientMessage(client, msg) {
	WSV1_Log("客户端收到文本:" msg)
}

WSV1_ClientData(client, buf) {
	local value
	value := buf.Size >= 4 ? NumGet(buf.Ptr + 0, 0, "UInt") : 0
	WSV1_Log("客户端收到二进制:" buf.Size " 字节,值=0x" Format("{:08X}", value))
}

WSV1_Log(text) {
	state := WSV1_State()
	state.logText .= "[" A_Hour ":" A_Min ":" A_Sec "] " text "`r`n"
	GuiControl,, % state.hLog, % state.logText
	PostMessage, 0x115, 7, 0,, % "ahk_id " state.hLog
}

WSV1_State() {
	static state
	if (!IsObject(state))
		state := {}
	return state
}

WSV1_ExceptionText(e) {
	if (!IsObject(e))
		return e
	return e.Message " | What: " e.What " | Extra: " e.Extra " | Line: " e.Line
}
声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。