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