An ECHO client and server using TCP

The simplest TCP server application is an Echo Server (RFC 862).  It simply listens to port 7, and when it receives a data packet, it returns the data packet back to the client.

Writing a TCP server in PowerBASIC requires some advanced programming.  The Winsock API will only send notification requests to a window.  Although a "Console" is technically a window, it cannot receive TCP notification because an application cannot gain access to the callback for the console window.  It is therefore necessary to use the Windows API to create a Window for the application to receive notifications.  The following function will register a window class, and create a hidden window that can be used by your server.

FUNCTION MakeWindow() AS DWORD

  LOCAL wce         AS WndClassEx

  LOCAL szClassName AS ASCIIZ * 80

  LOCAL hWnd        AS DWORD

 

  STATIC registered AS LONG

 

  IF ISFALSE registered THEN

    szClassName = "PBTCPCOMM"

    wce.cbSize        = SIZEOF(wce)

    wce.style         = %NULL

    wce.lpfnWndProc   = CODEPTR(TcpProc)

    wce.cbClsExtra    = 0

    wce.cbWndExtra    = 0

    wce.hInstance     = GetModuleHandle(BYVAL %NULL)

    wce.hIcon         = %NULL

    wce.hCursor       = %NULL

    wce.hbrBackground = %NULL

    wce.lpszMenuName  = %NULL

    wce.lpszClassName = VARPTR(szClassName)

    wce.hIconSm       = %NULL

    RegisterClassEx wce

    registered = %TRUE

  END IF

 

  hWnd = CreateWindow("PBTCPCOMM", "", 0,0,0,0,0, %NULL, _

         %NULL, GetModuleHandle(BYVAL %NULL), BYVAL %NULL)

  ShowWindow hWnd, %SW_HIDE

  FUNCTION = hWnd

END FUNCTION

To create a TCP server, your program must first open a socket using the TCP OPEN SERVER statement.  Then, when a client contacts your server, this socket will receive the notification.  To specify which notifications your code will process, use the TCP NOTIFY statement:

%TCP_ACCEPT = %WM_USER + 4093 ' user-defined message value

...

hServer = FREEFILE

TCP OPEN SERVER PORT 7 AS #hServer

TCP NOTIFY hServer, ACCEPT TO hWnd AS %TCP_ACCEPT

TCP NOTIFY tells Winsock that it should send the %TCP_ACCEPT message to the window specified by hWnd.  Your callback will then include a message handler for the %TCP_ACCEPT message.  The lParam& parameter to your callback will tell you what type of notification was sent:

%TCP_ECHO = %WM_USER + 4094 ' user-defined message value

...

CASE %TCP_ACCEPT

  SELECT CASE LO(WORD, lParam&)

    '* An ACCEPT notification was sent

    CASE %FD_ACCEPT

      hEcho = FREEFILE

      TCP ACCEPT hServer AS hEcho

      TCP NOTIFY hEcho, RECV CLOSE TO hWnd AS %TCP_ECHO

  .

  .  'other notification code goes here

  .

  END SELECT

Once your code receives the ACCEPT notification, it uses the TCP ACCEPT statement to "close" the socket.  A new socket is created for the actual communication with the client.  The original socket (hServer) is used strictly to process ACCEPT notifications only.  TCP NOTIFY is then used with the new socket handle to process RECV and CLOSE notifications.

When the Echo Client sends its message to your server, a RECV notification will be sent to your window.  Your code can then log the incoming message, and send it right back to the client.  When the CLOSE notification is received, you can close the socket:

CASE %TCP_ECHO

  SELECT CASE LO(WORD, lParam&)

 

    CASE %FD_READ

      IF hEcho <> %INVALID_SOCKET THEN

        TCP RECV hEcho, 1024, buffer

        TCP SEND hEcho, buffer

        LogEvent $DQ + Buffer + $DQ

      END IF

 

    CASE %FD_CLOSE

      TCP CLOSE hEcho

      hEcho = %INVALID_SOCKET

 

  END SELECT

To connect with the Echo Server, our Client simply needs to open a socket at port 7, send a string, and display the string echoed back from the server.

FUNCTION PBMAIN() AS LONG

 

  LOCAL hSocket AS LONG

 

  hSocket = FREEFILE

  TCP OPEN PORT 7 AT "" AS hSocket

  IF ERR THEN

    PRINT "OPEN Error"; ERR

    EXIT FUNCTION

  END IF

 

  IF LEN(COMMAND$) = 0 THEN

    TCP SEND hSocket, "This is a test"

  ELSE

    TCP SEND hSocket, COMMAND$

  END IF

 

  TCP RECV hSocket, 1024, buffer$

  IF ERR THEN

    PRINT "RECV Error"; ERR

    EXIT FUNCTION

  END IF

  PRINT buffer$

 

  TCP CLOSE hSocket

 

END FUNCTION

The complete Echo Server and Echo Client sample can be found in your PB\SAMPLES\INTERNET\TCP folder.

Finally, it should be noted that there is no direct correlation between the number of TCP SEND statements executed, compared to the number of %FD_READ messages received.  This is because Winsock may concatenate multiple data packets and issue a lesser number of %FD_READ messages in response.  Therefore, it is usually necessary to construct your code so that it continues to read data from the incoming data stream until either the returned string is empty, or an error is detected.  For example:

DIM InBuffer AS STRING

...

  CASE %FD_READ

    InBuffer = ""

    IF hEcho = %INVALID_SOCKET THEN EXIT SELECT

    DO

      TCP RECV hEcho, 1024, buffer

      IF LEN(buffer) = 0 OR ISTRUE ERR THEN EXIT LOOP

      InBuffer = InBuffer + buffer

      TCP SEND hEcho, buffer

      LogEvent $DQ + Buffer + $DQ

    LOOP

...

 

See Also

TCP and UDP Communications

Simple Mail Transfer Protocol (SMTP)