Basic TCP Server

[03/05/2023]

1. Introduction

Let's start with implementation of TCP Server echoing message from TCP Client. I am going to use CodeSys V3.5.

2. Implementation

Variable declaration


          VAR
            // general
            serverStep : UINT := 10;
            serverAddr : SOCKADDRESS;
            serverIp : STRING(16) := '192.168.0.204';
            serverPort : WORD := 52233;
            
            // 10 - socket initialization
            hServerSocket : SysSocket.SysSocket_Interfaces.RTS_IEC_HANDLE;
            rCreate : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            
            // 30 - socket binding
            rBind : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            
            // 40 - listen
            rListen : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            
            // 50 - accept
            hAccept : SysSocket.SysSocket_Interfaces.RTS_IEC_HANDLE := SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE;
            rAccept : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            clientAddress : SOCKADDRESS;
            clientAddrSize : DINT := SIZEOF(clientAddress);
            
            // 60 - exchange data - receive and send
            rRecv : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            rSend : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
            rClose : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;	
            dataReceived : ARRAY[1..32] OF BYTE;
            bytesReceived : __XINT;
            
            // 90 - close sever
            rServerClose : SysSocket.SysSocket_Interfaces.RTS_IEC_RESULT;
          
          END_VAR
        

Main code


          CASE serverStep OF
          10:
            SysSockInetAddr(serverIp, ADR(serverAddr.sin_addr));
            serverAddr.sin_family := SOCKET_AF_INET;
            serverAddr.sin_port := SysSockHtons(serverPort);
            hServerSocket := SysSockCreate(SOCKET_AF_INET, SOCKET_STREAM, SOCKET_IPPROTO_TCP, ADR(rCreate));
            IF hServerSocket <> SysSocket.SysSocket_Interfaces.RTS_INVALID_HANDLE THEN
              serverStep := 30;
            END_IF
            
          30:
             rBind := SysSockBind(hServerSocket, ADR(serverAddr), SIZEOF(SOCKADDRESS));
            IF rBind = CmpErrors.Errors.ERR_OK THEN
              serverStep := 40;
            END_IF
            
          40:
            rListen := SysSockListen(hServerSocket, 1);
            IF rListen = CmpErrors.Errors.ERR_OK THEN
              serverStep := 50;
            END_IF
          
          50:
            hAccept := SysSockAccept(hServerSocket, ADR(clientAddress), ADR(clientAddrSize), ADR(rAccept));
            IF rAccept = CmpErrors.Errors.ERR_OK THEN	
              serverStep := 60;
            END_IF
            
          60:
            bytesReceived := SysSockRecv(hAccept, ADR(dataReceived), SIZEOF(dataReceived), 0, ADR(rRecv));
            IF (rRecv = CmpErrors.Errors.ERR_OK) THEN
              SysSockSend(hAccept, ADR(dataReceived), bytesReceived, 0, ADR(rSend));
            END_IF
            IF ((rRecv = CmpErrors.Errors.ERR_SOCK_NOTCONNECTED) OR (rRecv = CmpErrors.Errors.ERR_SOCK_CLOSED)) THEN
              rClose := SysSockClose(hAccept);
              serverStep := 50;	
            END_IF
            
          90:
            rServerClose := SysSockClose(hServerSocket);
            IF rServerClose = CmpErrors.Errors.ERR_OK THEN
              serverStep := 91;	
            END_IF
            
          END_CASE
        

3. Explanation

CodeSys provides SysSocket library containing functions for TCP communication.

step 10:

A socket is created with SysSockCreate() function. TCP communication is selected. First three parameters of SysSockCreate() are predefined in Global Variable List of the library. Fourth parameter, is a pointer of custom variable to know the result of function execution. SysSockCreate() returns a handler that is saved for further use.

step 30:

Binding IP address and port number.

step 40:

Server is listening for new connections.

step 50:

Connection is accepted and handler to accepted socket is returned. Handler will be used to receive and send data.

step 60:

When data are received, they are send back to client as they are. When Client closes socket or when the connection is lost, socket on server side is closed as well and program goes back to step 50.

step 90:

Presented program never enters that step. By default, PLCs are looping through the program constantly. If the device provides TCP Server functionality, I assume that in normal conditions I would never want to stop the server. Nevertheless, I may want to stop server from elsewhere in the program under some special conditions.

4. Issues

It is a good practice to place the server in a separate task. In this case, the main task is not affected. For each task it is possible to set watchdog. Normally PLC have it set by default. If You set a watchdog for the server task, CodeSys will stop executing with an exception error of timeout as SysSocket functions, by default, are blocking program execution. You may disable the watchdog and the program will not pop up any errors but execution will still be freezing at almost every function.

Table 1 - PM800 Power Factor register value and its interpretation

Another problem is that above program is able to manage only one socket at a time. It is not possible to connect more than one TCP Client simultaneously.

Both problems can be overcome with SysSockIoctl() and SysSockSelect() functions. See Advanced TCP Server chapter.

Source code here (IP: 192.168.0.204, PORT: 52333).