Intermediate-level Tutorial#

Before you start#

It is imperative that you have basic knowledge of HiSock. You should know everything that the beginner tutorial focuses on. If you don’t know, read that now!

This tutorial will focus on:

  • Overriding reserved events

  • How commands and data are sent

  • Threading

  • The other send methods

  • The other receive methods

  • Catch-all listeners (wildcard)

  • Groups, names, and client info


Override#

In HiSock, there are some reserved events. However, some use-cases may want to use the same name as the reserved events for unreserved events. In order to do this, you can override the reserved events. The on decorator has an argument for overriding a command. When set to True, the event will be treated like an unreserved event.

Warning

This isn’t recommended doing most times. Typically, the reserved events are used for handling clients. For example, if you override the join event, you’ll have to have the client send a join event.

Here is an example with a server-side code-block.

server = ...

@server.on("leave", override=True)
def on_leave(client: hisock.ClientInfo, reason: str):
    print(f"{client.name} left because {reason}")
    server.disconnect_client(client, force=True)

server.run()

Now, the leave event won’t be called when a client leaves. Instead, a client will manually send the leave event.


Threading#

In HiSock, there is an option for the client or server to run entirely in a different thread. This can be useful if you want to have a server that doesn’t block the main thread, such as in conjunction with Pygame or Tkinter.

This is really simple to use! In place of HiSockServer or HiSockClient, you can use ThreadedHiSockServer or ThreadedHiSockClient respectively. You can also use threaded_connect() in place of connect() and start_threaded_server() in place of start_server().


How commands and data are sent#

Commands and data are sent in a special syntax.

Note

In this section, text in <> is a placeholder for data.

Reserved commands#

For reserved commands, the syntax is different for each one.

For the client:

  • $KEEPALIVE$

    This command is sent to the client from the server to make sure that the client is still connected.

  • $DISCONN$

    This command is sent to the client from the server to disconnect the client.

  • $CLTCONN$<client info as a stringified dict>

    This command is sent to the client from the server to inform that a new client connected.

  • $CLTDISCONN$<client info as a stringified dict>

    This command is sent to the client from the server to inform that a client disconnected.

For the server:

  • <connecting socket is same as server socket>

    This happens when a new client connects to server.

  • <bad client file number> OR <client info is falsy> OR $USRCLOSE$

    This happens when the client closes the connection and emits its leave, or it encounters an error when transmitting data. The client will be disconnected.

  • $KEEPACK$

    This is sent to the client from the server to acknowledge that the client is still connected.

  • $GETCLT$<client_identifier, either a name or stringified IP>

    This is sent to the server from the client to get the client’s data.

  • $CHNAME$<new name> OR $CHGROUP$<new group>

    This is sent to the server from the client to change the client’s name or group, respectively.


Unreserved commands#

For unreserved commands, the data is sent as follows:

  • command and message sent

    $CMD$<command>$MSG$<message>

  • command sent

    $CMD$<command>


The other send methods#

In HiSock, there are multiple send methods for the server. These methods are:

  • send_client - sends a command and/or message to a singe client

  • send_all_clients - sends a command and/or message to every client connected

  • send_group - sends a command and/or message to every client in a group


A new way to receive data#

In HiSock, there is also a different way to receive data.

Say you want to send data and wait for a response. Normally, you’d have to do something like this:

client = ...

@client.on("start")
def on_start():
    client.send(input("What would you like to say?"))
    print("Waiting for a response...")

@client.on("response")
def on_response(response: str):
    print(f"The server said: {response}")

client.start()

However, there is a better way! There is a method called recv(). This method has two parameters. The parameters (in order) are the command to receive on (optional) and the type to receive as (defaults to bytes).

The recv() method works like the on decorator. If the command to receive on is not specified, recv() will receive on any command or data that is sent and not caught by a function. Otherwise, it will only receive on the command. Then, the message received will be type-casted to the type specified and returned.

recv() is blocking, so the code in the function will pause until it’s done. This is why it’s recommended to use it in a threaded function.

Now, let’s use the example above, but using the recv() method!

client = ...

@client.on("start", threaded=True)
def on_start():
    client.send(input("What would you like to say?"))
    print("Waiting for a response...")
    response = client.recv("response", str)
    print(f"The server said: {response}")

client.start()

Catch-all listeners (wildcard)#

In HiSock, there is a way to catch every piece of data sent that hasn’t been handled by a listener already. This amazing thing is known as the catch-all or wildcard listener. Despite the name, this is not like the message reserved listener. It will only be called when there is no other handler for the data.

The function that will be called for the catch-all listener will be passed in the client info if it’s a server, the command, and the message. If there is no message, it’ll be type-casted into the equivalent of None.

Here is an example with a client-side and server-side code block:

client = ...

client.send(
   "hello i am an uncaught command",
   f"Random data: "
   + "".join(
      [
            chr(choice((randint(65, 90), randint(97, 122))))
            for _ in range(100)
      ]
   ),
)

@client.on("client_connect")
def on_connect(client: hisock.ClientInfo):
    ...

@client.on("client_disconnect")
def on_disconnect(client: hisock.ClientInfo):
    ...

@client.on("*")
def on_wildcard(command: str, data: str):
    print(f"The server sent some uncaught data: {command=}, {data=}")

client.start()
server = ...

@server.on("join")
def on_join(client: hisock.ClientInfo):
   ...

@server.on("leave")
def on_leave(client: hisock.ClientInfo):
   ...

@server.on("*")
def on_wildcard(client: hisock.ClientInfo, command: str, data: str):
    print(
        f"There was some unhandled data from {client.name}. "
        f"{command=}, {data=}"
    )

    server.send_client(client, "i am also an uncaught command", data.replace("a", "ඞ"))

server.start()

Groups, name, and client info#

In HiSock, each client has its own client info. Like mentioned in the previous tutorial, this client info can be a dictionary or an instance of ClientInfo.

Client info contains the following:

  • name

    The name of the client. Will be None if not entered.

  • group

    The group of the client. Will be None if not entered.

  • ip

    The IP and port of the client as a string.