Spectranet: Tutorial 5

From Spectrum
Jump to navigation Jump to search

You will have noted by now that the programs so far stop and wait whenever they want some data.

For many programs, there's nothing wrong with this. There's a broad category of network programs that only do something when data arrives on a socket. However, there are plenty of programs for which this would be a disaster.

Consider a game. If the program stops and waits on recv() each time, it can't process keyboard input or update what's happening on screen. Or even a simple telnet client - it must be able to process user keypresses at any time, as well as process incoming data at any time. Many server programs must handle data from multiple sockets.

Large systems, such as Unix (and its relatives, such as BSD and Linux), as well as Microsoft Windows, have the system call select() which allows you to multiplex many sources of input. The POSIX specification (used by Unix type operating systems, and partially implemented on Windows) also provides a simpler function called poll(). The select() system call is rather more complex than we need, so the Spectranet provides poll() instead. Actually, it provides three poll() routines - because poll() as defined by POSIX is also overkill and adds unnecessary complexity to a relatively simple system. (It is planned that a full implementation of the POSIX poll() will be provided for Z88DK users, once the new fcntl for Z88DK has been developed, which will allow one function to multiplex not only sockets, but other things like serial ports and keyboard entry, all in one go. Until this is the case, generally, you should use pollfd() and pollall() instead).

The function calls that the Spectranet programmer will be most interested in is pollall() and poll_fd(). The poll_fd() function checks a single socket for some kind of 'I'm ready' status. The pollall() function checks all open sockets for some kind of 'I'm ready' status.

It's important to note at this stage that the poll functions aren't merely an 'INKEY$ for sockets' only telling you if the other end sent some data. They don't themselves return any data other than what state the socket is in. The poll routines also tell you why a socket is ready to have something done to it: it will tell you if the other end closed the connection, it will tell you if a listening socket needs to have accept() called, and it will of course tell you if an established socket has some data ready to be read.

The code

The code for this tutorial shows how pollall() is used to handle numerous TCP connections in a server program. If you need to refresh yourself on how a TCP server works, take a look at tutorial 2 which covers this.

You can find code for this tutorial here: [1]. You can find an assembly language version here: (TO DO).

Using pollall

If you look at the example code, you'll see that it is pretty much identical to the example for tutorial 2, until you get to the part where incoming connections are to be accepted. Instead of calling accept(), which will block until a new connection is made, a big while(1) loop is started. Then, near the start of this loop, you will see in the C version of the program:

    #include <sockpoll.h>
    struct pollfd p;

This does the following. It checks all open sockets to see if there's some interesting state that needs to be handled, and if so, the first socket encountered with some kind of ready state is returned. Information about the state is inserted into the pollfd structure. If no sockets are ready, then the function returns 0.

This means a simple way of knowing whether there's something to do is to simply see if polled is greater than zero.

In the example code, you will notice also the following set of if statements (in the C example):

    if(polled == sockfd)
        connfd=accept(sockfd, NULL, NULL);
    else if(polled > 0)
        if(p.revents & POLLHUP)
             rc=recv(polled, rxdata, sizeof(rxdata)-1, 0);

As you can see, if the socket returned is the socket on which listen() was called, the only reason why pollall can have picked it up is that it has a new connection pending. So, when polled==sockfd, accept() is called to accept the connection, and create its data socket - see the explanation of accept() in tutorial 2 if you need to revisit this.

In any other case, there's either data available, or the client closed the connection. You can see what happened by checking the revents bitfield in the pollfd structure - in the example above, the POLLHUP flag is checked. If this is set, then we deal with it by closing that socket. If POLLHUP is not set, then there's data to be read - so it's read with the recv() function.

The pollall() function returns as soon as it's done its work, whether there are ready sockets or not. So in your main loop you can also check for keyboard input, and go ahead and do other things your program needs to do. In the example code, you will see that the program scans the keyboard and exits if the 'x' key is pressed.

In summary

You can use pollall and pollfd to quickly check to see whether an action needs to be carried out on a socket. This allows your program to go on and do other things in the case that nothing needs to be done to a socket. The poll functions tell you what sort of things need to be done to a socket, by telling you if the other end hung up the connection, or whether it sent you data, or whether a listening socket has a connection pending.

The next tutorial covers some generally useful functions in the Spectranet ROM that you may want to use in your programs.

Spectranet: Tutorial 6 - Other useful Spectranet routines.

Further reading

The example IRC client, written in C, shows a client that multiplexes user input with the socket that's connected to the IRC server. You can look at the code here: [2]. It uses the poll_fd() function, since there's only one socket to be polled. The actual polling occurs in the main loop, which is in this file: [3]

The reference for poll, pollall and poll_fd can be found here.