CodingBison

Connection-less sockets do not require any connect() call before we can start sending and receiving data. However, the downside is that each time we send data, we also need to pass the address of the remote socket, where we intend to send the data. This also means that we can use one socket to send data to multiple sockets -- with each call, we can potentially change the address to a new socket endpoint.

The common transport protocol for connection-less sockets is UDP (User Datagram Protocol). Unlike TCP, UDP does not retransmit lost packets and hence is unreliable. Also, it does not estimate the sending rate and hence is not aware of network congestion. However, UDP is faster than TCP and that is why a lot of applications (for example, audio and video) still use UDP.

Let us create a UDP-based socket, socketA, and bind it to a port. We still need to bind the port otherwise the other socket endpoint would not know the address to which it needs to send. Once again, we keep both the sockets on the same machine; for remote machine, the address needs to include the IP address or the machine name instead of ''.

 import socket

 #Open a UDP server socket.
 socketA = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 print(socketA)
 print(type(socketA))

 bindAddress = ("", 9999)
 socketA.bind(bindAddress)

 counter = 1 

 #Receive data two times.
 while counter <= 2:
     recvData = socketA.recvfrom(100)
     print("\nReceived Data")
     print(recvData)
     counter += 1

 #Close the server socket.
 socketA.close()

Let us understand the server implementation.

The server starts by opening a UDP-based socket, socketA -- for UDP, we use SOCK_DGRAM as the socket type. Next, we bind the socket to a port (in this case, 9999). We still need to bind the port otherwise the other socket endpoint would not know the address to which it needs to send. Once again, we have keep both the sockets on the same machine; for remote machine, the address needs to include the IP address or the machine name instead of ''.

The server uses recvfrom() call on socketA and we can retrieve the received data. Please note that recvfrom() also returns the address of the other socket endpoint. We can also use recv() call to receive data on connection-less sockets as well; with recv(), the return value would not include the address of the client socket.

The server code receives data twice (we have custom-made it for the sake of demonstration) and then closes the socket.

Next, let us create another UDP-based socket (which is a client), socketB that forms the second endpoint of the socket "pipe". This client uses the sendto() call to send data to socketA. This call takes not only the string, but also the address of the remote socket, where we want to send the data. Like send() call, sendto() call also returns the number of bytes sent. We send data twice and then close the socket.

 import socket

 #Open a UDP client socket.
 socketB = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 print(socketB)
 print(type(socketB))

 serverAddress = ("", 9999)

 #Send some data to the server.
 numBytes = socketB.sendto(b"Hello World", serverAddress)
 print("Sent " + str(numBytes) + " bytes.")

 #Send some more data.
 numBytes = socketB.sendto(b"101, 280, 237, 680", serverAddress)
 print("Sent " + str(numBytes) + " bytes.")

 #Close the socket.
 socketB.close()
 [user@codingbison]$ python3 udp-client.py 
 <socket._socketobject object at 0xb76df294>
 <class 'socket._socketobject'>
 Sent 11 bytes.
 Sent 18 bytes.
 [user@codingbison]$ 
 [user@codingbison]$ python3 udp-server.py 
 <socket._socketobject object at 0xb76ef48c>
 <class 'socket._socketobject'>

 Received Data
 ('Hello World', ('127.0.0.1', 35886))

 Received Data
 ('101, 280, 237, 680', ('127.0.0.1', 35886))
 [user@codingbison]$ 

Now that we have both the implementations ready, let us run the UDP server. Since recvfrom() is a blocking call, this would block until there is some data from a client or clients.

 [user@codingbison]$ python3 udp-server.py 
 <socket.socket object, fd=3, family=2, type=2, proto=0>
 <class 'socket.socket'>

Like the case of TCP sockets, we can easily verify the state of these sockets using "netstat" tool (or its new variant "ss" tool) on Linux (or MAC OS) machines. If we were to run this tool at this point, then we would find an entry for a UDP socket bound to port 9999. Note that, unlike TCP, UDP sockets do not have a LISTEN state. To have a better output, we pass a set of options to netstat: "u" for UDP only sockets, "p" for printing associated programs, "l" for printing only listening sockets, and "n" for printing numeric addresses. Also, to access names of all programs, we must be logged as a root. We show the output both as the root user and as a non-root user. Both of these output show tcp-server listening on port 9999.

 Output when logged in as root.
 [root@codingbison]# 
 [root@codingbison]# netstat -upln
 Active Internet connections (only servers)
 Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
 udp        0      0 0.0.0.0:631                 0.0.0.0:*                               1/init              
 udp        0      0 0.0.0.0:9999                0.0.0.0:*                               13191/python        
 udp        0      0 0.0.0.0:18990               0.0.0.0:*                               12760/dhclient      
 udp        0      0 0.0.0.0:68                  0.0.0.0:*                               12760/dhclient      
 udp        0      0 :::14112                    :::*                                    12760/dhclient      
 [root@codingbison]# 

 Output when logged in as a non-root user.
 [codingbison@localhost ~]$ netstat -upln
 (Not all processes could be identified, non-owned process info
  will not be shown, you would have to be root to see it all.)
 Active Internet connections (only servers)
 Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
 udp        0      0 0.0.0.0:631                 0.0.0.0:*                               -                   
 udp        0      0 0.0.0.0:9999                0.0.0.0:*                               13191/python        
 udp        0      0 0.0.0.0:18990               0.0.0.0:*                               -                   
 udp        0      0 0.0.0.0:68                  0.0.0.0:*                               -                   
 udp        0      0 :::14112                    :::*                                    -                   
 [codingbison@localhost ~]$ 

Now that we know that the UDP server is bound to port 9999, let us run the UDP client code on another terminal:

 [user@codingbison]$ python3 udp-client.py 
 <socket.socket object, fd=3, family=2, type=2, proto=0>
 <class 'socket.socket'>
 Sent 11 bytes.
 Sent 18 bytes.
 [user@codingbison]$ 

And at this point, we see that the server code has additional information. This is because when we run the "udp-client.py", it sends datagrams to the server socket and the blocked recvfrom() calls return.

 [user@codingbison]$ python3 udp-server.py 
 <socket.socket object, fd=3, family=2, type=2, proto=0>
 <class 'socket.socket'>

 Received Data
 (b'Hello World', ('127.0.0.1', 60688))

 Received Data
 (b'101, 280, 237, 680', ('127.0.0.1', 60688))
 [user@codingbison]$ 

As noted earlier, the recvfrom() call returns both the received data and a tuple (tupSocketA) that consists of (socketA, address). The tuple is ("('127.0.0.1', 60688)") that holds the address from where the data was sent. Since the connection is local, we see the address of the loopback ("127.0.0.1") and we see the port number as 60688. Thus, the UDP socket "pipe" consists of two endpoints both sitting on the same machine -- one endpoint sits on port 9999 and the other endpoint sits on the port 60688.

Note that a recvfrom() call is blocking and so unless it has an incoming data, it will continue to block. This means that the other side needs to do a send() call.

Unlike TCP, when the peer closes the connection, then the recvfrom() call does not return ''. This is because unlike TCP, when we close a UDP socket, it is done silently and the other remote does not know about it, since it is not connected! Thus, the server would not know when the client closed the socket.

Non-blocking Sockets

Like the case of TCP, some of the UDP calls are also blocking like recvfrom() and recv(). We can also make UDP sockets non-blocking using the setblocking() method.

Setting socketA.setblocking(0) means that the socket would become unblocking. This means that the recvfrom() or recv() calls would return immediately if there is nothing to read.

 >>> socketA=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 >>> socketA.bind(('',9999))
 >>> 
 >>> socketA.setblocking(0)
 >>> 
 >>> x = socketA.recvfrom(100)
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 socket.error: [Errno 11] Resource temporarily unavailable
 >>> 

The error, "Resource temporarily unavailable" is a valid error in this case and should be trapped if we wish to work with non-blocking sockets. This error simply means that there is nothing to read. Once the client sends data, then issuing a recvfrom() will return with received data instead of the above error.





comments powered by Disqus