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
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
Simple Mail Transfer Protocol (SMTP)