Inspired by Ann Harter's post about three dead protocols, which I would not pronounce dead just yet, I also decided that it was time to re-connect with socket programming. I foolishly decided that this was also a great time to apply some of the new features of C++11. Kill two birds with one stone, or something like that. In the end, I narrowly escaped with my sanity, but was able to finish at least an implementation of RFC 865, the Quote of the Day protocol.

The quest begins

C++ does not have a standardized API to deal with sockets. It probably never will. Although there are some higher-level implementations out there, such as Boost.Asio, my inner Klingon decided that it would be more honourable to learn the concepts from the venerable POSIX sockets API. To a C++ programmer, this API is somewhat odd, to say the least. Lots of usage of void* force the diligent programmer to make keen use of reinterpret_cast. Functions return an integer, with -1 indicating an error, and the magical errno variable describing the cause.

Perusal of the documentation yielded the following information for creating a server socket that is reachable via TCP/IP:

  1. Creating a socket with domain AF_INET and type SOCK_STREAM.
  2. Setting the socket option SO_REUSEADDR of level SOL_SOCKET. Otherwise, stale connections might hinder the program from re-binding to the desired address after quitting and restarting the server.
  3. Creating an instance of sockaddr_in and filling it with details about the desired address and port to bind to.
  4. Calling bind() to perform the actual binding.
  5. Calling listen() on the socket to wait for connections. But wait, this does not actually do anything! To really "wait" for a connection, we have to accept() it first.
  6. The call to accept() is blocking by default, meaning that program execution will stop until there is some client to be accepted. The call then returns a socket describing the client connection.
  7. The returned socket is a socket which we may finally use to send some data to!

I wrapped the calls described above in a slightly-improved interface for a simple server class so that other programmers do not have to deal with the innards of the POSIX sockets API as much. This is what the API looks like so far:

Server server;
server.setBacklog( 10 );
server.setBacklog( 2015 );
server.listen();

It is almost-but-not-quite usable. However, we only arrived at obtaining a socket for a client. The socket is again an integer that requires the use of the sockets API. Feeling more foolishness rise in me, I hastened onwards and wrote a nice class for wrapping a client socket. At present, it only wraps the send() function of the POSIX sockets API. But at least I am now able to send strings instead of const void*, hooray:

ClientSocket socket( fd ); // fd is coming from somewhere else; see below...
socket.write( "So much wow!" );
socket.close();            // Close the connection after so much excitement

Enter C++11

Until now, C++11 did not play a large role. Since my pretty server API only reacts to new client connections (it cannot know whether a client actually sent something), I though it would be nice to have a user-configurable functor that is called asynchronously whenever that happens. Enter std::function and std::async. The user needs to specify a function for handling accepted connections:

server.onAccept( [&] ( std::unique_ptr<ClientSocket> socket )
{
  socket->write( "Your quote here, for only 9.99 USD!" ); 
  socket->close();
} );

The server then launches this function asynchronously whenever an accept() call returns:

auto clientSocket = std::unique_ptr<ClientSocket>( new ClientSocket( clientFileDescriptor ) );
auto result       = std::async( std::launch::async, _handleAccept, std::move( clientSocket ) );

I am using an std::unique_ptr because the server does not want to take ownership of the client file descriptor at this time. Maybe I am going to change this in the future.

So?

If this seems like a lot of hassle for doing something as simple as sending a random quote to a client, you are right. I cried hot tears of shame when I compared my 203 lines of code to Ann's 37, which even contained some commented code:

require 'socket'
require 'csv'

quotes_array_unparsed = CSV.read('goodreads_quotes_export.csv')
keys = quotes_array_unparsed.delete_at(0)

count = 0
quotes_array = []
while quotes_array.length < quotes_array_unparsed.length
  quotes_array_unparsed.each do |quote|
    quotes_array[count] = Hash[keys.zip quote]
    count += 1
  end
end

quotes_array.each do |hash|
  hash.each do |key, value|
    value.gsub!("<br/>", "\n")
  end
end

#def less_than_512
#  if @quote_body.bytesize < 512
#  qotd(@quote_body, @quote_author)
#  else 
#    less_than_512
#  end
#end

server = TCPServer.new 17

loop do
    Thread.start(server.accept) do |client|
        random_index = rand(quotes_array.length)
        @quote_body = quotes_array[random_index]["Quote"]
        @quote_author = quotes_array[random_index]["Author"]

        def qotd(quote, author)
            "#{quote}\n   - #{author}"
        end
        client.puts qotd(@quote_body, @quote_author) 
        client.close
  end
end

I am amazed! Of course, this comparison is slightly unfair because I had to write my own version of Ruby's TCPServer module. Still, this code is definitely more elegant than mine. To compensate for this, my implementation of the Quote of the Day protocol serves up random quotes from Ambrose Bierce's The Devil's Dictionary, which I hope will get me some pity points.

Where is the code?

Please find the code on its GitHub repository. I plan on doing at least an implementation of RFC 862, the Echo protocol as well, but this will require more changes to the client socket, namely the ability to read stuff as well.

The code is released under an MIT licence.

Posted late Sunday evening, July 19th, 2015 Tags:

I now also have keybase.io profile and feel slightly overwhelmed by the possibilities. So far, I managed to verify my website, my public key, and my GitHub identity. This way of keeping tabs about a person using a single identifier has certainly its merits, but supposing I were to use, say, Hacker News, it might not be in my interest to have that profile associated with my public persona.

Further reports about my experiences are almost certain to follow.

Posted late Tuesday evening, July 28th, 2015 Tags:

One of my most favourite books, Cryptonomicon by Neal Stephenson contains a scene in which one of the protagonists, Randy, is letting other people use a demo on his laptop in a business meeting. Since Randy does not know all the people in the meeting he figures it would be a good idea to clandestinely collect their mugshots by writing a program that captures all the different faces appearing in front of his webcam.

I always wanted to do this because it sounds like a very fun idea. I sometimes leave my laptop open and lock it when I am on a conference and know that I shall be shortly returning to my seat. It was always a pet peeve of mine to know whether other people were trying to unlock the laptop...

So, with the power of Python and OpenCV, years after reading Cryptonomicon for the first time, I finally wrote my own version of Randy's program. This turned out to be surprisingly easy. It is a testament to the versatility of Python (and its libraries of course) that this problem can be solved by a really short program.

You can download the script from its GitHub repository. Further improvements may include (in the hopes that I get around to do this):

  • Recognizing and categorizing different faces
  • Taking mugshots when certain actions happen (a git hook could trigger this, for example)
  • Investigating different methods of face recognition
  • Detecting basic emotions

The script is dead simple to use. Just call

./mugshot.py

or

./mugshot.py --hide

in case you do not want to get visual feedback about the recognized faces. mugshot.py uses a simple logic to not fill about its working directory with useless faces: If either the number of faces changes between two frames or more than 10 seconds passed between the current face detection and the previous one, a new mugshot will be taken. The mugshots are named using the current time, with a suffix in case multiple faces are detected.

Have fun!

Posted Friday evening, July 31st, 2015 Tags: