Tuesday, 23 August 2016

Using Sockets with Gambas

Sockets are defined as communication end-points, and relate to a means of computer comms using IP addresses and ports.


Sockets are generally used to send data between two computers on a network, but may also be used to communicate between two programs on the same computer.


This post provides a simple introduction using the Gambas Client and Server Socket components.

Some background


As you probably know, an IP address defines both the network and the computer identity. The first part of the IP relates to the network, the second part the computer. Just where the first part ends and the second part starts is determined by the sub-net mask.

Each computer also has a number of ports. These are not physical ports (e.g. like USB sockets) but a simple number within a 64k range (i.e. 0 to 65536). So it is possible for a computer to "talk" to a number of other devices at the same time by using dedicated ports for each conversation.



We can also use the same approach to communicate between two programs on the same computer by using network loopback.


In the illustrations above, the sockets are referred to as "sockets" or "client sockets". Server sockets are different, and their job is to "listen" for requests to connect. Once the request is accepted by the server socket, a new instance of a [client] socket is created and used for communication with the remote socket.

Here, the use of the word "server" does not mean network server. The server socket may be used on any type of computer.

Generally, a computer with a server socket is providing some kind of data to one or more clients. For example, a Raspberry Pi measuring temperature, humidity and atmospheric pressure could include a server socket, allowing access to its data via a number of clients on a network.

Let's add some example IPs and port numbers for the two-computer setup.


For the server socket, we must specify a port to be used by the Client to request connection. In the example above, computer A needs to request a connection to computer B by using the IP & port: 192.168.0.25:12345

If a request for connection is accepted, computer A will open a port (port x) which is basically a port number selected by the operating system.

For two programs running on the same computer (one using a Server Socket, the other a Client Socket) the IP is the loopback address: 127.0.0.1

Create a Gambas project


In this simple example, I will use a single computer for both Client & Server socket programs.

Gambas Server Socket code


So first we can create a Gambas project using a Server Socket component. We need to include gb.net and add a Server Socket component to the form.

We also need a TextArea and a button with the text changed to: Listen



Now add some code to set the socket port & socket type when the program starts:-

Public Sub Form_Open()

    ServerSocket1.Port = 12345
    ServerSocket1.Type = Net.Internet

End


Then we need to ensure that the port is closed when our program closes:-

Public Sub Form_Close()
 
  Close #ServerSocket1
 
End


And then we simply need to Listen on our chosen port:-

Public Sub Button1_Click()
 
  ServerSocket1.Listen(1)
 
End


The Listen method's argument (0) allows the server socket to connect to any number of Clients, but it is generally a good idea to limit connections to a low value.

When you run this program and click on the button, you can test that it is working by using the terminal command:-

netstat -at

...the output on my terminal looks like this:-



Here we see that port 12345 is in the LISTEN state.

So this minimalistic code is only capable of listening at the moment, and if you click the button more than once, you will get an error. To avoid the error and provide some feedback, modify the button click code:-

Public Sub Button1_Click()
 
  ServerSocket1.Listen(1)
 
  If ServerSocket1.Status = Net.Active Then
    Button1.Enabled = False
    TextArea1.Text &= "Listening on Port: " & ServerSocket1.Port & gb.CrLf
  End If
 
End


This program also needs to be able to connect with a Client Socket, so add this code:-

Public Sub ServerSocket1_Connection(strHostIP As String)
Dim thisSocket As Socket

  If ServerSocket1.Status > Net.Inactive Then
    thisSocket = ServerSocket1.Accept()
    thisSocket.Blocking = False
    If thisSocket.Status = Net.Connected Then
      TextArea1.Text &= "Connected: " & thisSocket.RemoteHost & ":" & thisSocket.RemotePort & gb.Lf
    End If
    Write #thisSocket, "Hello little client!", Len(

"Hello little client!")
  Endif
End


When the _Connection event is raised, we must use the .Accept method to create a new Socket object. We can then use properties and methods for this new socket such as write/send text.

Gambas Client Socket code


Create a second Gambas project. As before, include gb.Net and a Button and a TextArea, and add a Client Socket component to the Form.

Set the button text to: Connect



Now add some code to set the socket server IP address and socket port when the program starts:-


Public Sub Form_Open()

  ClientSocket.Host = "127.0.0.1"
  ClientSocket.Port = "12345"
 
End


Then we need to ensure that the port is closed when our program closes:-

Public Sub Form_Close()

   If ClientSocket.Status > Net.Inactive Then
    Close #ClientSocket
  Endif

 
End


And then we simply need to use the button to connect using the Client.Connect() method:-

Public Sub Button1_Click()
 
  ClientSocket.Connect()

  If ClientSocket.Status > Net.Inactive Then
    Wait 1
    If ClientSocket.Status = Net.Connected Then
      Write #ClientSocket, "Hi Mr Server!", Len("Hi Mr Server!")
    Else
      Close #ClientSocket
      TextArea1.Text &= "Error: Timeout"
    End If
  End If
 
End


In addition, I have allowed 1 second for connection to be made, and then test for an active connection. I've also added a timeout notification and a short message to send to the server.

Now you should be able to run the server program (and check it is Listening by using Netstat), and also run the client program (and again use netstat to check that they are connecting).

More...


These two programs are very basic and don't even respond to one another's messages!

So they should be enhanced by the following additions:-

Add "Read" routine to server code like this:-

Public Sub Socket_Read()
Dim strMsg As String
 
  If Last.Status <> Net.Connected Then Return
  Read #Last, strMsg, Lof(Last)
  TextArea1.Text &= "incoming message: " & strMsg & gb.Lf
 
End


...and to client code like this:-

Public Sub clientSocket_Read()
Dim strMsg As String

  If clientSocket.Status = Net.Connected Then
    Read #ClientSocket, strMsg, Lof(ClientSocket)
    TextArea1.Text &= "incoming: " & strMsg & gb.Lf
  Endif  
End


Use socket Error events. For the Server code:-

Public Sub ServerSocket1_Error()
 
   Message.Error("Unable to bind socket")
 
End


...and for the Client:-

Public Sub ClientSocket_Error()

  Select Case ClientSocket.Status
    Case Net.CannotCreateSocket
      Message.Error("Unable to create a socket")
    Case Net.HostNotFound
      Message.Error("Host not Found")
    Case Net.ConnectionRefused
      Message.Error("Remote server refused connection")
    Case Net.CannotRead
      Message.Error("Data read error")
    Case Net.CannotWrite
      Message.Error("Data write error")
  End Select

End



Be sure to checkout the Gambas documentation for details of other Methods, Properties and Events for server & client sockets.

4 comments:

  1. Hi Captain.
    I got a question. Using server socket and the "listen" method i can intercept the fact that a remote client is (or isn't) sending me some packets.
    But how i can get those packets and read the data inside?
    PS: really i had to "sniff" the packets and fire and alarm that the original receiving server has not in his code ...

    ReplyDelete
    Replies
    1. Not sure if I understand your question, but the important point about a Server Socket is that it just Listens for incoming requests from Clients.

      Let's assume there is a remote Client (on another computer) requesting a connection with our local machine; The idea is for the Server Socket to react to the incoming remote request by creating another Socket which is then used to communicate with the remote Client. It does this with the code in my example: thisSocket = ServerSocket1.Accept()

      Once this connection between local (thisSocket) & remote Client is established, the server socket can carry on listening for new requests (e.g. from other remote Clients, maybe on on other computers).
      Note: the number of connections allowed depends upon your code, so ServerSocket.Listen(5) would allow up to 5 connections.

      I hope this helps.

      Delete
  2. How do you close a individual socket on the server

    ReplyDelete
    Replies
    1. I don't think you can close just one individual socket on the server side, although you can Close or Pause all of them.

      On the client side you can close a connection: Close #ClientSocket

      Once the client has closed a socket on the client side, it should be released by the server automatically, so that it can be re-used by the system.

      Delete