What Is A Network – Complete Guide

Network programming is a fascinating and crucial field in the world of development, and understanding it can unlock new possibilities and optimizations for programs, especially in our modern, interconnected world. As we delve into this realm, we introduce fundamental concepts and walk through practical examples to ensure you not only comprehend the theory but also know how to apply it in real-world scenarios. Join us on this digital journey through the ins and outs of network programming.

What Is Network Programming?

Network programming revolves around writing applications that communicate over a network. This could be anything from a simple data transfer between systems to complex multiplayer game mechanics. At its core, network programming enables different software systems to exchange information, regardless of their geographical separation or the platform they are running on.

What Is It For?

The purposes of network programming are abundant. This technology powers the web applications we use daily, facilitates the multiplayer capabilities in games, and underpins the operations of financial transactions, instant messaging services, and much more. Essentially, if an application needs to talk to another, either on the same system or over the internet, network programming is at play.

Why Should I Learn Network Programming?

Grasping the concepts of network programming empowers you to develop applications with real-time communication features, manipulate remote systems, and understand the architecture of the internet services you use. If you’re aiming to create anything that interacts over a network—from IoT devices to web servers—network programming is an essential skill in your developer toolkit.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Setting Up a Basic Server

Creating a server is the starting point for most network programming tasks. We’ll begin with a basic example using Python’s built-in socket module, which provides the tools for creating network applications.

import socket

# Create a socket object using the socket library
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Host (localhost here) and port number
host = '127.0.0.1'
port = 12345

# Bind the socket to an address and a port
s.bind((host, port))

# Listen for incoming connections (5 can be any number)
s.listen(5)

print(f'Server running on {host}:{port}')

# Accept connections
while True:
   c, addr = s.accept()
   print(f'Got connection from {addr}')

   # Send a thank you message to the client.
   c.send(b'Thank you for connecting')

   # Close the connection
   c.close()

This basic server waits for a connection on localhost and port 12345. Once a connection is made, it sends a message and closes the connection.

Making a Simple Client

To work alongside the server, we’ll write a simple client that can connect and receive data from the server.

import socket

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Define the host and the port to connect to
host = '127.0.0.1'
port = 12345

# Connect to the server
s.connect((host, port))

# Receive data from the server
print(s.recv(1024))

# Close the connection
s.close()

This client connects to the server we previously set up and prints the message it receives. It then closes the connection.

Handling Multiple Clients with Threading

In a real-world scenario, servers should handle multiple clients. We’ll use the threading module to manage concurrent connections.

import socket
import threading

def handle_client(c, addr):
    print(f'Connected to {addr}')
    c.send(b'Hello, you are connected.')
    c.close()

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Host (localhost) and port number
host = '127.0.0.1'
port = 12345

# Bind the socket to address and port
s.bind((host, port))
s.listen(5)

print(f'Server running on {host}:{port}')

# Accept connections
while True:
    c, addr = s.accept()
    threading.Thread(target=handle_client, args=(c, addr)).start()

This server improvement allows it to handle each client in a separate thread, enabling multiple clients to be connected and served simultaneously.

Adding Write and Read Functions

Let’s add functions to handle reading from and writing to a connection, encapsulating these functionalities and making our code cleaner.

# Server side

import socket

def read_from_client(client):
    return client.recv(2048)

def write_to_client(client, message):
    client.send(message)

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# ... (rest of the server setup code remains the same)
# Client side

import socket

def read_from_server(s):
    return s.recv(2048)

def write_to_server(s, message):
    s.send(message)

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# ... (rest of the client setup code remains the same)

These functions will streamline socket interactions, promoting better management of server-client communications as our applications grow in complexity.

Creating a Chatroom

With our knowledge of servers and client operations, we can create a simple chatroom that allows multiple clients to connect and exchange messages.

# Server side

# ... (include the required import statements and function definitions)

# ... (initial server setup code)

# List for keeping track of connected clients
clients = []

# The client-handling function now receives and sends messages to all clients
def handle_client(c, addr):
    global clients
    while True:
        try:
            msg = read_from_client(c)
            if msg:
                print(f'[{addr}] {msg.decode()}')
                for client in clients:
                    if client != c:
                        write_to_client(client, msg)
        except:
            index = clients.index(c)
            clients.remove(c)
            c.close()
            break

# ... (remaining server setup, including main loop)

In a chatroom, the server receives messages from one client and broadcasts them to all others, enabling an interactive environment. This is a foundational step towards building more sophisticated chat applications.

These examples serve as the foundation for network programming, covering how to build a server and client as well as handling multiple clients and basic message broadcasting. In the next part, we’ll expand these concepts to create more advanced networking applications.

Great, let’s expand on the chatroom example by adding features like nickname handling and client disconnection notifications to enhance the experience and further develop our network programming skills.

Firstly, we want to assign a nickname to each client that connects. This allows users to identify who is sending each message in the chatroom. Modify the handle_client function to request a nickname from the client upon connection:

def handle_client(c):
    c.send(b'NICKNAME') # ask for a nickname
    nickname = c.recv(1024).decode()
    clients.append((c, nickname))
    print(f'Nickname of the client is {nickname}')
    broadcast(f'{nickname} joined the chat!'.encode())

    while True:
        # Receiving messages from the client
        # and broadcasting to other clients
        # handle disconnections ...

Now let’s handle incoming messages in a way that includes the sender’s nickname:

def broadcast(message, sender=None):
    for client, nickname in clients:
        if client != sender:
            client.send(message)

# In the 'handle_client' function, update the broadcasting part
msg = read_from_client(c)
if msg:
    print(f'{nickname}: {msg.decode()}')
    broadcast(f'{nickname}: {msg}'.encode(), sender=c)

Next, we address the issue of a client disconnecting. We want the server to notify all remaining clients and remove the disconnected client from the list:

try:
    # Existing code for message handling
except Exception as e:
    index, _ = next((idx, tup) for idx, tup in enumerate(clients) if tup[0] == c)
    clients.pop(index)
    c.close()
    broadcast(f'{nickname} left the chat.'.encode())
    print(f'{nickname} disconnected')
    break

With clients joining and leaving, the server must also be capable of handling errors gracefully, such as a client terminating the connection unexpectedly. Introduce error handling in the handle_client method:

while True:
    try:
        msg = read_from_client(c)
        # Existing broadcast code
    except socket.error as err:
        # Handle errors like disconnection
        print(f'An error occurred: {err}')
        break

Finally, we should ensure that our server can shut down gracefully. To do this, we can catch a keyboard interrupt in the server’s main loop and close all client connections before exiting:

try:
    while True:
        # Existing code for accepting clients
except KeyboardInterrupt:
    print('Server is shutting down.')
    for client, _ in clients:
        client.close()
    s.close()

This additional functionality makes our simple chatroom more robust and user-friendly. By adding nicknames, handling client disconnections, and ensuring error handling and server shutdown, we’ve brought the chatroom closer to a real-world application.

Network programming requires thoughtfulness about the user experience and handling unexpected behavior. These code examples illustrate the importance of anticipating real-world scenarios in network application design.

With these fundamentals in place, you’re well on your way to building more complex networked applications. And remember, at Zenva, we strive to provide you with high-quality learning resources for coding, game creation, and more. Develop your skills further with our interactive courses and become proficient in crafting your own networked systems.

Expanding our network programming adventure, we’ll introduce additional concepts and functions that deepen the functionality of our chatroom. Specifically, we’ll explore command handling for administrative actions, private messaging, and the capability to list all connected users.

Let’s start by implementing a command for administrators to kick users out of the chatroom:

admin_password = "secret"

def handle_client(c, nickname):
    while True:
        try:
            message = read_from_client(c)
            if message.startswith(b"/kick"):
                password, nick_to_kick = message[6:].decode().split()
                if password == admin_password:
                    kick_user(nick_to_kick, 'You were kicked by an admin!')
                else:
                    write_to_client(c, b"Invalid admin password")
            # Existing message broadcasting code
            # ...
        except socket.error as err:
            # Existing error handling code
            # ...

def kick_user(nick, reason):
    client_to_kick = next((client for client, nick_ in clients if nick_ == nick), None)
    if client_to_kick:
        write_to_client(client_to_kick, reason.encode())
        clients.remove((client_to_kick, nick))
        client_to_kick.close()

Additionally, we might want to allow users to send private messages. Below is an implementation for private messaging using the “/msg” command:

def handle_client(c, nickname):
    # ...
    if message.startswith(b"/msg"):
        _, nick_to_msg, *private_msg = message.decode().split()
        private_msg = ' '.join(private_msg).encode()
        send_private_message(nick_to_msg, nickname, private_msg)
    # Existing message broadcasting and error handling code
    # ...

def send_private_message(nick_to_msg, sender_nick, message):
    recipient = next((client for client, nick_ in clients if nick_ == nick_to_msg), None)
    if recipient:
        write_to_client(recipient, f"[Private]{sender_nick}: {message}".encode())

Now let’s enable users to view a list of connected users with the “/list” command:

def handle_client(c, nickname):
    # ...
    if message.startswith(b"/list"):
        list_of_users = ", ".join(nick for _, nick in clients)
        write_to_client(c, list_of_users.encode())
    # Existing private message, broadcasting, and error handling code
    # ...

We might also consider enhancing user experience further by adding the ability for users to change their nicknames:

def handle_client(c, nickname):
    # ...
    if message.startswith(b"/nick"):
        new_nick = message[6:].decode()
        if new_nick and next((n for _, n in clients if n.lower() == new_nick.lower()), None) is None:
            broadcast(f"{nickname} is now known as {new_nick}".encode())
            index = next(idx for idx, (_, n) in enumerate(clients) if n == nickname)
            clients[index] = (c, new_nick)
        else:
            write_to_client(c, b"Nickname is taken or invalid")
    # Existing list users, private message, broadcasting, and error handling code
    # ...

Our chatroom is starting to take shape with features that are commonly expected in modern chat applications. Implementing commands for kicking users, sending private messages, viewing user lists, and changing nicknames are all critical for maintaining an interactive and controlled environment.

Combining these new functionalities with the base we’ve already built, we pave the way to a fully-featured, networked chatroom application. As you work with these code examples, you are not only practicing network programming but are also exploring how to incorporate fundamental features of real-time communication platforms.

At Zenva, our goal is to guide you through this creative process, encouraging you to build and expand upon each concept learned. Continue practicing, and soon enough, you’ll be crafting intricate network applications that drive modern digital interaction.

Continue Your Learning Journey

The exciting world of network programming is vast, and there’s so much more to explore and master. Whether you’ve found a passion for backend development, are curious about how the Internet’s underlying architecture works, or are interested in refining your Python skills, continuing your educational journey is key.

We encourage you to check out our Python Mini-Degree, a valuable resource that will help you deepen your programming knowledge. With a variety of courses on Python basics, app development, and even game creation, you’ll find ample material to forge ahead in your coding adventure.

And for those who desire an even broader scope of learning opportunities, our programming courses offer a rich selection. Our project-based approach empowers you to learn by doing, building a professional portfolio along the way. With our courses, which cater to both beginners and more experienced coders, you can propel your career forward at your own pace. Unlock your potential with Zenva today and take your next big step in the tech industry.

Conclusion

In the journey of network programming, you’ve seen how to transform basic concepts into functional, engaging applications. While our exploration from simple server-client models to a feature-rich chatroom might be complete, remember that learning is a continuous process. Applying these foundational skills in your projects will not only solidify your knowledge but also open doors to innovative possibilities and advance your career as a developer.

At Zenva, we are committed to providing you with the best possible path to mastery. Our Python Mini-Degree is designed to take you further, from the fundamentals right through to creating your own applications. Equip yourself with expertise that is in high demand in today’s tech-driven world. Leap into your future with confidence and curiosity, and let us guide you each step of the way. Start learning with Zenva today and see where your newfound skills will lead you!

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.