Beginner Tutorial#

Before you start#

It is highly encouraged that you read the Understanding HiSock section before reading the tutorial.

This tutorial also relies on you having basic networking knowledge (client/server, IP addresses, ports, etc.) and at least basic Python knowledge.

Lastly, if you don’t have HiSock installed, do that now! Read Installation.

This tutorial will focus on:

  • Creating a server

  • Creating a client

  • Sending and receiving data

  • Acting upon the data

Note

For this tutorial, I will be referring the IP addresses as hisock.get_local_ip(). In reality, you will most likely use a hard-coded IP address or a user input.

Note

When I refer to “a way to identify the client”, I am talking about either:

  • a tuple of the IP address and port of the client or

  • a string of the client’s name (this will have ambiguity if multiple clients share the same name)

Note

There are a few terms that are used interchangeably here.

  • Message, content, and data mean the same thing.

  • Command and event mean the same thing.

Now, without further ado, let’s begin!


Creating our first server#

In HiSock, there exists a class, HiSockServer, in the server module. To create a server, you will need to create an instance of this class. The __init__ function of the HiSockServer class takes a required tuple parameter, which is the IP address as a string and port as an integer to start the server on. To find the local IP address, there is a function called utils.get_local_ip(). For the port, a number between 1024 and 65535 should be fine.

HiSockServer instances have a start() method to them, which will allow the server to listen for commands and data being sent.

import hisock

server = hisock.server.HiSockServer((hisock.get_local_ip(), 6969))

server.start()

That’s basically it! Of course, this server is useless, but hey, it’s a step in the right direction! We’ll add on to this later on.

Obviously, without a client, a server is kind of pointless. So, let’s spice things up with some client code!


Creating our first client#

In HiSock, there is a class, HiSockClient, in the client module. To create a client, you will need to create an instance of this class. The __init__ function of the HiSockClient class takes two required parameters. The first parameter is a tuple with the IP address and port that the server is running on. The second (optional) parameter is for the name of the client, which is used for identification. The third (optional) parameter is the group of the client, which won’t be talked about in this tutorial.

Like HiSockServer, HiSockClient needs to have its start() method called to start the client.

import hisock

client = hisock.client.HiSockClient(
    (hisock.get_local_ip(), 6969),
    name=input("What is your name? >")
)

client.start()

Like the server, this doesn’t do anything at all yet. Next, we will explore sending and receiving data in an example.


Transmitting Data#

Let’s explore transmitting data for HiSock!

HiSock is an event-driven module, and as such, has an on decorator and send() methods for both HiSockClient and HiSockServer.


Receiving data#

Note

There is another way of receiving data, which is the recv() method. This is not covered in this tutorial, but it is covered in the Intermediate-level Tutorial.

When a function is prefaced with the on decorator, it will run on something. It will listen for a command and run when that command is received.

The on decorator takes a maximum of three parameters. One of the parameters is the command to listen on. The second (optional) parameter is whether to run the listener in its own thread or not. The third (optional) parameter is whether to override a reserved command, and this tutorial won’t be covering it.

For the server: The on decorator will send a maximum of two parameters to the function it is decorating (there are a few exceptions we will touch on). The first parameter is the client info. It is an instance of ClientInfo that includes the client’s name, client IP address, and the group the client is in. The second parameter is the data that is being received.

For the client: the on decorator will send a maximum of one parameter to the function it is decorating, which will be the message or content the client receives (in most cases).

Here’s an example with the on decorator in use in a server. Here, the server has a command, print_message_name, and will print the message that it gets and who sent it.

server = ...

@server.on("print_message_name")
def on_print_message_name(client: hisock.ClientInfo, message: str):
    print(f'{client.name} sent "{message}"')

server.start()

Here’s another example with receiving data, this time on the client-side. The client will receive a command, greet, with a name. It will then print out a greeting with the name.

client = ...

@client.on("greet")
def on_greet(name: str):
    print(f"Hello there, {name}!")

client.start()

If the threaded parameter for the on decorator is True, then the function being decorated will run in a separate thread. This allows blocking code to run while still listening for updates.

It is useful if you want to get user input but also want to have the user receive other data.

client = ...

@client.on("ask_question", threaded=True)
def on_ask_question(question: str):
    """Contains blocking code with ``input()``."""
    answer = input(f"Please answer this question: {question}\n>")
    # ... send answer to server ...

@client.on("important")
def on_important(message: str):
    """This is important and cannot be missed!"""
    ...

client.start()

Sending data#

HiSock has multiple send methods. For now, we will be talking about sending to the server from one client or to one client from the server.

For the server: Sending data from the server to one client in HiSock uses the send_client() method. This method takes in a maximum of three parameters. The three parameters (in order) are a way to identify the client, the command to send, and the message being sent (optional). Although we won’t be talking about it here, send_all_clients() does exactly what it says. It will do send_client() to all the clients that are connected, and only takes in the command and optional message

For the client: Sending data to the server in HiSock uses the send() method. This method takes a maximum of two parameters. The first parameter is the command to send, and the second parameter is the message being sent (optional).

Here is an example of sending data with a server-side code block:

server = ...

@server.on("join")
def on_client_join(client: hisock.ClientInfo):
    server.send_client(client, "ask_question", "Do you like sheep?")

@server.on("question_response")
def on_question_response(client: hisock.ClientInfo, response: str):
    server.send_client(client, "grade", 100)

server.start()

And here is an example on the client-side:

client = ...

@client.on("ask_question")
def on_ask_question(question: str):
    answer = input(f"Please answer this question: {question}\n>")
    client.send("question_response", answer)

@client.on("grade")
def on_grade(grade: int):
    print(f"You got a {grade:>3}%.")

client.start()

Reserved events#

As I stated before, not every receiver has a maximum of two parameters passed to it. Here are the cases where that is the case.

HiSock has reserved events. These events shouldn’t be sent by the client or server explicitly as it is currently unsupported.

Here is a list of the reserved events:

Server:

  • join

    The client sends the event join when they connect to the server. The only parameter sent to the function being decorated is the client info.

  • leave

    The client sends the event leave when they disconnect from the server. The only parameter sent to the function being decorated is the client info.

  • name_change

    The client sends the event name_change when they change their name. The parameters sent to the listening function are (in order) the client info, the old name, and the new name.

  • group_change

    The client sends the event group_change when they change their group. The parameters sent to the listening function are (in order) the client info, the old group, and the new group.

  • message

    When the server receives a command, it’ll send an event to itself called message which will have two parameters. The two parameters are the client info who sent it and the raw data which was received.

  • *

    This will be called when there is no listener for an incoming command and data. The three parameters are the client info, the command, and the content.

Client:

  • client_connect

    When a client connects to the server, all the clients will have this event called. The only parameter for this is the client info for the client which joined.

  • client_disconnect

    When a client disconnects from the server, all the clients will have this event called. The only parameter for this is the client info for the client which left.

  • force_disconnect

    The server sends the event force_disconnect to a client when they kick the client. There are no parameters sent with the function that is being decorated with this.

  • *

    This will be called when there is no listener for an incoming command and content. The two parameters are the command and the content.


Typecasting#

Changed in version 3.0: Previously, HiSock used manual type casting, where the type hints of the event’s function actually determined the type the data was supposed to be interpreted. However, now, it doesn’t matter. Even if it doesn’t matter, it’s still recommended to type hint your functions!

You shouldn’t worry about this too much, but HiSock has a system called “typecasting” when transmitting data. This system is how hisock will convert supported datatypes to and from bytes to get sent along the network.

Data sent and received can be one of the following supported types:

  • bytes

  • str

  • int

  • float

  • bool

  • NoneType

  • list (with the types listed here)

  • dict (with the immutable types listed here)

Note

There is a type alias in hisock.utils called Sendable which is a union of all of these.

If you have a type that isn’t one of these supported types, such as a class, consider manually converting to and from bytes.


Dynamic arguments#

Remember where I said the on decorator will call the function with a maximum number of parameters?

In HiSock with an _unreserved_ event, the function to handle it can be called with the maximum number of parameters or less. Note that in a reserved event, dynamic arguments doesn’t apply.

As an example, for the server: If an event has 1 argument, it will only be called with the client info. If it has 2 arguments, it will be called with the client info and the message. If it has 0 arguments, it’ll be called as a void (no arguments).

Data can be sent similarly. If there is no data sent, the server will receive the equivalent of None for the typecasted data.

Here are a few examples of this with a server-side code block.

@server.on("event1")
def on_event1(client: hisock.ClientInfo, message: str):
    print(f"I have {client=} and {message=} as a string!")

@server.on("event2")
def on_event2(client: hisock.ClientInfo, message: int):
    print(f"I have {client=} and {message=} as an integer! {message+1=}")

@server.on("event3")
def on_event3(client: hisock.ClientInfo):
    print(f"I only have {client=}!")

@server.on("event4")
def on_event4():
    print("I have nothing.")

Here is an example with a client-side code block that ties into the server-side code block above:

client.send("event1", "Hello")  # Server will receive "Hello"
client.send("event1")  # Server will receive an empty string
client.send("event2", b"123")  # Server will receive 123 and output 124
client.send("event2")  # Server will receive 0 and output 1
client.send("event3", "there")  # Server won't receive "there"
client.send("event4", "Hi")  # Server won't receive anything

Conclusion#

Now, you know how to:

  • Create a server

  • Create a client

  • Transmit data

  • Work with dynamic arguments

  • Handle datatypes transmitted

  • Do stuff with the data

Have fun HiSock-ing!