Socket.ahk

Post your working scripts, libraries and tools
GeekDude
Posts: 853
Joined: 02 Oct 2013, 22:13

Socket.ahk

28 Jul 2017, 08:35

Socket.ahk

A socket class based on Bentschi's excellent "Socket Class (überarbeitet)".

Some of the changes that have been made:
  • Instead of accepting the address and port separately, they are passed as a single array of length 2 (similar to python)
  • SendText no longer includes the null terminator
  • The class is self contained and no longer requires an external helper function
Examples
Related projects

Download
zotune
Posts: 82
Joined: 17 Nov 2014, 17:57

Re: Socket.ahk

04 Oct 2017, 18:27

Thanks for the 'socket.ahk' script GeekDude. It's beyond awesome, and a great improvement upon the original.

Do you think you could make an example where you copy a file from client to server? I have yet to figure it out.
iPhilip
Posts: 428
Joined: 02 Oct 2013, 12:21

Re: Socket.ahk

11 Apr 2018, 13:15

Hi GeekDude,

Thank you for the Socket class. I found it easier to use than the AHKsock library by TheGood found here.

For a script that I am working on I dealt with a server that, at times, never responded so I modified the class to include a Timeout parameter as a static variable in the class. I set the default value to 5 seconds. See the updated class below. I marked the modified lines with a ; <<< comment.

Code: Select all

; class Socket by GeekDude - https://autohotkey.com/boards/viewtopic.php?t=35120
; modified by iPhilip to incorporate a timeout check in the Recv() method - see the lines marked by ; <<<

class Socket
{
	static WM_SOCKET := 0x9987, MSG_PEEK := 2
	static FD_READ := 1, FD_ACCEPT := 8, FD_CLOSE := 32
	static Blocking := True, BlockSleep := 50
	static Timeout := 5000  ; <<<
	
	__New(Socket:=-1)
	{
		static Init
		if (!Init)
		{
			DllCall("LoadLibrary", "Str", "Ws2_32", "Ptr")
			VarSetCapacity(WSAData, 394+A_PtrSize)
			if (Error := DllCall("Ws2_32\WSAStartup", "UShort", 0x0202, "Ptr", &WSAData))
				throw Exception("Error starting Winsock",, Error)
			if (NumGet(WSAData, 2, "UShort") != 0x0202)
				throw Exception("Winsock version 2.2 not available")
			Init := True
		}
		this.Socket := Socket
	}
	
	__Delete()
	{
		if (this.Socket != -1)
			this.Disconnect()
	}
	
	Connect(Address)
	{
		if (this.Socket != -1)
			throw Exception("Socket already connected")
		Next := pAddrInfo := this.GetAddrInfo(Address)
		while Next
		{
			ai_addrlen := NumGet(Next+0, 16, "UPtr")
			ai_addr := NumGet(Next+0, 16+(2*A_PtrSize), "Ptr")
			if ((this.Socket := DllCall("Ws2_32\socket", "Int", NumGet(Next+0, 4, "Int")
				, "Int", this.SocketType, "Int", this.ProtocolId, "UInt")) != -1)
			{
				if (DllCall("Ws2_32\WSAConnect", "UInt", this.Socket, "Ptr", ai_addr
					, "UInt", ai_addrlen, "Ptr", 0, "Ptr", 0, "Ptr", 0, "Ptr", 0, "Int") == 0)
				{
					DllCall("Ws2_32\freeaddrinfo", "Ptr", pAddrInfo) ; TODO: Error Handling
					return this.EventProcRegister(this.FD_READ | this.FD_CLOSE)
				}
				this.Disconnect()
			}
			Next := NumGet(Next+0, 16+(3*A_PtrSize), "Ptr")
		}
		throw Exception("Error connecting")
	}
	
	Bind(Address)
	{
		if (this.Socket != -1)
			throw Exception("Socket already connected")
		Next := pAddrInfo := this.GetAddrInfo(Address)
		while Next
		{
			ai_addrlen := NumGet(Next+0, 16, "UPtr")
			ai_addr := NumGet(Next+0, 16+(2*A_PtrSize), "Ptr")
			if ((this.Socket := DllCall("Ws2_32\socket", "Int", NumGet(Next+0, 4, "Int")
				, "Int", this.SocketType, "Int", this.ProtocolId, "UInt")) != -1)
			{
				if (DllCall("Ws2_32\bind", "UInt", this.Socket, "Ptr", ai_addr
					, "UInt", ai_addrlen, "Int") == 0)
				{
					DllCall("Ws2_32\freeaddrinfo", "Ptr", pAddrInfo) ; TODO: ERROR HANDLING
					return this.EventProcRegister(this.FD_READ | this.FD_ACCEPT | this.FD_CLOSE)
				}
				this.Disconnect()
			}
			Next := NumGet(Next+0, 16+(3*A_PtrSize), "Ptr")
		}
		throw Exception("Error binding")
	}
	
	Listen(backlog=32)
	{
		return DllCall("Ws2_32\listen", "UInt", this.Socket, "Int", backlog) == 0
	}
	
	Accept()
	{
		if ((s := DllCall("Ws2_32\accept", "UInt", this.Socket, "Ptr", 0, "Ptr", 0, "Ptr")) == -1)
			throw Exception("Error calling accept",, this.GetLastError())
		Sock := new Socket(s)
		Sock.ProtocolId := this.ProtocolId
		Sock.SocketType := this.SocketType
		Sock.EventProcRegister(this.FD_READ | this.FD_CLOSE)
		return Sock
	}
	
	Disconnect()
	{
		; Return 0 if not connected
		if (this.Socket == -1)
			return 0
		
		; Unregister the socket event handler and close the socket
		this.EventProcUnregister()
		if (DllCall("Ws2_32\closesocket", "UInt", this.Socket, "Int") == -1)
			throw Exception("Error closing socket",, this.GetLastError())
		this.Socket := -1
		return 1
	}
	
	MsgSize()
	{
		static FIONREAD := 0x4004667F
		if (DllCall("Ws2_32\ioctlsocket", "UInt", this.Socket, "UInt", FIONREAD, "UInt*", argp) == -1)
			throw Exception("Error calling ioctlsocket",, this.GetLastError())
		return argp
	}
	
	Send(pBuffer, BufSize, Flags:=0)
	{
		if ((r := DllCall("Ws2_32\send", "UInt", this.Socket, "Ptr", pBuffer, "Int", BufSize, "Int", Flags)) == -1)
			throw Exception("Error calling send",, this.GetLastError())
		return r
	}
	
	SendText(Text, Flags:=0, Encoding:="UTF-8")
	{
		VarSetCapacity(Buffer, StrPut(Text, Encoding) * ((Encoding="UTF-16"||Encoding="cp1200") ? 2 : 1))
		Length := StrPut(Text, &Buffer, Encoding)
		return this.Send(&Buffer, Length - 1)
	}
	
	Recv(ByRef Buffer, BufSize:=0, Flags:=0)
	{
		StartTime := A_TickCount                                                                        ; <<<
		while (!(Length := this.MsgSize()) && this.Blocking && A_TickCount - StartTime < this.Timeout)  ; <<<
			Sleep, this.BlockSleep
		if !Length
			return 0
		if !BufSize
			BufSize := Length
		VarSetCapacity(Buffer, BufSize)
		if ((r := DllCall("Ws2_32\recv", "UInt", this.Socket, "Ptr", &Buffer, "Int", BufSize, "Int", Flags)) == -1)
			throw Exception("Error calling recv",, this.GetLastError())
		return r
	}
	
	RecvText(BufSize:=0, Flags:=0, Encoding:="UTF-8")
	{
		if (Length := this.Recv(Buffer, BufSize, flags))
			return StrGet(&Buffer, Length, Encoding)
		return ""
	}
	
	RecvLine(BufSize:=0, Flags:=0, Encoding:="UTF-8", KeepEnd:=False)
	{
		while !(i := InStr(this.RecvText(BufSize, Flags|this.MSG_PEEK, Encoding), "`n"))
		{
			if !this.Blocking
				return ""
			Sleep, this.BlockSleep
		}
		if KeepEnd
			return this.RecvText(i, Flags, Encoding)
		else
			return RTrim(this.RecvText(i, Flags, Encoding), "`r`n")
	}
	
	GetAddrInfo(Address)
	{
		; TODO: Use GetAddrInfoW
		Host := Address[1], Port := Address[2]
		VarSetCapacity(Hints, 16+(4*A_PtrSize), 0)
		NumPut(this.SocketType, Hints, 8, "Int")
		NumPut(this.ProtocolId, Hints, 12, "Int")
		if (Error := DllCall("Ws2_32\getaddrinfo", "AStr", Host, "AStr", Port, "Ptr", &Hints, "Ptr*", Result))
			throw Exception("Error calling GetAddrInfo",, Error)
		return Result
	}
	
	OnMessage(wParam, lParam, Msg, hWnd)
	{
		Critical
		if (Msg != this.WM_SOCKET || wParam != this.Socket)
			return
		if (lParam & this.FD_READ)
			this.onRecv()
		else if (lParam & this.FD_ACCEPT)
			this.onAccept()
		else if (lParam & this.FD_CLOSE)
			this.EventProcUnregister(), this.OnDisconnect()
	}
	
	EventProcRegister(lEvent)
	{
		this.AsyncSelect(lEvent)
		if !this.Bound
		{
			this.Bound := this.OnMessage.Bind(this)
			OnMessage(this.WM_SOCKET, this.Bound)
		}
	}
	
	EventProcUnregister()
	{
		this.AsyncSelect(0)
		if this.Bound
		{
			OnMessage(this.WM_SOCKET, this.Bound, 0)
			this.Bound := False
		}
	}
	
	AsyncSelect(lEvent)
	{
		if (DllCall("Ws2_32\WSAAsyncSelect"
			, "UInt", this.Socket    ; s
			, "Ptr", A_ScriptHwnd    ; hWnd
			, "UInt", this.WM_SOCKET ; wMsg
			, "UInt", lEvent) == -1) ; lEvent
			throw Exception("Error calling WSAAsyncSelect",, this.GetLastError())
	}
	
	GetLastError()
	{
		return DllCall("Ws2_32\WSAGetLastError")
	}
}

class SocketTCP extends Socket
{
	static ProtocolId := 6 ; IPPROTO_TCP
	static SocketType := 1 ; SOCK_STREAM
}

class SocketUDP extends Socket
{
	static ProtocolId := 17 ; IPPROTO_UDP
	static SocketType := 2  ; SOCK_DGRAM
	
	SetBroadcast(Enable)
	{
		static SOL_SOCKET := 0xFFFF, SO_BROADCAST := 0x20
		if (DllCall("Ws2_32\setsockopt"
			, "UInt", this.Socket ; SOCKET s
			, "Int", SOL_SOCKET   ; int    level
			, "Int", SO_BROADCAST ; int    optname
			, "UInt*", !!Enable   ; *char  optval
			, "Int", 4) == -1)    ; int    optlen
			throw Exception("Error calling setsockopt",, this.GetLastError())
	}
}
Cheers!

- iPhilip
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)
txiah

Re: Socket.ahk

04 Oct 2018, 10:32

hello.

I am an old fart programmer but rusty.

I am new to AHK and I need to listen on a socket for an acknowledge from the server before I terminate the client process.

how would I do that?


also is there a script that listens on a socket and returns everything it "sees"?
GeekDude
Posts: 853
Joined: 02 Oct 2013, 22:13

Re: Socket.ahk

04 Oct 2018, 12:21

txiah wrote:hello.

I am an old fart programmer but rusty.

I am new to AHK and I need to listen on a socket for an acknowledge from the server before I terminate the client process.

how would I do that?


also is there a script that listens on a socket and returns everything it "sees"?
Hello txiah, welcome to the AutoHotkey community!

I've written some sample code below that may be able to be
used for these purposes. It listens on TCP socket 1337 for a single newline-delimited line to
be sent, then echoes it to the console. Let me know if any part is unclear or if I there is any other part
you may need help with.

Regards,
GeekDude

Code: Select all

#Include Socket.ahk

; Ask Windows to allocate a console for us to use
; as our output to the user.
DllCall("AllocConsole")

; Set up a queue to contain our received messages,
; and a routine to monitor that queue and carry
; out any work that may be required.
RecvQueue := []
SetTimer, CheckQueue, 100

; Create a socket server to listen for incoming messages.
Server := new SocketTCP()
Server.OnAccept := Func("OnAccept")
Server.Bind(["0.0.0.0", 1337])
Server.Listen()

; Show a dialog to let the user know the server is running
MsgBox, Serving on port 1337`nClose this dialog to exit
ExitApp

; This routine will continually check the RecvQueue
; for newly received messages, and will carry out
; any long-lived or blocking operations that would
; be necessary.
CheckQueue()
{
	global RecvQueue
	
	; Loop through any received messages
	Loop, % RecvQueue.Length()
	{
		; Our newer messages will be at the front
		; of the queue, so remove an item from the
		; first index (AHK arrays are 1-indexed)
		Text := RecvQueue.RemoveAt(1)
		
		; Write the text, with an ending newline,
		; to the console output (Windows exposes
		; the console output as a special file)
		FileAppend, %Text%`n, CONOUT$
	}
}

; We want this routine to run for as little time as possible.
; AutoHotkey is not multithreaded, and while this rotuine
; is running it blocks any other clients from connecting to
; and communiting with the script.
OnAccept(Server)
{
	global RecvQueue
	
	; Accept the socket connection, read one newline-
	; delimited line, then close the socket immediatley.
	; Again, AHK is not multithreaded and can only
	; serve one client at a time, so the less time we
	; spend with the socket open the better.
	Sock := Server.Accept()
	Text := Sock.RecvLine()
	Sock.Disconnect()
	
	; Take the the text we received and put it into
	; the received text queue. Another routine will
	; handle that text and perform any blocking operations,
	; freeing up this routine to be run again for the
	; next client.
	RecvQueue.Push(Text)
}
rIsidoro
Posts: 1
Joined: 05 Feb 2019, 02:21

Re: Socket.ahk

05 Feb 2019, 02:42

Hi all,
I recently began AHK programming. For a small script I made, I used this library so I want to say Thank You to the developer(s).
Also, I would like to contribute a little with a couple of methods I added to the SocketUDP subclass, recvFrom and sendTo, to be used with connectionless protocols like UDP.
They are closely related to recv and send methods. You can add them to the SocketUDP subclass (suggested) or to the main Socket class.
I hope someone may find them useful.

Code: Select all

	RecvFrom(ByRef Buffer, BufSize:=0, Flags:=0, ByRef AddrFrom := 0)
	{
		while (!(Length := this.MsgSize()) && this.Blocking)
			Sleep, this.BlockSleep
		if !Length
			return 0
		if !BufSize
			BufSize := Length
		VarSetCapacity(Buffer, BufSize)
		szAddrFrom := VarSetCapacity(AddrFrom, 16, 0)
		if ((r := DllCall("Ws2_32\recvfrom", "UInt", this.Socket
								, "Ptr", &Buffer, "Int", BufSize
								, "Int", Flags
								, "Ptr", &AddrFrom, "Ptr*", szAddrFrom )) == -1)
			throw Exception("Error calling RecvFrom",, this.GetLastError())
		return r
	}
	
	SendTo(pBuffer, BufSize, Flags:=0, ByRef ToAddr := 0)
	{
		if ((r := DllCall("Ws2_32\sendto", "UInt", this.Socket
						, "Ptr", pBuffer, "Int", BufSize
						, "Int", Flags
						, "Ptr", &ToAddr
						, "Int", 16)) == -1)
			throw Exception("Error calling SendTo",, this.GetLastError())
		return r
	}
Example of use:

Code: Select all

; e.g. in a UDP server, inside a Recv callback function:

VarSetCapacity(pktIN, DGRAMSIZE, 0)
Sock.RecvFrom(pktIN, DGRAMSIZE, 0, addrFrom)
IPfrom := DllCall( "Ws2_32.dll\inet_ntoa","UInt",NumGet(addrFrom,4,"UInt"), "AStr" ) ; IPFrom will contain the IP of the client in string format

; then, after preparing the answer in pktOUT, send it back to the client
Sock.SendTo(&pktOUT, DGRAMSIZE, 0, addrFrom)
Again Thank You G33kDude and Bentschi for the great library.
Bye
Portwolf
Posts: 161
Joined: 08 Oct 2018, 12:57

Re: Socket.ahk

17 Mar 2019, 07:32

Hi there,

I was linked here from Discord. I have no idea on how to use this, looks extremely complicated!
I'll just ask here, hoping someone might help..

I have 2 scripts, on 2 computers.
They are on different networks, and i needed to make script A (sender) send information to script B (receiver).
I am now using One Drive to sync a file with the information, but as it is updated every second, it's really not practical at all.
My objective is that everyone on my team that has script B installed can receive info from script A.

Is this possible?
0x00
Posts: 80
Joined: 22 Jan 2019, 13:12

Re: Socket.ahk

19 Mar 2019, 07:50

Portwolf wrote:
17 Mar 2019, 07:32
Hi there,

I was linked here from Discord. I have no idea on how to use this, looks extremely complicated!
I'll just ask here, hoping someone might help..

I have 2 scripts, on 2 computers.
They are on different networks, and i needed to make script A (sender) send information to script B (receiver).
I am now using One Drive to sync a file with the information, but as it is updated every second, it's really not practical at all.
My objective is that everyone on my team that has script B installed can receive info from script A.

Is this possible?
Not quite, if as you mentioned they're on different' networks, unless you resolve to using noip/dyndns to get a static address through which you can indeed use the functionality provided by this function, but you reminded me of a way i use to do something similar to what you describe, just posted it.
tlk.io bot


Oww, & @G33Kdude, awesome Lib, as always.
User avatar
niczoom
Posts: 76
Joined: 09 Mar 2016, 22:17

Re: Socket.ahk

20 May 2019, 00:27

Hi Geekdude,

I tried the WakOnLan utility above (to wake my TV) and found it didnt work for me. I used the correct MAC address and even tried changing the ip and port numbers in here, UDP.Connect(["255.255.255.255", "7"]) to match the device but with no luck.

Im using it over my wireless network.

Ive installed a WOL app on my phone which does wake my tv so I know it works. Also nirsoft's 'WakeMeOnLan' works via command line.

Any ideas?
[AHK] 1.1.23.05 x32 Unicode
[WIN] 10 Pro x64 Version 5111 (Build 10586.218)

Return to “Scripts and Functions”

Who is online

Users browsing this forum: comvox, joekingcool and 44 guests