# Socket Programming in Python

Python is a versatile programming language that can be used for various projects. In this blog post, we will discuss socket programming in python. We will cover the basics of sockets, and how to use them to create network connections between devices. We will also provide some example code for those who want to try out socket programming in python. So, if you are interested in learning more about sockets, or want to start using them in your Python projects, keep reading!

The Python `socket` module provides a straightforward interface to the Berkeley sockets API. But it uses Python’s object-oriented style. The `socket()` function returns a *socket object* whose methods implement the various socket system calls.

The primary socket API functions are:

* `socket()`
    
* `.bind()`
    
* `.listen()`
    
* `.accept()`
    
* `.connect()`
    
* `.send()`
    
* `.recv()`
    
* `.close()`
    

## Server socket vs client socket

Sockets can mean subtly different things depending on context. There is a difference between a client socket and a server socket.

A client socket is an endpoint of a conversation. It usually is only used for one exchange or a small set of sequential exchanges, and that is it.

A server socket on the other hand is more like a switchboard operator. It doesn't send or receive any data. It only produces client sockets. Each client socket is created in response to some other client socket doing `connect()` to the server. Once a client socket is created, the two clients are free to communicate with each other.

An example of this distinction can be found on your browser. The web browser you operate uses client sockets exclusively. The web server that handles requests from the client uses both server sockets and client sockets.

## Echo client and server

Below is the minimum example code. A server that echoes all data it receives back (servicing only one client) and a client that sends data.

### Echo Server

```python
import socket

QUEUE_SIZE = 5
BUFFER_SIZE = 4096

def serve(host, port):
	# Create a server socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
	# Bind socket to host and port
    sock.bind((host, port))
    
	# Officially become a server socket
	sock.listen(QUEUE_SIZE)
	
	# Accept connection from outside
    client_socket, addr = sock.accept()
    
	while True:
		data = client_socket.recv(BUFFER_SIZE)
		if not data:
			break
		client_socket.sendall(data)

	# Close the client socket
	client_socket.close()    

	# Close the server socket
    sock.close()
```

A server must perform the sequence `socket()`, `bind()`, `listen()`, `accept()`. The server does not `send()`, `sendall()` or `recv()` on the socket it is listening on but on the new socket returned by `accept()`.

The `socket()` function takes two parameters: address family and socket types. The address family is usually IPv4 (`socket.AF_INET`) or IPv6 (`socket.AF_INET6`). Socket types are usually streaming sockets (`socket.SOCK_STREAM`) or datagram sockets (`socket.SOCK_DGRAM`).

The value passed to `bind()` depends on the socket's address family. For `socket.AF_INET` (IPv4), it expects a `(host, port)` tuple. `host` can be a hostname, IP address, or an empty string. The IP address `127.0.0.1` is the standard IPv4 address for the loopback interface, so only processes on the host will be able to connect to the server. If you pass an empty string, the server will accept connections on all available IPv4 interfaces.

Socket `listen()` can take a parameter called *backlog*. When a backlog is specified, it tells the system to allow a number of unaccepted connections before refusing new connections. A good number for a backlog is 5 (the typical max). If you write your code correctly, five should be plenty.

### Echo Client

```python
import socket

BUFFER_SIZE = 4096


def connect(host, port, message):
    # Create a client socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

	# Connect to a server
    sock.connect((host, port))

	# Send message to the server
    sock.sendall(bytes(message, "utf-8"))

	# Capture any information back 
	# from the server
    data = sock.recv(BUFFER_SIZE)

	# Close the connection
    sock.close()
```

### Notes on `recv()` and `send()`

`send` and `recv` operate on network buffers. They do NOT necessarily handle all the bytes you hand them (or expect from them). They return when the network buffers have been filled (`send`) or emptied (`recv`). They then tell you how many bytes they handled. It is **your** responsibility to call them again until your message has been completely dealt with.

The `recv()` method receive data from the socket. It returns the number of bytes received, which may be less than the size of the data. It requires the `bufsize` parameters to be specified. It is the maximum amount of data to be received at once. As for the value of the `bufsize`, the documentation recommended the following:

> For best match with hardware and network realities, the value of *bufsize* should be a relatively small power of 2, for example, 4096.

Typically we set this to 4096 or 8192.

The other side has closed the connection when a `recv` returns 0 bytes. You will NOT receive any more data from this connection.

The `send()` method also sends the number of bytes sent, which may be less than the size of the data passed in. To ensure you send all the data, use the `sendall()` method instead.

## Conclusion

As we have seen, sockets are the fundamental building blocks of network communications. By learning how to use them in Python, we can easily add this powerful tool to our arsenal. With a little practice, you'll be able to build robust client-server applications that can communicate across networks. Thanks for reading!
