ClientSocket

(Engine-Level Function)

Description: Opens a client WinSock-compliant socket stream and returns a stream value, or a numeric error code.
Returns: Varies - see comments
Usage: Script Only.
Function Groups: Stream and Socket
Related to: CloseStream | ServerSocket | SocketAttribs | SocketServerEnd | SocketServerStart | SocketWait | SRead | SWrite | LargeSocketWrite
Format: ClientSocket(Protocol, Host, Service, TransmitLen, ReceiveLen, Flush[,ProtocolFilters, InboundPortOrStream, IPOut, PortOut, MatchRemoteUDPSourcePort])
Parameters:  
Protocol
Required. Any numeric expression giving the protocol to be used. This must be a valid 0 for TCP/IP protocol or a valid 1 for a UDP protocol.
Host

Required. Any text expression giving the host name or TCP/IP address to connect.

For a ClientSocket where bi-directional UDP communication with a remote system is required and where the client socket defines InboundPortOrStream as a port, the Host parameter must be populated with the expected IP address.

To ensure reliability, you should acquire the IP address by using GetHostByName.

Service
Required. Either any text expression giving the service name to connect, or any numeric expression giving the port number with which to connect.
TransmitLen
Any numeric expression for the number of bytes to buffer when transmitting. The value must be a signed long integer, where only positive values are useful.
If the application is running on a operating system of Windows 7 / Server 2008 R2, or later, and the value is set to zero, then Windows will manage the appropriate buffer size for the link speed and latency.
If you set the buffer size, the value should match or be larger than the largest message that is expected.
A high bandwidth / high latency link will require a larger size to achieve optimum efficiency, but the exact size can be determined only by empirical testing.
ReceiveLen

Required. Any numeric expression for the maximum number of bytes to buffer by VTScada when receiving. Additional buffering will be handled by WinSock.
The value must be a signed long integer, where only positive values are useful (an error will be returned if the value is not greater than zero). The value should match or be larger than the largest message that is expected.
A high bandwidth / high latency link will require a larger size to achieve optimum efficiency, but the exact size can be determined only by empirical testing.

Note: Behavior of this parameter varies depending on ClientSocket is being used for UDP or TCP communications.

  • When used with UDP communications, the provided size is passed to the system to define the size of the Windows receive buffers as well as those within VTScada.
    The system receive buffer size will be set to the larger of the ReceiveLen parameter or the current default receive buffer size for the windows buffers. An upper limit of 4 MiB may be provided for the system level buffer. If the ReceiveLen parameter is greater than that, the Windows buffer will be restricted to 4 MiB.
  • When used with TCP communications, VTScada will use the Windows default receive buffer size.
Flush
Required. Any logical expression. If true, the transmit buffer will be flushed (transmitted) after each write to the stream.

This normally should be false to reduce network traffic by allowing the driver to group smaller packets into a single larger packet.
ProtocolFilters

An optional array that specifies what filtering should be applied to socket connections. The order in the ProtocolFilter array determines priority.

Each filter item consists of two elements where the first is the filter type ("TLS/SSL", "VIC", "NULL" and "PROXY") The second is a value dependent upon the filter type, noting that there is no second value for "VIC" and "NULL" types. The certificate subject name is not normally used on a client connection.

Examples:
For TLS/SSL the value is the subject name in the certificate, given as "CN=host.example.com".
For PROXY it is a string.

     PFilter = New(2);
     PFilter[0] = New(2);
     PFilter[0][0] = "SSL";
     PFilter[0][1] = "CN=host.example.com";
     PFilter[1] = New(2);
     PFilter[1][0] = "NULL";
     PFilter[1][1] = "";
InboundPortOrStream
Optional. Used only in connection with a UDP connection.

If set, this should be an existing UDP stream as returned from a ServerSocket, for the same remote IP as is being connected to.

Incoming UDP datagrams on the ServerSocket stream will continue to be received, but the stream can also be used for datagram transmission. If InboundPortOrStream is not a stream, it is interpreted as a local port number on which to listen for inbound datagrams.

The stream returned by ClientSocket can be used, in both cases, for datagram transmission and reception.
IPOut
Optional Used to identify the IP of the network interface card from which to transmit UDP datagrams. LocalIP s of use on multi-homed machines to identify the physical IP binding to use. No default is provided.
PortOut
Optional. Used to identify the local port from which to transmit UDP datagrams No default is provided.
MatchRemoteUDPSourcePort

When Invalid or FALSE (default) datagrams transmitted from any UDP port on the remote host to InboundPortOrStream on the local computer will be accepted and routed into the UDP stream returned from ClientSocket.

 

When TRUE, only datagrams from the remote host sent to InboundPortOrStream on the local computer and sent from the same port as that to which this UDP stream will transmit, will be routed into this UDP stream.

All other datagrams from the remote host sent to the UDP port this ClientSocket is listening on will cause a SocketWait trip, assuming you have an active ServerSocketStart monitoring the local UDP port. The intent of this is to provide a means whereby UDP datagrams from different ports on a remote device can be separated into different UDP streams. MatchRemoteUDPSourcePort provides a method to override this behavior when required.

Comments

This function will return the integer 32767(short) when the socket is created but not yet connected, a stream value when the connection is made, or a short integer error code. If the socket connection is lost (server shutdown ) the stream is closed and set invalid ( no error code returned).

Note that codes 804 through 808 are relevant only for ClientSockets that operate in TLS mutual authentication mode.

Client Socket Error Codes

Error Code

Description

-2 Failed to open the certificate store. This is likely because the user account you are running VTScada under has insufficient privilege
-21 Public/private key pair mismatch. The private key stored for a certificate does not match the private key. Reinstall your certificate and private key pair.
-87 Insufficient private key permission. The user account you are running VTScada under has insufficient privilege to use the private key associated with the certificate.
-257 Certificate expired. Renew it.
-271 Failed to find certificate. Check the spelling of the certificate name if you typed it. Ensure that the certificate is installed in the correct Certificate Store.
-802 Neither the Common Name (CN) or Subject Alternative Names (SAN) used in the X.509 certificate match the target device name.
-803 There is no Local Security Authority (LSA) context for the operation
-804 The clocks on the client and server machines are too far skewed.
-805 Untrusted certificate, the server's certificate is untrusted by the client or vice-versa.
-806 Bad TLS handshake. Often caused by failing to provide a client certificate when the server requires one.
-807 The certificate given to the server is not yet valid.
-808 The certificate given to the server has expired.
-1312 Failed to load private key. Usually insufficient permission or missing private key.
-10051 WSAENETUNREACH - No suitable source address can be found for the destination.
-10060 WSAETIMEDOUT - No connection can be made.
-11001 WSAHOST_NOT_FOUND - A bad name was supplied, such that no IP address can be found.
-10061 Cannot connect to Host at port number specified
-11004 Cannot find requested host

Other socket errors may return individual error codes, refer to the MSDN documentation for Windows Sockets Error Codes, currently found at: https://docs.microsoft.com/en-us/windows/desktop/winsock/windows-sockets-error-codes-2

The client socket function has a slightly different behavior, depending on whether the connection is made via TCP or UDP. The difference is explained in the following two diagrams.

Client Sockets on TCP:

  1. A ClientSocket statement runs and returns an integer value.
  2. An outbound connection is made.
  3. When the connection is established, the stream is triggered.
  4. The stream trigger causes the value returned from 1 to become a stream value.
  • If the connection attempt fails at any point, the value returned from 1 will become a negative integer, representing an error code

Client Sockets on UDP:

  1. A ClientSocket statement runs and returns a stream value.
  2. Stream writing statements are used to write data to the stream
  3. A UDP datagram is issued to the target device each time a write is done by script code.

Example:

Init [
  If 1 Main; 
  [ 
    Client = ClientSocket(0 { TCP/IP protocol }, 
                          "WServer" { Host }, 
                          20000 { Port number }, 
                          1024, 1024 { Buffering }, 
                          1 { Flush after writes }); 
  ] 
] 
Main [
  { If stream connection lost, retry connection } 
  If TimeOut(! Valid(Client), 2) Init;
  { Exit if return value valid and not a stream } 
  If ValueType(client) != 8 Error;
  { Read stream data as received or on demand with "r" key } 
  If GetStreamLength(Client) > 0 || MatchKeys(2, "r");
  [ 
    SRead(Client, Concat("%", Concat(GetStreamLength(Client),
          "c")), data); 
  ] 

  { Write stream data to server every second } 
  If TimeOut(1, 1);
  [ 
    SWrite(Client, "%s", Concat(" Hello World ",
           Time(Seconds(), 3)));
  ] 
  { Close stream if window closed, then stop } 
  If WindowClose(Self());
  [ 
    CloseStream(client);
    Slay(Self(), 1) ;
  ] 
  { Display received data and connection status } 
  ZText(10, 150, Data, 2, 0);
  ZText(200, 100, Cond(ValueType(Client) == 8,
  "Connected", "Not Connected"), 10, 0); 
]
Error [
  { Display error code } 
  ZText(100, 130, Concat("Client error code : ", Client),
        10, 0); 
]