Day 9 : 16 June 2022 : Add a Sockets class and TCP messaging

 My 100 Daze of Code

https://github.com/davidjwalling/100-days-of-code

#9 : Add a Sockets class and TCP messaging

Today we'll add some new classes to our Ava library: Buffer, Channel, Log and Socket. The Buffer class optimizes some behaviors that are similar to vectors and deques. The Channel class defines the state of a connection and maintains read and write buffers. Each Channel has a state which follows defined transitions. The Log is a singleton which manages serialized output to a logging sink. The Socket class implements sockets API functions.

We'll implement only TCP messaging for Today and add UDP support soon. For now, the Log will simply capture error conditions. That is, OS-level error codes and application error codes. Later we'll extend this to output log messages to either a file, an application-defined handler or, on systems that support it, to syslog.

With Today's code, Ava will start listening on a TCP socket bound to the "any" IP version 4 address (INADDR_ANY) and accept incoming connections. We'll test this by pointing a browser with the "http" scheme (unsecured). Later, we'll investigate TLS (SSL) support.

The network traffic will be handled by a dedicated thread that is created and run as our app starts and stopped (joined) when our app stops. On macOS, Linux and Windows, we'll put the "main" thread into a loop on std::getline until an exit command is entered. Since the shutdown logic is built into the IDriver destructor, we don't need to capture any particular shutdown event on the app.

File Structure Updates

We've simply added some new files in the repository root.


CMakeLists.txt Updates

Our source code will now require linking with a threading library, pthread, on Linux and macOS. So we instruct CMake to find the Threads package.


Our library sources are updated to include the new source files (.cpp). However, our public API is still only the api.h and driver.h files. We do not want to publish internal headers.


Now, the target link libraries for the Ava library are different for Clang and GNU compilers - they must include Threads. For Windows, we must must link with wsock32.lib.


Executable Updates

Now our driver program on Linux, macOS and Windows has been updated. We'll still output a message of the day. Then, our IDriver constructor is updated to pass program arguments. The constructor will now perform a more thorough initialization. If this succeeds, AppErr() will return zero and we'll enter a "mainline" loop calling std::getline for user input until "exit" is entered, at which point we'll exit the while loop and return the final application status code (AppErr()). Not shown here, is that when our IDriver goes out of scope and its destructor is called, we'll shutdown our network listener.


IDriver Updates

The IDriver constructor now passes program arguments to the internal IDriver::Driver constructor. If the default constructor is called, no arguments are passed. Also, we've added an AppErr method to the IDriver class. This will invoke Driver::AppErr to return the current application error code, if any. Or, it will return a specifically defined error indicating that the Driver unique pointer was not constructed.


Driver Updates

The IDriver::Driver constructor now calls Start, that will perform any initialization required for our (future) Windows service or Linux daemon. Then Run is called, which will initialize resources and, if that is successful, start a worker thread to run the network I/O mainline and then release resources when we indicate the app must shut down. The IDriver::Driver destructor, called when our instance goes out of scope at the end of the program, will signal the I/O thread to exit mainline and will then join the thread and return.


I/O thread initialization consists of prepping our Sockets library, performing any configuration based on input arguments, and creating our stream socket for TCP, binding to our INADDR_ANY address and starting to listen on the address and port.


The Sockets I/O thread's mainline calls GetClient to perform an accept() call and ServiceChannel, which services any "Channel" that is in a busy state. We have an array of 16 instances of the Channel class in an array on the Driver. These are reused after returning replies to incoming requests. The Sockets I/O thread's finalize routine shuts down and closes the listener socket and cleans up our sockets API layer, if on Windows.


Running Ava on Linux or macOS

Now when we run Ava, it will starting listening for incoming TCP connections, accept HTTP request line and headers and return a CSS and HTML result body.


Enter "exit" at the command prompt to signal the Sockets I/O thread to exit and terminate the program.


Updates to the Objective C Wrapper

Our Driver class no longer contains a "Hello" method. So we've moved the message-of-the-day output up to the Objective C wrapper in the form of the "title" method. Also the "init" method no longer requires an input parameter.


Updates to the SwiftUI App

The SwiftUI driver will call the IDriver constructor and display the program title. The Sockets I/O shutdown occurs when the program is exited. Now, we really only call "title" here so that the created "wrapperItem" is used, to avoid a compiler warning. But the message-of-the-day is not central to our goals anyway. So, we can consider instead invoking IDriver::AppErr and report the result of the Sockets I/O initialization. We can take that change up on a later day. Here is our ContentView for Today:


Debugging on iOS

Now when we run the project on iOS, we can confirm the message-of-the-day. Also, we can point a browser to our iPad at our chosen port and see that Ava returns and HTML body. This is the Channel class behavior. Finally, we use Wireshark to capture the HTTP reply.










Comments

Popular posts from this blog

Day 12 : 19 June 2022 : Adding Windows Service Logic

Day 5 : 12 June 2022 : CMake on Windows

Day 11: 18 June 2022 : Handling Program Arguments