Code: Select all
#SingleInstance, Off
SetBatchLines, -1
ListeningPort := "8338"
ConnectingAddress := A_IPAddress1, ConnectingPort := "8338"
; try { ; Get own public ip address (can use this for connecting instead of A_IPAddress1/2/3/4 when listening on 0.0.0.0)
; HttpObj := ComObjCreate("WinHttp.WinHttpRequest.5.1"), HttpObj.SetTimeouts(0, 1000, 1000, 1000)
; HttpObj.Open("GET", "http://www.netikus.net/show_ip.html"), HttpObj.Send(), ConnectingAddress := HttpObj.ResponseText
; }
Gui, -DPIScale
Gui, Margin, 0, 0
;──────────────────────────────────────────────────────────────── Server ─────────────────────────────────────────────────────────────────┐
Gui, Font, s11, Consolas
Gui, Add, GroupBox, x10 y10 w401 h520 cRed +Center +hwndFocusDummy, Server
; Listen
Gui, Font, s8
Gui, Add, GroupBox, x17 y26 w72 h69
Gui, Font, s11
Gui, Add, Edit, xp+5 yp+12 wp-10 h22 +hwndServerPortEditHwnd +Center +Number +Limit5 -Multi -VScroll, % ListeningPort
DllCall("SendMessage", "Ptr", ServerPortEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Port")
Gui, Font, s10
Gui, Add, Button, xp-1 yp+24 wp+2 h28 gServerListen, Listen
; Disconnect
Gui, Font, s8
Gui, Add, GroupBox, x88 y26 w102 h69
Gui, Font, s11
Gui, Add, Edit, xp+5 yp+12 w92 h22 +hwndServerDisconnectEditHwnd +Center +Number +Limit5 -Multi -VScroll
DllCall("SendMessage", "Ptr", ServerDisconnectEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Socket")
Gui, Font, s10
Gui, Add, Button, xp-1 yp+24 w94 h28 gServerDisconnect, Disconnect
; Send
Gui, Font, s8
Gui, Add, GroupBox, x189 y26 w214 h69 +Center
Gui, Font, s11
Gui, Add, Edit, xp+5 yp+12 wp-10 h22 +hwndServerSendTextEditHwnd +Center +Limit -Multi -VScroll, SomeText
DllCall("SendMessage", "Ptr", ServerSendTextEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Text to send")
Gui, Font, s12
Gui, Add, Edit, xp yp+25 w70 h26 +hwndServerSendSocketEditHwnd +Center +Limit +Limit5 -Multi -VScroll
DllCall("SendMessage", "Ptr", ServerSendSocketEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Socket")
Gui, Font, s11
Gui, Add, Button, xp+72 yp-1 w133 h28 gServerSend, Send
; Log
Gui, Font, s8
Gui, Add, Text, x20 y110 w380 +BackgroundTrans +Center, SERVER LOG
Gui, Add, Progress, xp-3 yp-3 wp+6 hp+6 +hwndServerLogTextHwnd +cCCCCCC +BackgroundA0A0A0, 100
WinSet, Top,, ahk_id %ServerLogTextHwnd%
Gui, Add, GroupBox, xp yp+11 wp+1 h405 cBlack
Gui, Add, Edit, xp+1 yp+10 wp-5 hp-15 +hwndServerLogHwnd +ReadOnly -Theme -E0x200
;─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
;──────────────────────────────────────────────────────────────── Client ─────────────────────────────────────────────────────────────────┐
Gui, Font, s11
Gui, Add, GroupBox, x420 y10 w401 h520 cBlue +Center, Client
; Connect/Disconnect
Gui, Font, s8
Gui, Add, GroupBox, x427 y26 w218 h69 +Center
Gui, Font, s11
Gui, Add, Edit, xp+5 yp+12 wp-10 h22 +hwndClientAddressEditHwnd +Center +Limit -Multi -VScroll, % ConnectingAddress
DllCall("SendMessage", "Ptr", ClientAddressEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Address")
Gui, Font, s12
Gui, Add, Edit, xp yp+25 w54 h26 +hwndClientPortEditHwnd +Center +Number +Limit5 -Multi -VScroll, % ConnectingPort
DllCall("SendMessage", "Ptr", ClientPortEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Port")
Gui, Font, s10
Gui, Add, Button, xp+56 yp-1 w66 h28 gClientConnect, Connect
Gui, Add, Button, xp+67 yp w86 h28 gClientDisconnect, Disconnect
; Send
Gui, Font, s8
Gui, Add, GroupBox, x644 y26 w169 h69 +Center
Gui, Font, s11
Gui, Add, Edit, xp+5 yp+12 wp-10 h22 +hwndClientSendEditHwnd +Limit +Center -Multi -VScroll, SomeText
DllCall("SendMessage", "Ptr", ClientSendEditHwnd, "UInt", 0x1501, "Ptr", 1, "WStr", "Text to send")
Gui, Add, Button, xp-1 yp+24 wp+2 h28 gClientSend, Send
; Log
Gui, Font, s8
Gui, Add, Text, x430 y110 w380 +BackgroundTrans +Center, CLIENT LOG
Gui, Add, Progress, xp-3 yp-3 wp+6 hp+6 +hwndClientLogTextHwnd +cCCCCCC +BackgroundA0A0A0, 100
WinSet, Top,, ahk_id %ClientLogTextHwnd%
Gui, Add, GroupBox, xp yp+11 wp+1 h405 cBlack
Gui, Add, Edit, xp+1 yp+10 wp-5 hp-15 +hwndClientLogHwnd +ReadOnly -Theme -E0x200
;─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
GuiControl, Focus, %FocusDummy%
Gui, Show, x0 y0 w830 h539
Return
GuiEscape:
GuiClose:
ExitApp
ServerListen:
if !(Server)
Server := new Winsock("Server", "SocketHandler")
GuiControlGet, ServerPort,, % ServerPortEditHwnd
Server.Listen("0.0.0.0", ServerPort)
Return
ServerDisconnect:
GuiControlGet, ServerDisconnectText,, % ServerDisconnectEditHwnd
Server.Disconnect(ServerDisconnectText)
Return
ServerSend:
GuiControlGet, ServerSendText,, % ServerSendTextEditHwnd
GuiControlGet, ServerSendSocket,, % ServerSendSocketEditHwnd
if (ServerSendSocket)
Server.Send(ServerSendSocket, ServerSendText)
else
Server.Send(ServerSendText)
Return
ClientConnect:
if !(Client)
Client := new Winsock("Client", "SocketHandler")
GuiControlGet, ClientAddress,, % ClientAddressEditHwnd
GuiControlGet, ClientPort,, % ClientPortEditHwnd
Client.Connect(ClientAddress, ClientPort)
Return
ClientDisconnect:
Client.Disconnect()
Return
ClientSend:
GuiControlGet, ClientSendText,, % ClientSendEditHwnd
Client.Send(ClientSendText)
Return
SocketHandler(SocketName, Event, EventInfo) {
Global ServerLogHwnd, ClientLogHwnd
if (SocketName = "Server") {
Switch (Event) {
Case "Listening": GuiPrint(ServerLogHwnd, "Listening on """ EventInfo.Address ":" EventInfo.Port """")
Case "Accepted": GuiPrint(ServerLogHwnd, "Accepted client """ EventInfo.SocketNumber " | " EventInfo.SocketAddress """")
Case "Received": GuiPrint(ServerLogHwnd, "Received """ EventInfo.Message """ from client """ EventInfo.SocketNumber """")
Case "ClientDC": GuiPrint(ServerLogHwnd, "Client """ EventInfo.SocketNumber """ disconnected")
Case "DCClient": GuiPrint(ServerLogHwnd, "Disconnected client """ EventInfo.SocketNumber """")
Case "Shutdown": GuiPrint(ServerLogHwnd, "Shutdown")
Case "SendFail": GuiPrint(ServerLogHwnd, "Sending to client """ EventInfo.SocketNumber """ failed, details in EventInfo")
Case "Error": GuiPrint(ServerLogHwnd, "`r`nError: " EventInfo.Error
. "`r`nError code: " EventInfo.ErrorCode
. "`r`nError description:`r`n" EventInfo.ErrorDescription)
}
}
else if (SocketName = "Client") {
Switch (Event) {
Case "ConnInit": GuiPrint(ClientLogHwnd, "Connecting to """ EventInfo.Address ":" EventInfo.Port """")
Case "ConnBusy": GuiPrint(ClientLogHwnd, "There is a connecting attempt in progress")
Case "ConnDone": GuiPrint(ClientLogHwnd, "Connected to """ EventInfo.Address ":" EventInfo.Port """ successfully")
Case "ConnFail": GuiPrint(ClientLogHwnd, "Connecting to """ EventInfo.Address ":" EventInfo.Port """ failed")
Case "Received": GuiPrint(ClientLogHwnd, "Received """ EventInfo.Message """")
Case "ServerDC": GuiPrint(ClientLogHwnd, "Server disconnected")
Case "Shutdown": GuiPrint(ClientLogHwnd, "Shutdown")
Case "SendFail": GuiPrint(ClientLogHwnd, "Sending failed, details in EventInfo")
Case "Error": GuiPrint(ClientLogHwnd, "`r`nError: " EventInfo.Error
. "`r`nError code: " EventInfo.ErrorCode
. "`r`nError description:`r`n" EventInfo.ErrorDescription)
}
}
}
GuiPrint(Hwnd, Text) {
Static Counter := []
(Counter.HasKey(Hwnd)?Counter[Hwnd]++:Counter[Hwnd]:=1)
L:=DllCall("SendMessage", "Ptr", Hwnd, "UInt", 0x00E, "Int", 0, "Int", 0)
DllCall("SendMessage", "Ptr", Hwnd, "UInt", 0x0B1, "Int", L, "Int", L)
DllCall("SendMessage", "Ptr", Hwnd, "UInt", 0x0C2, "Int", 0, "Str", (L?"`r`n":"") Counter[Hwnd] ". " Text)
DllCall("SendMessage", "Ptr", Hwnd, "UInt", 0x115, "Int", 7, "Int", 0)
}
;------------------------------------------------------------------------------------------------------------------------------------------
Class Winsock { ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=125911
__New(Name, Callback) {
Static WSAStartup
if !(WSAStartup) {
WSAStartup := DllCall("LoadLibrary", "Str", "Ws2_32", "Ptr"), VarSetCapacity(WSAData, 394+A_PtrSize)
if (EC:=DllCall("Ws2_32\WSAStartup", "UShort", 0x0202, "Ptr", &WSAData))
Return,, this.SocketError("Error starting Winsock", EC)
if (NumGet(WSAData, 2, "UShort") != 0x0202)
Return,, this.SocketError("Winsock version 2.2 not available")
}
this.Name := Name, this.Callback := Callback, this.CS := [], this.Socket := -1
}
WM_SOCKET(wParam, lParam) {
Critical, -1
Static TextBacklog
if (wParam != this.Socket)
Return
Switch (lParam) {
Case "8":
VarSetCapacity(SocketAddress, 16), SN := DllCall("Ws2_32\accept", "UInt", this.Socket, "UInt", &SocketAddress, "Int*", 16)
Sock := new Winsock(this.Name, this.Callback), Sock.Parent := this, Sock.Socket := SN, Sock.EventProcRegister()
SockInfo := {SocketNumber:SN, SocketAddress:DllCall("Ws2_32\inet_ntoa", "UInt", NumGet(SocketAddress,4),"AStr")}
this.CS[SN] := Sock, this.CS[SN].Info := SockInfo, this.Callback(this.Name, "Accepted", SockInfo)
Case "1":
if (this.CTimer)
Return,, this.ReadBackedup := True
if (DllCall("Ws2_32\ioctlsocket", "UInt", this.Socket, "UInt", 0x4004667F, "UInt*", DataSize) = -1)
Return,, TextBacklog := this.SocketError("WM_SOCKET > Read > ioctlsocket")
VarSetCapacity(DataBuffer, DataSize)
if (DllCall("Ws2_32\recv", "UInt", this.Socket, "Ptr", &DataBuffer, "Int", DataSize, "Int", 0) = -1)
Return,, TextBacklog := this.SocketError("WM_SOCKET > Read > recv")
if (SubStr(Text := StrGet(&DataBuffer, DataSize, "UTF-8"), 0) != Chr(3))
Return,, TextBacklog .= Text
else if (TextBacklog)
Text := TextBacklog Text, TextBacklog := ""
Loop, parse, % RTrim(Text, Chr(3)), % Chr(3)
this.Callback(this.Name, "Received", {SocketNumber:this.Socket, Message:A_LoopField})
Case "32":
if (this.Parent) {
this.CloseSocket(), this.Parent.CS.Delete(wParam), this.Callback(this.Name, "ClientDC", {SocketNumber:wParam})
} else this.CloseSocket(), this.Callback(this.Name, "ServerDC", "")
}
}
Listen(Address, Port) {
if (this.CTimer)
Return,, this.Callback(this.Name, "ConnBusy", "")
(this.Socket=-1?_:this.Disconnect()), VarSetCapacity(Hints, 16+(4*A_PtrSize), 0), NumPut(1,Hints,8,"Int"), NumPut(6,Hints,12,"Int")
if (EC:=DllCall("Ws2_32\getaddrinfo", "AStr", Address, "AStr", Port, "Ptr", &Hints, "Ptr*", ai))
Return,, this.SocketError("Bind > getaddrinfo", EC)
this.Socket := DllCall("Ws2_32\socket", "Int", 2, "Int", 1, "Int", 6, "UInt"), this.EventProcRegister()
if (EC:=DllCall("Ws2_32\bind", "UInt", this.Socket, "Ptr", NumGet(ai+0,16+(2*A_PtrSize),"Ptr"), "UInt", NumGet(ai+0,16,"UPtr"),"Int"))
Return,, DllCall("Ws2_32\freeaddrinfo", "Ptr", ai) this.SocketError("Listen > bind", EC)
if (EC:=DllCall("Ws2_32\listen", "UInt", this.Socket, "Int", 32))
Return,, DllCall("Ws2_32\freeaddrinfo", "Ptr", ai) this.SocketError("Listen > listen", EC)
this.Serving := True, VarSetCapacity(sa, 16), DllCall("Ws2_32\getsockname", "UInt", this.Socket, "UInt", &sa, "Int*", 16)
Address := DllCall("Ws2_32\inet_ntoa", "UInt", NumGet(sa,4),"AStr"), Port := DllCall("Ws2_32\htons", "UShort", NumGet(sa,2,"UShort"))
DllCall("Ws2_32\freeaddrinfo", "Ptr", ai), this.Callback(this.Name, "Listening", {Address:Address, Port:Port})
}
Connect(Address, Port, Timeout := 3000) {
Critical, -1
if (this.CTimer)
Return,, this.Callback(this.Name, "ConnBusy", "")
(this.Socket=-1?_:this.Disconnect()), VarSetCapacity(Hints, 16+(4*A_PtrSize), 0), NumPut(1,Hints,8,"Int"), NumPut(6,Hints,12,"Int")
if (EC:=DllCall("Ws2_32\getaddrinfo", "AStr", Address, "AStr", Port, "Ptr", &Hints, "Ptr*", ai))
Return,, this.SocketError("Connect > getaddrinfo", EC)
this.Socket := DllCall("Ws2_32\socket", "Int", 2, "Int", 1, "Int", 6), this.EventProcRegister()
this.Callback(this.Name, "ConnInit", {Address:Address, Port:Port})
this.CTimer := Timer := ObjBindMethod(this, "ConnectingTimeout", Address, Port, ai, A_TickCount, Timeout)
SetTimer, % Timer, 1
}
ConnectingTimeout(Address, Port, ai, StartTime, Timeout) {
if (this.CTimerCancel) {
if (DllCall("Ws2_32\send", "UInt", this.Socket, "Ptr", 0, "Int", 0, "Int", 0) = 0)
this.Callback(this.Name, "ConnDone", {Address:Address, Port:Port})
DllCall("Ws2_32\freeaddrinfo", "Ptr", ai), this.CTimerCancel := False, Timer := this.CTimer, this.CTimer := "", this.CloseSocket()
this.Callback(this.Name, "Shutdown", "")
SetTimer, % Timer, Delete
Return
}
DllCall("Ws2_32\connect", "UInt", this.Socket, "UPtr", NumGet(ai+0,16+(2*A_PtrSize),"Ptr"),"UInt", NumGet(ai+0,16,"UPtr"))
if (DllCall("Ws2_32\WSAGetLastError") = 10056) {
DllCall("Ws2_32\freeaddrinfo", "Ptr", ai), this.Callback(this.Name, "ConnDone", {Address:Address, Port:Port})
Timer := this.CTimer, this.CTimer := "", (this.ReadBackedup?this.ReadBackedup := this.WM_SOCKET(this.Socket, 1, 0):_)
SetTimer, % Timer, Delete
Return
}
if ((A_TickCount - StartTime) > Timeout) {
DllCall("Ws2_32\freeaddrinfo", "Ptr", ai), this.CloseSocket(), Timer := this.CTimer, this.CTimer := ""
this.Callback(this.Name, "ConnFail", {Address:Address, Port:Port})
SetTimer, % Timer, Delete
}
}
Send(P1, P2 := "") {
if (this.Socket = -1)
Return
if (this.CTimer)
Return,, this.Callback(this.Name, "ConnBusy", "")
VarSetCapacity(TextBuffer, StrPut(Text:=(P2?P2:P1), "UTF-8")), Length := (StrPut(Text Chr(3), &TextBuffer, "UTF-8")-1)
if (this.Serving) {
if (P2) {
if (this.CS.HasKey(P1))
if (DllCall("Ws2_32\send", "UInt", P1, "Ptr", &TextBuffer, "Int", Length, "Int", 0) = -1) {
EC := DllCall("Ws2_32\WSAGetLastError"), VarSetCapacity(Desc, 2000, 32)
DllCall("FormatMessage", "UInt", 0x1000, "UInt", 0, "UInt", EC, "UInt", 0x800, "UInt", &Desc, "UInt", 500, "UInt", 0)
this.Callback(this.Name, "SendFail", {SocketNumber:P1, Message:Text, Code:EC, Description:Desc})
}
} else {
For i, v in this.CS
if (DllCall("Ws2_32\send", "UInt", v.Info.SocketNumber, "Ptr", &TextBuffer, "Int", Length, "Int", 0) = -1) {
EC := DllCall("Ws2_32\WSAGetLastError"), VarSetCapacity(Desc, 2000, 32)
DllCall("FormatMessage", "UInt", 0x1000, "UInt", 0, "UInt", EC, "UInt", 0x800, "UInt", &Desc, "UInt", 500, "UInt", 0)
this.Callback(this.Name, "SendFail", {SocketNumber:v.Info.SocketNumber, Message:Text, Code:EC, Description:Desc})
}
}
} else {
if (DllCall("Ws2_32\send", "UInt", this.Socket, "Ptr", &TextBuffer, "Int", Length, "Int", 0) = -1) {
EC := DllCall("Ws2_32\WSAGetLastError"), VarSetCapacity(Desc, 2000, 32)
DllCall("FormatMessage", "UInt", 0x1000, "UInt", 0, "UInt", EC, "UInt", 0x800, "UInt", &Desc, "UInt", 500, "UInt", 0)
this.Callback(this.Name, "SendFail", {Message:Text, Code:EC, Description:Desc})
}
}
}
Disconnect(SN := "") {
if (this.CTimer)
Return,, this.CTimerCancel := True
if (this.Socket = -1)
Return
if !(SN) {
if (this.Serving) {
For i, v in this.CS
v.CloseSocket()
this.CloseSocket(), this.CS := [], this.Callback(this.Name, "Shutdown", "")
}
else
this.CloseSocket(), this.Callback(this.Name, "Shutdown", "")
}
else if (this.Serving)
if (this.CS.HasKey(SN))
this.CS[SN].CloseSocket(), this.CS.Delete(SN), this.Callback(this.Name, "DCClient", {SocketNumber:SN})
}
CloseSocket() {
this.EventProcRegister(False)
if (DllCall("Ws2_32\closesocket", "UInt", this.Socket, "Int") = -1)
this.SocketError("CloseSocket > closesocket")
this.Serving := False, this.Socket := -1
}
EventProcRegister(R := True) {
if (DllCall("Ws2_32\WSAAsyncSelect", "UInt", this.Socket, "Ptr", A_ScriptHwnd, "UInt", 0x9987, "UInt", (R?0x3FF:0)) = -1)
Return,, this.SocketError("EventProc > WSAAsyncSelect")
(this.Bound?OnMessage(0x9987, this.Bound, this.Bound := False):OnMessage(0x9987, this.Bound := ObjBindMethod(this, "WM_SOCKET")))
}
SocketError(Error, ErrorCode := "") {
ErrorCode := (ErrorCode?ErrorCode:DllCall("Ws2_32\WSAGetLastError")), VarSetCapacity(Description, 2000, 32)
DllCall("FormatMessage", "UInt", 0x1000, "UInt", 0, "UInt", ErrorCode, "UInt", 0x800, "UInt", &Description, "UInt", 500, "UInt", 0)
this.Callback(this.Name, "Error", {Error:Error, ErrorCode:ErrorCode, ErrorDescription:(ErrorCode?Description:"-")})
}
}
Sorry for how sporadic this post is, i didn't know how to structure it nicely :/
When creating a new instance - ClassInstance := new Winsock(P1, P2)
P1 is the socket name, i recommend having ClassInstance variable the same as the socket name to avoid confusion.
P2 is the callback function, look at the template (SocketHandler) for all possible events, hopefully they are self-explanatory.
Socket.ahk and just winsock in general has issues when sending too fast, this class has a different approach to sending and receiving -
Each message ends with the End of Text character, this makes it possible to use Send() rapidly and messages are separated properly.
Delimiting also allows to properly receive multiple packets (when sending a large message, over 64KB, it gets split up into multiple packets)
Because of that, this class shouldn't be used with different winsock implementations.
When listening, Send() sends to all connected sockets, however when a connection is being accepted -
Case "Accepted": GuiPrint(ServerLogHwnd, "Accepted client """ EventInfo.SocketNumber " | " EventInfo.SocketAddress """")
EventInfo.SocketNumber can be saved for sending only to this specific socket connection - ClassInstance.Send(EventInfo.SocketNumber, Text)
Compared to socket.ahk, connecting is a lot different:
ClassInstance.Connect(Address, Port, Timeout)
It's non-blocking, meaning that the script doesn't hang while trying to connect.
If you set the timeout parameter to for example 30000 (30 seconds), it will continuously try to connect for that 30 seconds.
The connecting attempt can be cancelled by calling Disconnect()
When sending, if SendFail event occurs then you can check EventInfo.SocketNumber/Message/Code/Description to figure out what caused it,
otherwise it should be assumed that sending succeeded. I could have added an event that indicates successful send but it wouldn't be very reliable,
you can implement some sort of confirmation where the peer responds that a message has been received for extra precaution.
Before deleting a class instance, you should always call Disconnect() first.
Otherwise the class might not delete at all because there's a circular reference.
That's pretty much all the major features/differences, here are some random final notes:
• I didn't write this class with readability in mind, if someone suggests an interesting or useful feature i'm down to implement it!
• Sadly, getaddrinfo can cause the script to hang, you can test it with trying to connect to weird IP's like "123"
i don't think there are any work-arounds for it but if someone knows some black magic, please tell me!
• I didn't do much testing with receiving messages from multiple sockets at the same time,
i think there could be a "bug" where messages get mixed up, if someone complains about it i'll fix it.
• Rapid sending was tested with the code below, you can play around with it if you want..
F1::SetTimer, TestingTimer, % ((Toggle:=!Toggle)?1:"Delete")
TestingTimer:
Count++
Server.Send(Count)
Client.Send(Count)
Return
• I probably won't dabble in different socket types like UDP, however i:
TestingTimer:
Count++
Server.Send(Count)
Client.Send(Count)
Return
- Might add IPv6 support (probably not, it's a big headache)
- Might add binary and file transfer (probably not, i personally don't need these features)
- Might add binary and file transfer (probably not, i personally don't need these features)
That's pretty much it!
Hopefully someone finds this useful c.c