Sunday, May 22, 2011

Creating Non-Blocking Client and Server with Java NIO

With Java NIO, creating non-blocking client and server is pretty straightforward. Below is a sample code how to do that.

HelloServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

public class HelloServer {
    private static final String HELLO_REPLY = "Hello World!";
    
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.wrap(HELLO_REPLY.getBytes());
        ServerSocketChannel ssc = null;
        try {
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(8888));
            ssc.configureBlocking(false);
            
            while (true) {
                SocketChannel sc = ssc.accept();
                // if sc == null, that means there is no connection yet
                // do something else
                if (sc == null) {
                    // pretend to do something useful here
                    System.out.println("Doing something useful....");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else { // received an incoming connection
                    System.out.println("Received an incoming connection from " +
                        sc.socket().getRemoteSocketAddress());
                    printRequest(sc);
                    buffer.rewind();
                    sc.write(buffer);
                    sc.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ssc != null) {
                try {
                    ssc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private static void printRequest(SocketChannel sc) throws IOException {
        ReadableByteChannel rbc = Channels.newChannel(
            sc.socket().getInputStream());
        WritableByteChannel wbc = Channels.newChannel(System.out);
        ByteBuffer b = ByteBuffer.allocate(8); // read 8 bytes 
        while (rbc.read(b) != -1) {
            b.flip();
            while (b.hasRemaining()) {
                wbc.write(b);
            }
            b.clear();
        }
    }
}

HelloClient.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class HelloClient {
    public static final String HELLO_REQUEST = "Hello!";
    public static void main(String[] args) {
        SocketChannel sc = null;
        try {
            sc = SocketChannel.open();
            sc.configureBlocking(false);
            // make sure to call sc.connect() or else 
            // calling sc.finishConnect() will throw 
            // java.nio.channels.NoConnectionPendingException
            sc.connect(new InetSocketAddress(8888));
            // if the socket has connected, sc.finishConnect() should 
            // return false
            while (!sc.finishConnect()) {
                // pretend to do something useful here
                System.out.println("Doing something useful...");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Sending a request to HelloServer");
            ByteBuffer buffer = ByteBuffer.wrap(HELLO_REQUEST.getBytes());
            sc.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sc != null) {
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

YouTube Downloader

I've created an open source GUI front-end of the famous youtube-dl script.

youtube-dl-gui

Wednesday, May 4, 2011

Getting Started with SCons to Build C/C++ Applications

SCons is a wonderful cross-platform build tool, mostly used for building C/C++ applications similar to make. It's based on Python and Python is an excellent programming language. Since Python is a fully-fledged programming language, there are many things that can be done easily in SCons as opposed to writing a shell script. In this blog, I'm going to show some basic stuff to get started with SCons.

SCons requires a file normally called SConstruct. The file structure in the below example looks like this.
scons-project
  |--- src/Hello.cpp
  |        Main.cpp
  |--- include/Hello.h
  |--- SConstruct

Hello.h
#ifndef HELLO_H_
#define HELLO_H_

class Hello {
public:
    Hello();
    virtual ~Hello();
    void sayHello() const;
};

#endif /* HELLO_H_ */

Hello.cpp
#include "Hello.h"
#include <iostream>
using namespace std;

Hello::Hello() {
}

Hello::~Hello() {
}

void Hello::sayHello() const {
    cout << "Hello!" << endl;
}

Main.cpp
#include "Hello.h"
#include <iostream>
using namespace std;

int main() {
    Hello hello;
    hello.sayHello();

#ifdef DEBUG
    cout << "Debugging..." << endl;
#endif
}

This is the simplest SConstruct file.
SConstruct
import os

env = Environment(CCFLAGS="-g -O0 -DDEBUG", CPPPATH="./include")

env.Program("bin/Main", ["src/Hello.cpp", "src/Main.cpp"])
This will produce an executable called Main. The CPPPATH here is to tell where the header files are located. And the CCFLAGS are the compiler flags used by the GCC compiler that tells the compiler to add the debug statement and turn off the optimization.

If we need to control how the object files are created, e.g. we want to put the object files in a directory called obj. We can use the Object node in our SConstruct. The SConstruct will look like this.
import os

env = Environment(CCFLAGS="-g -O0 -DDEBUG", CPPPATH="./include")

hello_obj = env.Object("obj/Hello", "src/Hello.cpp")
main_obj = env.Object("obj/Main", "src/Main.cpp")
env.Program("bin/Main", main_obj + hello_obj, LIBPATH="lib", LIBS="hello")

If we need to build a static library, i.e. libhello.a that can be linked against Main, we can modify the SConstruct to look this.
import os

env = Environment(CCFLAGS="-g -O0 -DDEBUG", CPPPATH="./include")

env.StaticLibrary("lib/hello", ["src/Hello.cpp"])
env.Program("bin/Main", ["src/Main.cpp"], LIBPATH="lib", LIBS="hello")

To build a shared library, i.e. libhello.so, we just need to change from StaticLibrary to SharedLibrary. The SConstruct will look like this.
import os

env = Environment(CCFLAGS="-g -O0 -DDEBUG", CPPPATH="./include")

env.SharedLibrary("lib/hello", ["src/Hello.cpp"])
env.Program("bin/Main", ["src/Main.cpp"], LIBPATH="lib", LIBS="hello")