Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Client & Server Script for TCP/IP Network Communication


  • Please log in to reply
130 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

The inet_addr function converts a string containing an (Ipv4) Internet Protocol dotted address into a proper address for the IN_ADDR structure.

You should be able to manually fill SocketAddress with a sockaddr_in6 structure. The definition is:
typedef struct sockaddr_in6 {
    ADDRESS_FAMILY sin6_family; // AF_INET6.
    USHORT sin6_port;           // Transport level port number.
    ULONG  sin6_flowinfo;       // IPv6 flow information.
    IN6_ADDR sin6_addr;         // IPv6 address.
    union {
        ULONG sin6_scope_id;     // Set of interfaces for a scope.
        SCOPE_ID sin6_scope_struct; 
    };
} SOCKADDR_IN6_LH, *PSOCKADDR_IN6_LH, FAR *LPSOCKADDR_IN6_LH;

typedef struct in6_addr {
    union {
        UCHAR       Byte[16];
        USHORT      Word[8];
    } u;
} IN6_ADDR, *PIN6_ADDR, FAR *LPIN6_ADDR;


IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007
thx I will try this.

LYCOS17
  • Members
  • 23 posts
  • Last active: May 11 2009 04:22 AM
  • Joined: 06 Feb 2008
Hi there, I have been working with this code a bit and had a weird question, .... the problem was the script kept crashing when I close the client script, but if I just did a re-load on the client script the problem goes away.... so thanks very much (!) to the other poster aka. someotherguy who helped me by giving me this _obvious_ clue! :D

LYCOS17
  • Members
  • 23 posts
  • Last active: May 11 2009 04:22 AM
  • Joined: 06 Feb 2008
*bump*

Z Gecko
  • Guests
  • Last active:
  • Joined: --
I am no expert a t this, so i have no general idea.

But if you post your try and the errormessages you get,
it might be easier to analyze that problem.

CraSH23000
  • Members
  • 40 posts
  • Last active: Apr 15 2013 06:03 AM
  • Joined: 22 Jun 2007
Thanks a lot for this Zed Gecko, this looks awesome, and thanks LYCOS17 for bumping because I would have never seen this, lol.
Truth is truth, whether you believe it or not.

CraSH23000
  • Members
  • 40 posts
  • Last active: Apr 15 2013 06:03 AM
  • Joined: 22 Jun 2007
Hi,
I was trying to combine qtmspin's code that allowed multiple users and wtg's code that sent the text to an edit box. I get an error when I send:

Posted Image
(if they take that down, go here http://crash23000.fr...m/TCP_error.JPG

As you can see, it sends the data and works for multiple clients, but still displays an error on send.

Heres the code

Server:
; -------------------------------------------------
; ------------SERVERSCRIPT------------------
; -------------------------------------------------
; CONFIGURATION SECTION:

; Specify Your own Network's address and port.
Network_Address = 127.0.0.1
Network_Port = 8765
; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------

Gui, Add, Text,, Send:
Gui, Add, Edit, w100 vSendText
Gui, Add, Text,, Repeat:
Gui, Add, Edit, w40 vRepeat, 1
Gui, Add, Text,, Delay (ms):
Gui, Add, Edit, w40 vDelay, 0   
Gui, Add, Button, gSendviaNet, Send
Gui, Show

Gosub Connection_Init
return

Connection_Init:
OnExit, ExitSub  ; For connection cleanup purposes.

; set up a very basic server:
socket := PrepareForIncomingConnection(Network_Address, Network_Port)
if socket = -1  ; Connection failed (it already displayed the reason).
    ExitApp

; Find this script's main window:
Process, Exist  ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1     ; Received when data is available to be read.
;FD_WRITE = 
FD_CLOSE = 32   ; Received when connection has been closed.
FD_CONNECT = 20 ; Received when connection has been made.
if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_CLOSE|FD_CONNECT)
{
    MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
    ExitApp
}


i=1
Loop ; Wait for incomming connections
{
; accept requests that are in the pipeline of the socket   
   conectioncheck%i% := DllCall("Ws2_32\accept", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
; Ws2_22/accept returns the new Connection-Socket if a connection request was in the pipeline
; on failure it returns an negative value
    if conectioncheck%i% > 1
    {
       MsgBox Incoming connection accepted
       i++
    }       
    sleep 500 ; wait half 1 second then accept again
}   
return

SendviaNet:
Gui, Submit, NoHide
Loop %i% {
SendData(conectioncheck%A_Index%,SendText,Repeat,Delay)
}
SentText =
return

PrepareForIncomingConnection(IPAddress, Port)
; This can connect to most types of TCP servers, not just Network.
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
    VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
    result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
    ; Since WSAStartup() will likely be the first Winsock function called by this script,
    ; check ErrorLevel to see if the OS has Winsock 2.0 available:
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
    {
        MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    ; Prepare for connection:
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
    InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
    InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

    ; Bind to socket:
    if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
    {
        MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
    if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN")
    {
        MsgBox % "LISTEN() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
   
    return socket  ; Indicate success by returning a valid socket ID rather than -1. 
}

SendData(wParam,SendData, Repeat, Delay)
{
   socket := wParam
;   SendDataSize := VarSetCapacity(SendData)
;   SendDataSize += 1
   Loop % Repeat
   {
        SendIt := SendData  ;SendIt := SendData . "(" . A_Index . ")"
      SendDataSize := VarSetCapacity(SendIt)
      SendDataSize += 1
      
      sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendIt, "Int", SendDatasize, "Int", 0)
      WinsockError := DllCall("Ws2_32\WSAGetLastError")
      if WinsockError <> 0 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
         ; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
         MsgBox % "send() indicated Winsock error " . WinsockError
        sleep,Delay
   }      
;send( sockConnected,> welcome, strlen(welcome) + 1, NULL);
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
    Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
        DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

guiclose:
ExitSub:  ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp

Client:
; -------------------------------------------------
;-----------CLIENTSCRIPT----------------------
; -------------------------------------------------
; CONFIGURATION SECTION:

; Specify address and port of the server.
Network_Address = 127.0.0.1
Network_Port = 8765
; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------

Gui, Add, Button, gConnection_Init, Connect
Gui, Add, Edit, R10 vMyEdit
Gui, Add, Button, gClear, Clear
Gui, Show

LinesReceived:=0
return

Connection_Init:
OnExit, ExitSub  ; For connection cleanup purposes.

; Connect to any type of server:
socket := ConnectToAddress(Network_Address, Network_Port)
if socket = -1  ; Connection failed (it already displayed the reason).
    ExitApp

; Find this script's main window:
Process, Exist  ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; When the OS notifies the script that there is incoming data waiting to be received,
; the following causes a function to be launched to read the data:
NotificationMsg = 0x5555  ; An arbitrary message number, but should be greater than 0x1000.
OnMessage(NotificationMsg, "ReceiveData")

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1     ; Received when data is available to be read.
FD_CLOSE = 32   ; Received when connection has been closed.
if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CLOSE)
{
    MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
    ExitApp
}
return

Clear:
ShowReceived=
LinesReceived:=0   
GuiControl,, MyEdit, 
return

ConnectToAddress(IPAddress, Port)
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
    VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
    result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
    ; Since WSAStartup() will likely be the first Winsock function called by this script,
    ; check ErrorLevel to see if the OS has Winsock 2.0 available:
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
    {
        MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    ; Prepare for connection:
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
    InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
    InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

    ; Attempt connection:
    if DllCall("Ws2_32\connect", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
    {
        MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
    return socket  ; Indicate success by returning a valid socket ID rather than -1.
}

ReceiveData(wParam, lParam)
; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection. 
{
     Critical
    global ShowReceived
    global MyEdit
    global LinesReceived

    socket := wParam
    ReceivedDataSize = 4096  ; Large in case a lot of data gets buffered due to delay in processing previous data.
    VarSetCapacity(ReceivedData, ReceivedDataSize, 0)  ; 0 for last param terminates string for use with recv().
    Data   := ""
    Loop  ; This loop solves the issue of the notification message being discarded due to thread-already-running.
    {
        ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
        if ReceivedDataLength = 0  ; The connection was gracefully closed,
            ExitApp  ; The OnExit routine will call WSACleanup() for us.
        if ReceivedDataLength = -1
        {
            WinsockError := DllCall("Ws2_32\WSAGetLastError")
            if ( WinsockError = 10035 ) { ; WSAEWOULDBLOCK, which means "no more data to be read".
                break
            }
            if WinsockError <> 10054 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
                ; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
                MsgBox % "recv() indicated Winsock error " . WinsockError
            ExitApp  ; The OnExit routine will call WSACleanup() for us.
        }
        Data .= ReceivedData
    }
    ; Otherwise, process the data received.
    Loop, parse, Data, `n, `r
    {
       LinesReceived++               
        if (LinesReceived = 1) {
           ShowReceived = %LinesReceived%: %A_LoopField%
        } else {
           ShowReceived = %ShowReceived%`n%LinesReceived%: %A_LoopField%
       }
       ;Tooltip % ShowReceived
       GuiControl,, MyEdit, %ShowReceived%
    }
    return 1  ; Tell the program that no further processing of this message is needed.
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
    Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
        DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

guiclose:
ExitSub:  ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp

I guess I could comment out the error report since it works anyways, unless it is a serious error, but seems like it isn't because it still sends. I was thinking that it is trying to send to someone who isn't connected but I guess I can't interpret the code good enough to verify my assumptions : / lol.


Thanks for any help
Truth is truth, whether you believe it or not.

Z Gecko
  • Guests
  • Last active:
  • Joined: --
since Winsock Error 10038 means Socket operation on non-socket.

WSAENOTSOCK
(10038)
Socket operation on non-socket.
An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.


You could be right with your assumption:

I was thinking that it is trying to send to someone who isn't connected


maybe it just happens after at least one client disconnected?
could you check this out?

CraSH23000
  • Members
  • 40 posts
  • Last active: Apr 15 2013 06:03 AM
  • Joined: 22 Jun 2007
The screenshot I took was the server sending "hey" after one client connected, with no disconnects. So I dont think that this is caused by a client disconnecting.

I'm just making another assumption, but I think it has something to do with the code I changed to allow multi-clients.

maybe, either here:
i=1
Loop ; Wait for incomming connections
{
; accept requests that are in the pipeline of the socket   
   conectioncheck%i% := DllCall("Ws2_32\accept", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
; Ws2_22/accept returns the new Connection-Socket if a connection request was in the pipeline
; on failure it returns an negative value
    if conectioncheck%i% > 1
    {
       MsgBox Incoming connection accepted
       i++
    }       
    sleep 500 ; wait half 1 second then accept again
}   
return
or maybe here:
SendviaNet:
Gui, Submit, NoHide
Loop %i% {
SendData(conectioncheck%A_Index%,SendText,Repeat,Delay)
}
SentText =
return

I'm guessing that its in one of the spots above, because its the only part of the sending code I changed I think.
Truth is truth, whether you believe it or not.

Z Gecko
  • Guests
  • Last active:
  • Joined: --
i think i got it.
SendviaNet:
Gui, Submit, NoHide
[color=darkred]Loop % i - 1[/color]
{
SendData(conectioncheck%A_Index%,SendText,Repeat,Delay)
}
SentText =
return
will fix it.

the var "i" is 2 when one client is accepted,
so in the sending loop, the last iteration will fail.

CraSH23000
  • Members
  • 40 posts
  • Last active: Apr 15 2013 06:03 AM
  • Joined: 22 Jun 2007
Thanks a lot, this works great, I tried i=0 instead of the i=1 for the 1st part of code I pasted, I should have tried something similar on the 2nd part of code that you fixed, lol.

Thanks a lot though.
Truth is truth, whether you believe it or not.

Burn2
  • Guests
  • Last active:
  • Joined: --
Hi,

Is it possible to create username and password checker at server side that received from client?

Z Gecko
  • Guests
  • Last active:
  • Joined: --
I´m not shure what you mean, if you mean to have
the client script send a username and a password to the serverscript,
and the serverscript to cancel the connection when they are wrong.
Then yes, it´s pssible. 8)

FredMcGary
  • Guests
  • Last active:
  • Joined: --
Like the script got a question.I wanted to know if there was anyway to identify the connections coming in. Like associating the incoming socket of the person trying to connect with their IP address. I was trying to make a sort of chat server if you will and in order to make a effective user list, I needed to associate incoming connections with their network address. This way I can slap a nickname on that address and bam the list will work. Also so when the person disconnects it will show their address disconnecting.

I also noticed on that new Close message that Zed posted were he didn't know if it worked or not, when I attempted to add the functions in the script, only 1 of the 2 dll calls (right after the

FD_READ = 1     ; Received when data is available to be read.
FD_CLOSE = 32   ; Received when connection has been closed.
FD_CONNECT = 20 ; Received when connection has been made.
)
would work. In terms, I could either be notified that the client was disconnecting OR receive the text they send.

Zed Gecko
  • Members
  • 149 posts
  • Last active:
  • Joined: 23 Sep 2006
Well, i found out, that the getpeername function of Winsock will return the IP-Address of the socket.
But i neither know how to call it properly via DLL-Call, nor do i know how to handle the the struct that is returned.
(Maybe this is helpfull: http://www.autohotke...pic.php?t=27644 )

For the WSASyncSelect problem, i would like to point you to ManaUsers
TCP/Ip Remote-Script, where he shows, how to do it right.

I also want to note, that Zippo() has crated an network-script that can be include, in his C4-Game-script
code removed due to protest.
http://www.autohotke...pic.php?t=81795