Network programming in python with examples
GETING A HOST NAME
>>> import socket
>>> host_name = socket.gethostname()
>>> print ("Host name: %s" %host_name)
>>> print ("IP address: %s",host_name)
GETTING A HOST NAME WITH A FUNCTION DEFINITION
import socket
def print_machine_info():
host_name = socket.gethostname()
ip_address = socket.gethostbyname(host_name)
print ("Host name: " ,host_name)
print ("IP address: " ,ip_address)
if __name__ == '__main__':
print_machine_info()
The hostname is what you assigned to your computer when you configured your operating system. This output will be different on your machine depending on the system's host configuration. Here hostname indicates where the Python interpreter is currently executing.
Retrieving a remote machine's IP address
If you need to know the IP address of a remote machine, you can use a built-in library function, gethostbyname(). In this case, you need to pass the remote hostname as its parameter.
import socket
def get_remote_machine_info():
remote_host = 'www.python.org'
try:
print ("IP address of %s: %s" ,(remote_host,socket.gethostbyname(remote_host)))
except socket.error as err_msg:
print ("%s: %s" %(remote_host, err_msg))
if __name__ == '__main__':
get_remote_machine_info()
Converting an IPv4 address to different formats
When you would like to deal with low-level network functions, sometimes, the usual string notation of IP addresses are not very useful. They need to be converted to the packed 32-bit binary formats.
The Python socket library has utilities to deal with the various IP address formats. Here, we will use two of them: inet_aton() and inet_ntoa().
Let us create the convert_ip4_address() function, where inet_aton() and inet_ntoa() will be used for the IP address conversion. We will use two sample IP addresses, 127.0.0.1 and 192.168.0.1.
import socket
from binascii import hexlify
def convert_ip4_address():
for ip_addr in ['127.0.0.1', '192.168.0.1']:
packed_ip_addr = socket.inet_aton(ip_addr)
unpacked_ip_addr = socket.inet_ntoa(packed_ip_addr)
print ("IP Address: %s => Packed: %s,Unpacked: %s" %(ip_addr,
hexlify(packed_ip_addr),unpacked_ip_addr))
if __name__ == '__main__':
convert_ip4_address()
Finding a service name, given the port and protocol
If you would like to discover network services, it may be helpful to determine what network services run on which ports using either the TCP or UDP protocol.
import socket
def find_service_name():
protocolname = 'tcp'
for port in [80, 25]:
print ("Port: %s => service name: %s" %(port,
socket.getservbyport(port, protocolname)))
print ("Port: %s => service name: %s" %(53, socket.getservbyport(53,'udp')))
if __name__ == '__main__':
find_service_name()
Converting integers to and from host to network byte order
If you ever need to write a low-level network application, it may be necessary to handle the low-level data transmission over the wire between two machines. This operation requires some sort of conversion of data from the native host operating system to the network format and vice versa. This is because each one has its own specific representation of data.
import socket
def convert_integer():
data = 1234
# 32-bit
print ("Original: %s => Long host byte order: %s, Network byte order: %s" %(data, socket.ntohl(data), socket.htonl(data)))
# 16-bit
print ("Original: %s => Short host byte order: %s, Network byte order: %s" %(data, socket.ntohs(data), socket.htons(data)))
if __name__ == '__main__':
convert_integer()
Setting and getting the default socket timeout
Sometimes, you need to manipulate the default values of certain properties of a socket library, for example, the socket timeout.
You can make an instance of a socket object and call a gettimeout() method to get the default timeout value and the settimeout() method to set a specific timeout value. This is very useful in developing custom server applications.
import socket
def test_socket_timeout():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print ("Default socket timeout: %s" %s.gettimeout())
s.settimeout(100)
print ("Current socket timeout: %s" %s.gettimeout())
if __name__ == '__main__':
test_socket_timeout()
Handling socket errors gracefully
In any networking application, it is very common that one end is trying to connect, but the other party is not responding due to networking media failure or any other reason. The Python socket library has an elegant method of handing these errors via the socket.error exceptions. In this recipe, a few examples are presented.
import sys
import socket
import argparse
def main():
# setup argument parsing
parser = argparse.ArgumentParser(description='Socket Error Examples')
parser.add_argument('--host', action="store", dest="host", required=False)
parser.add_argument('--port', action="store", dest="port", type=int,required=False)
parser.add_argument('--file', action="store", dest="file", required=False)
given_args = parser.parse_args()
host = given_args.host
port = given_args.port
filename = given_args.file
# First try-except block -- create socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as e:
print ("Error creating socket: %s" % e)
sys.exit(1)
# Second try-except block -- connect to given host/port
try:
s.connect((host, port))
except socket.gaierror as e:
print ("Address-related error connecting to server: %s" % e)
sys.exit(1)
except socket.error as e:
print ("Connection error: %s" % e)
sys.exit(1)
# Third try-except block -- sending data
try:
msg = "GET %s HTTP/1.0\r\n\r\n" % filename
s.sendall(msg.encode('utf-8'))
except socket.error as e:
print ("Error sending data: %s" % e)
sys.exit(1)
while 1:
# Fourth tr-except block – waiting to receive data from remote host
try:
buf = s.recv(2048)
except socket.error as e:
print ("Error receiving data: %s" % e)
sys.exit(1)
if not len(buf):
break
# write the received data
sys.stdout.write(buf.decode('utf-8'))
if __name__ == '__main__':
main()
Modifying a socket's send/receive buffer sizes
Let us manipulate the default socket buffer size using a socket object's setsockopt()
method.
import socket
SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096
def modify_buff_size():
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
# Get the size of the socket's send buffer
bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
print ("Buffer size [Before]:%d" %bufsize)
sock.setsockopt(socket.SOL_TCP,socket.TCP_NODELAY, 1)
sock.setsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF,
SEND_BUF_SIZE)
sock.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,
RECV_BUF_SIZE)
bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
print ("Buffer size [After]:%d" %bufsize)
if __name__ == '__main__':
modify_buff_size()
Changing a socket to the blocking/non-blocking mode
By default, TCP sockets are placed in a blocking mode. This means the control is not returned to your program until some specific operation is complete. If you call the connect() API, the connection blocks your program until the operation is complete. On many occasions, you don't want to keep your program waiting forever, either for a response from the server or for any error to stop the operation. For example, when you write a web browser client that connects to a web server, you should consider a stop functionality that can cancel the connection process in the middle of this operation. This can be achieved by placing the socket in the non-blocking mode.
Let us see what options are available under Python. In Python, a socket can be placed in the blocking or non-blocking mode. In the non-blocking mode, if any call to API, for example, send() or recv(), encounters any problem, an error will be raised. However, in the blocking mode, this will not stop the operation. We can create a normal TCP socket and experiment with both the blocking and non-blocking operations.
To manipulate the socket's blocking nature, we should create a socket object first.
We can then call setblocking(1) to set up blocking or setblocking(0) to unset
blocking. Finally, we bind the socket to a specific port and listen for incoming connections.
import socket
def test_socket_modes():
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setblocking(1)
s.settimeout(0.5)
s.bind(("127.0.0.1", 0))
socket_address = s.getsockname()
print ("Trivial Server launched on socket: %s" %str(socket_address))
while(1):
s.listen(1)
if __name__ == '__main__':
test_socket_modes()
Reusing socket addresses
You want to run a socket server always on a specific port even after it is closed intentionally or unexpectedly. This is useful in some cases where your client program always connects to that specific server port. So, you don't need to change the server port.
If you run a Python socket server on a specific port and try to rerun it after closing it once, you won't be able to use the same port.
The remedy to this problem is to enable the socket reuse option, SO_REUSEADDR.
After creating a socket object, we can query the state of address reuse, say an old state. Then, we call the setsockopt() method to alter the value of its address reuse state. Then,we follow the usual steps of binding to an address and listening for incoming client connections.
Import socket
import sys
def reuse_socket_addr():
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
# Get the old state of the SO_REUSEADDR option
old_state = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR )
print ("Old sock state: %s" %old_state)
# Enable the SO_REUSEADDR option
sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
new_state = sock.getsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR )
print ("New sock state: %s" %new_state)
local_port = 8282
srv = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
srv.bind( ('', local_port) )
srv.listen(1)
print ("Listening on port: %s " %local_port)
while True:
try:
connection, addr = srv.accept()
print ('Connected by %s:%s'% (addr[0], addr[1]))
except KeyboardInterrupt:
break
except socket.error as msg:
print ('%s' % (msg,))
if __name__ == '__main__':
reuse_socket_addr()
Printing the current time from the internet time server
Writing an SNTP client
import socket
import struct
import sys
import time
NTP_SERVER = "0.uk.pool.ntp.org"
TIME1970 = 2208988800
def sntp_client():
client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
data = '\x1b' + 47 * '\0'
client.sendto( data.encode('utf-8'),( NTP_SERVER, 123 ))
data, address = client.recvfrom( 1024 )
if data:
print ('Response received from:', address)
t = struct.unpack( '!12I', data )[10]
t -= TIME1970
print ('\tTime=%s' % time.ctime(t))
if __name__ == '__main__':
sntp_client()
How it works…
This SNTP client creates a socket connection and sends the protocol data. After receiving the response from the NTP server (in this case, 0.uk.pool.ntp.org), it unpacks the data with struct. Finally, it subtracts the reference time, which is January 1, 1970, and prints the time using the ctime() built-in met hod in the Python time module.
Writing a simple TCP echo client/server application
After testing with basic socket APIs in Python, let us create a TCP socket server and client now. Here, you will have the chance to utilize your basic knowledge gained in the previous recipes.
How to do it...
In this example, a server will echo whatever it receives from the client. We will use the Python argparse module to specify the TCP port from a command line.
Both the server and client script will take this argument.
First, we create the server. We start by creating a TCP socket object. Then, we set the reuse address so that we can run the server as many times as we need. We bind the socket to the given port on our local machine. In the listening stage, we make sure we listen to multiple clients in a queue using the backlog argument to the listen() method. Finally, we wait for the client to be connected and send some data to the server. When the data is received, the server echoes back the data to the client.
import socket
import sys
import argparse
host = 'localhost'
data_payload = 2048
backlog = 5
def echo_server(port):
""" A simple echo server """
# Create a TCP socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# Enable reuse address/port
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind the socket to the port
server_address = (host, port)
print ("Starting up echo server on %s port %s" % server_address)
sock.bind(server_address)
# Listen to clients, backlog argument specifies the max no of queued connections
sock.listen(backlog)
while True:
print ("Waiting to receive message from client")
client, address = sock.accept()
data = client.recv(data_payload)
if data:
print ("Data: %s" %data)
client.send(data)
print ("sent %s bytes back to %s" % (data, address))
# end connection
client.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Socket Server Example')
parser.add_argument('--port',action="store", dest="port", type=int,
required=True)
given_args = parser.parse_args()
port = given_args.port
echo_server(port)
On the client side code, we create a client socket using the port argument and connect to the server. Then, the client sends the message, Test message. This will be echoed to the server, and the client immediately receives the message back in a few segments. Here, two try-except blocks are constructed to catch any exception during this interactive session.
import socket
import sys
import argparse
host = 'localhost'
def echo_client(port):
""" A simple echo client """
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect the socket to the server
server_address = (host, port)
print ("Connecting to %s port %s" % server_address)
sock.connect(server_address)
# Send data
try:
# Send data
message = "Test message. This will be echoed"
print ("Sending %s" % message)
sock.sendall(message.encode('utf-8'))
# Look for the response
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(16)
amount_received += len(data)
print ("Received: %s" % data)
except socket.error as e:
print ("Socket error: %s" %str(e))
except Exception as e:
print ("Other exception: %s" %str(e))
finally:
print ("Closing connection to the server")
sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Socket Server Example')
parser.add_argument('--port', action="store",dest="port", type=int, required=True)
given_args = parser.parse_args()
port = given_args.port
echo_client(port)
Comments
Post a Comment