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 is quite straightforward, but your application must contain a (GUI) window or dialog to receive notification requests from Winsock. It is therefore necessary to either: (1) create a dialog with DDT, or (2) use the Windows API to create a GUI window for the application to receive these 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

MSGBOX "OPEN Error" + STR$(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

MSGBOX "RECV Error" + STR$(ERR)

EXIT FUNCTION

END IF

MSGBOX 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)