[C/C++]libwebsocketsを試してみる。その3。SSL通信を試す。

libwebsocketsのサンプルでSSL通信を試す。
まず、libwebsocketsのサンプルで試してみます。
SSL通信によりサンプルサーバーを動作させるにはconfigureにオプションを付加してMakefileを作ります。

$./congifure --enable-openssl
$ make clean
$ make && sudo make install

test-serverにあるlibwebsockets-test-serverを起動します。
SSL通信を有効にするにはパラメータ付きで起動させます。

$ ./libwebsockets-test-server -h
Usage: test-server [--port=<p>] [--ssl] [-d <log bitfield>]

起動

$ ./libwebsockets-test-server --ssl
lwsts[25447]: libwebsockets test server - (C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - licensed under LGPL2.1
lwsts[25447]: Initial logging level 7
lwsts[25447]: Library version: 1.2 
lwsts[25447]:  Started with daemon pid 0
lwsts[25447]:  static allocation: 5472 + (12 x 1024 fds) = 17760 bytes
lwsts[25447]:  canonical_hostname = ubuntu
lwsts[25447]:  Compiled with OpenSSL support
lwsts[25447]:  Using SSL mode
lwsts[25447]:  per-conn mem: 184 + 1328 headers + protocol rx buf
lwsts[25447]:  Listening on port 7681

ブラウザからアクセスしてみる。
httpsでアクセスします。サンプルの認証鍵を使用しているので、ブラウザが信頼されないサイト、とか、セキュリティ証明書に問題あるとかいわれますが、続行して接続してみます。
Img20130317011820
※ちなみに、IE10ではHTMLは表示されますが、WebSocketの部分は動作しませんでした。たぶんセキュリティかなぁ。

その2のチャットサーバーをSSL通信化。
その2で作成したチャットソフトをSSL通信化させてみます。SSLの認証関連が面倒なので、サンプルサーバー同様、Webサーバー経由でhtmlをロードさせてからhtmlの中でWebSocketを使ってみます。

今回の実行環境
Ubuntu 12.04 LTS
Apache/2.2.22
OpenSSL 1.0.1
Windows7(クライアント実行環境)

apache2+SSLの準備
apache2+SSLを設定し、そこで設定した認証鍵をWebSocketサーバーで使用したいと思います。
今回はテストなのでSSL通信に必要な設定はサンプル等を使用します。

apache2+SSLのデフォルト設定を使用。

$ sudo aptitude -y install apache2
$ sudo a2enmod ssl
$ sudo a2ensite default-ssl
$ sudo /etc/init.d/apache2 restart

default-sslが使用する鍵のパス
設定ファイル:/etc/apache2/sites-available/default-ssl

SSLCertificateFile    /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key

チャットフォームの設置
DocumentRoot(/var/www)にhtmlファイルを保存します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>WebSocket Sample</title>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>Chat</h1>
            </div>
        </header>
        <section id="main">
        URI <input id="uri" type="text" />
        <input type="button" value="Connect" onClick="connect()" /><br>
		SEND<input id="chatmessage" type="text" />
		<input type="button" value="Send" onClick="send()" />
		<hr />
		<div id="log"></div>
		<script type="text/javascript">
			var ws;
			
			function connect(){
				if ("WebSocket" in window) {
					ws = new WebSocket(document.querySelector("#uri").value);
				} else if ("MozWebSocket" in window) {
					ws = new MozWebSocket(document.querySelector("#uri").value);
				}
				
				ws.onmessage = function(event){
				    output(event.data);
				}
				
				ws.onopen = function(event){
				    output("WebSocket Open!");
				}

				ws.onclose = function(event){
				    output("WebSocket Close.");
				}				
			}	
			
			function send(){
			    var str = document.querySelector("#chatmessage");
			    ws.send(str.value);
			    str.value="";
			}

			function disconnect(){
				ws.close();
				ws = null;
			}
			
			function output(str) {
			    document.getElementById("log").innerHTML += str + "<hr />";
			}
			
		</script>
        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

サーバー側の改修は楽。
ソース

int main()
{
	int n = 0;
	struct libwebsocket_context *context;
	int opts = 0;
	char interface_name[128] = "";
	const char *iface = NULL;
	int syslog_options = LOG_PID | LOG_PERROR;
	unsigned int oldus = 0;
	struct lws_context_creation_info info;
	int debug_level = 7;
	
	memset(&info, 0, sizeof info);
	info.port = 7681;
	
	lws_set_log_level(debug_level, lwsl_emit_syslog);
	

	info.iface = iface;
	info.protocols = protocols;
    
//	info.ssl_cert_filepath = NULL;
//	info.ssl_private_key_filepath = NULL;
	
	info.ssl_cert_filepath = "/etc/ssl/certs/ssl-cert-snakeoil.pem";
	info.ssl_private_key_filepath = "/etc/ssl/private/ssl-cert-snakeoil.key";
    
	info.gid = -1;
	info.uid = -1;
	info.options = opts;
	
	signal(SIGINT, sighandler);
	
	setlogmask(LOG_UPTO (LOG_DEBUG));
	openlog("lwsts", syslog_options, LOG_DAEMON);
	
    lwsl_notice("libwebsockets chat server -\n");
	
    context = libwebsocket_create_context(&info);
	if (context == NULL) {
		lwsl_err("libwebsocket init failed\n");
		return -1;
	}
	
	n = 0;
	while (n >= 0 && !force_exit) {
		struct timeval tv;

		gettimeofday(&tv, NULL);

 		n = libwebsocket_service(context, 50);
	}

	libwebsocket_context_destroy(context);

	lwsl_notice("libwebsockets-test-server exited cleanly\n");

	return 0;
}

SSL通信を有効にするにはlws_context_creation_info構造体に認証鍵を指定するだけのようです。

	info.ssl_cert_filepath = "/etc/ssl/certs/ssl-cert-snakeoil.pem";
	info.ssl_private_key_filepath = "/etc/ssl/private/ssl-cert-snakeoil.key";

コンパイル。

$g++ cppserver.cpp -lwebsockets -o wsserver_ssl

起動してみる。尚、デフォルトではSSL通信用の認証鍵のパーミッションが無いのでsudoで実行します。

$ sudo ./wsserver_ssl
[sudo] password for user: 
lwsts[27128]: libwebsockets chat server -
lwsts[27128]: Initial logging level 7
lwsts[27128]: Library version: 1.2 
lwsts[27128]:  Started with daemon pid 0
lwsts[27128]:  static allocation: 5472 + (12 x 1024 fds) = 17760 bytes
lwsts[27128]:  canonical_hostname = ubuntu
lwsts[27128]:  Compiled with OpenSSL support
lwsts[27128]:  Using SSL mode
lwsts[27128]:  per-conn mem: 184 + 1328 headers + protocol rx buf
lwsts[27128]: LWS_CALLBACK_ADD_POLL_FD
lwsts[27128]:  Listening on port 7681
lwsts[27128]: LWS_CALLBACK_PROTOCOL_INIT

ブラウザでアクセスしてみる。
Img20130317000419

[C/C++]C++でMessagePackを試す。

バイナリデータの扱いが難しかった。
C++のMessagePackライブラリは自作クラスのシリアル化が可能ですので、あるクラスのシリアル化を考えます。

InterfaceClassの定義
    int cmd;     // 数値
    string guid; // 文字列
    string time; // 文字列
    char[] data; // バイト配列

変数dataにはバイナリ(1バイト型の配列)を保持させたいのですが、ちょっとよくわからなくて、C#のように簡単にシリアライズすることはなりませんでした。もうちょっと調べてみないとよくわかりませんー。

配列を作成しデータを作成。
以前、python版のMessagePackを使用した時のように、配列を作成した上で、配列内に様々なデータ型を挿入することにしました。

InterfarceClassのシリアライズ

    // 対象データ
    int cmd = 0x10;
    string guid = "f65a9f53-15dc-48f4-b0f2-eac3f96df93a";
    string time = "20130226192100";
    
    printf("pack   cmd[%d],guid[%s],time[%s]\n", cmd, guid.c_str(), time.c_str());
    
    vector<char> rData;
    ReadAllBytes(IMAGEFILE_A, rData);
    
    
    msgpack::sbuffer buffer;
    msgpack::packer<msgpack::sbuffer> pk(&buffer);
    
    pk.pack_array(4);           // データが4つ
    pk.pack(cmd);
    pk.pack(guid);
    pk.pack(time);
    pk.pack_raw(rData.size());  // バイナリを入れる処理
    pk.pack_raw_body(&rData[0], rData.size());

    WriteAllBytes(MSGPACKFILE, buffer.data(), buffer.size());

pk.pack_array(4)の処理がないと配列型として認識されません。これは重要で、クラスをシリアル化した結果と同じになります。ちなみに、配列はデータ型と認識されるので、最終的には1つのデータになります。

デシリアライズの処理。
シリアル化したデータから元のデータを取り出すのも手動でやってみます。MessagePackの実装部分のソースを調べないとわからない部分がたくさんあった。

InterfarceClassのデシリアライズ

    vector<char> packData;
    
    // MessagePackデータ読み取り
    ReadAllBytes(MSGPACKFILE, packData);
    
    msgpack::unpacked msg;
    msgpack::unpack(&msg, &packData[0], packData.size());
    
    msgpack::object obj = msg.get();
    
    // msgapck::objectの配列を取り出す
    msgpack::object_array obj_array = obj.via.array;
    
    // デシリアライズ対象データ
    int cmd;
    string guid, time;
    
    // それぞれの型に変換して取り出す
    (obj_array.ptr[0]).convert(&cmd);
    (obj_array.ptr[1]).convert(&guid);
    (obj_array.ptr[2]).convert(&time);
   
    printf("unpack cmd[%d],guid[%s],time[%s]\n", cmd, guid.c_str(), time.c_str());
    
    // バイナリの取り出し
    msgpack::object_raw obj_raw = (obj_array.ptr[3]).via.raw;
    
    // バイナリの書き出し
    WriteAllBytes(IMAGEFILE_B, obj_raw.ptr, obj_raw.size);

正直、やりかた合っているかわからないですが、C#で同様の処理を作成し、C++版でシリアル化したデータが無事デシリアライズできたので、たぶん大丈夫だと思います。
ちょっと残念なのは、例えばコマンドによって専用のpack,unpackの処理をコーディングしないといけないところでしょうか。ちゃんとやり方があるんだろうなぁ。。。

C#での例。
以前作成したObject2MessagePack()の拡張メソッドを使うと以下のようにかけます。

    public class MyInterface
    {
        public int _0CMD { get; set; }
        public string _1GUID { get; set; }
        public string _2TIME { get; set; }
        public byte[] _3DATA { get; set; }
    }
    static void Main(string[] args)
    {
        var m = new MyInterface();
           
        m._0CMD = 0x10;
        m._1GUID = "f65a9f53-15dc-48f4-b0f2-eac3f96df93a";
        m._2TIME = "20130226192100";
        m._3DATA = new byte[] { 1, 2, 3 };
     
        System.IO.File.WriteAllBytes("csharp_MsgPack.bin", m.Object2MessagePack());
    }

C++で同じデータ作成します。

void pack2()
{
    // 対象データ
    int cmd = 0x10;
    string guid = "f65a9f53-15dc-48f4-b0f2-eac3f96df93a";
    string time = "20130226192100";
    
    printf("pack   cmd[%d],guid[%s],time[%s]\n", cmd, guid.c_str(), time.c_str());
    
    char data[] = {1,2,3};
    
    msgpack::sbuffer buffer;
    msgpack::packer<msgpack::sbuffer> pk(&buffer);
    
    pk.pack_array(4);           // データが4つ
    pk.pack(cmd);
    pk.pack(guid);
    pk.pack(time);
    pk.pack_raw(sizeof(data));  // バイナリを入れる処理
    pk.pack_raw_body(&data[0], sizeof(data));
    
    printf("size =%d\n", sizeof(data));
    
    WriteAllBytes("cpp_MsgPack.bin", buffer.data(), buffer.size());
}

シリアライズしたデータをダンプしてみます。
Img20130228132404

テストソース
InterfaceClassに合うようにMessagePackデータを作成し、pack,unpackを試してみました。
バイト配列には画像データを読み込み、処理終了後、元画像ファイルとデシリアライズ後出力した画像ファイルのmd5ハッシュ値を表示させ同じデータであるかを確認します。

テストソース(msgpack_main.cpp)

#include <msgpack.hpp>
#include <vector>
#include <string>
#include <iostream>
#include <fstream>

using namespace std;

// ファイル読み込み
int ReadAllBytes(const string& path,vector<char>& buf)
{
	ifstream fin(path.c_str(), ios::in | ios::binary );
	
	if(!fin)
	{
		return 1;
	}
	
	char c;
	for(;;)
	{
		fin.read(&c,sizeof(char));
		
		if(!fin.eof()) buf.push_back(c);
		else break;
	}
	fin.close();
	
	
	return 0;
}

// ファイル書き込み
int WriteAllBytes(const string& path,const  char* buf, int size)
{
	ofstream ofs(path.c_str(), ios::out | ios::binary | ios::trunc );
	
	if(!ofs)
	{
		return 1;
	}
	
    ofs.write(buf,size);
	ofs.close();
	
	
	return 0;
}

// md5の表示
void DispMd5(const string& fileName)
{
    FILE *pipe;
    
    char buf[1024]={0};
    string cmd = "md5sum " + fileName;
    
    pipe = popen(cmd.c_str(), "r");
    if( pipe )
    {
        fread(buf, sizeof(char), sizeof(buf), pipe);
        pclose(pipe);
    }
    
    printf("MD5> %s",buf);
}



// MessagePackデータ
const char* MSGPACKFILE = "MsgPack.bin";
const char* IMAGEFILE_A = "icon.png";
const char* IMAGEFILE_B = "_icon.png";

// シリアライズ
void pack()
{
    // 対象データ
    int cmd = 0x10;
    string guid = "f65a9f53-15dc-48f4-b0f2-eac3f96df93a";
    string time = "20130226192100";
    
    printf("pack   cmd[%d],guid[%s],time[%s]\n", cmd, guid.c_str(), time.c_str());
    
    vector<char> rData;
    ReadAllBytes(IMAGEFILE_A, rData);
    
    
    msgpack::sbuffer buffer;
    msgpack::packer<msgpack::sbuffer> pk(&buffer);
    
    pk.pack_array(4);           // データが4つ
    pk.pack(cmd);
    pk.pack(guid);
    pk.pack(time);
    pk.pack_raw(rData.size());  // バイナリを入れる処理
    pk.pack_raw_body(&rData[0], rData.size());
    
    WriteAllBytes(MSGPACKFILE, buffer.data(), buffer.size());
}

// デシリアライズ
void unpack()
{
    vector<char> packData;
    
    // MessagePackデータ読み取り
    ReadAllBytes(MSGPACKFILE, packData);
    
    msgpack::unpacked msg;
    msgpack::unpack(&msg, &packData[0], packData.size());
    
    msgpack::object obj = msg.get();
    
    // msgapck::objectの配列を取り出す
    msgpack::object_array obj_array = obj.via.array;
    
    // デシリアライズ対象データ
    int cmd;
    string guid, time;
    
    // それぞれの型に変換して取り出す
    (obj_array.ptr[0]).convert(&cmd);
    (obj_array.ptr[1]).convert(&guid);
    (obj_array.ptr[2]).convert(&time);
   
    printf("unpack cmd[%d],guid[%s],time[%s]\n", cmd, guid.c_str(), time.c_str());
    
    // バイナリの取り出し
    msgpack::object_raw obj_raw = (obj_array.ptr[3]).via.raw;
    
    // バイナリの書き出し
    WriteAllBytes(IMAGEFILE_B, obj_raw.ptr, obj_raw.size);
}



// interfaces
    //  int cmd
    //  string guid
    //  string time
    //  char[] data
int main()
{
    pack();
    unpack();
    
    printf("\n");
    
    DispMd5(IMAGEFILE_A);
    DispMd5(IMAGEFILE_B);
    
    return 0;
}

実行結果

$ ./msgpack_main
pack   cmd[16],guid[f65a9f53-15dc-48f4-b0f2-eac3f96df93a],time[20130226192100]
unpack cmd[16],guid[f65a9f53-15dc-48f4-b0f2-eac3f96df93a],time[20130226192100]

MD5> 5b2eb4f3de00f5d7477faa850514f641  icon.png
MD5> 5b2eb4f3de00f5d7477faa850514f641  _icon.png

[C/C++]libwebsocketsを試してみる。その2。

libwebsocketsその2。

C実装WebSocketsであるlibwebsockets続き。

libwebsocketsを解凍してできるtest-serverフォルダのtest-sever.cを参考に、チャットサーバーを作成する。
尚、C++でコーディングする。

・実行環境
Ubuntu 12.04 LTS
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
libwebsockets 1.2

サンプルサーバーの解析
test-server.cは3つのサーバー機能を有している。WebSocketsはサブプロトコルという機構を用意していて、これを指定することで同じ待ち受けポートで共有して使用することができる。

test-server.cのプロトコル定義

static struct libwebsocket_protocols protocols[] = {
	/* first protocol must always be HTTP handler */

	{
		"http-only",		/* name */
		callback_http,		/* callback */
		0,			/* per_session_data_size */
		0,			/* max frame size / rx buffer */
	},
	{
		"dumb-increment-protocol",
		callback_dumb_increment,
		sizeof(struct per_session_data__dumb_increment),
		10,
	},
	{
		"lws-mirror-protocol",
		callback_lws_mirror,
		sizeof(struct per_session_data__lws_mirror),
		128,
	},
	{ NULL, NULL, 0, 0 } /* terminator */
};

仕組みとしては、libwebsocket_protocolsの1つ目のパラメータにプロトコルとなる文字列を指定し、それを指定してアクセスしてきたクライアントに2つ目のパラメータのコールバック関数が実行される仕組み。

test-server(libwebsockets-test-server)はhttpでアクセスし、まず、htmlファイルを返し、その中に記述されているWebSocketsが動き出す仕組み。
html内には”dumb-increment-protocol”と”lws-mirror-protocol”の2つのプロトコルあり、”dumb-increment-protocol”は接続されたクライアントに対し、サーバーが一方的にインクリメントされた数値を返すというもの。”dumb-increment-protocol”はサーバーとクライアントが1対1に動作する。”lws-mirror-protocol”はhtml内のキャンバスエリアにユーザーが描写した線を接続された全てのクライアントに送信するというもの。”lws-mirror-protocol”は1対nの動作になる。

チャットサーバーは”lws-mirror-protocol”を参考に、ユーザーから受け取った文字列を接続中の全てのユーザーに送信する処理を行う。

mainの処理
test-server.cから不必要なものを取り除く。

main()

int main()
{
	int n = 0;
	int use_ssl = 0;
	struct libwebsocket_context *context;
	int opts = 0;
	char interface_name[128] = "";
	const char *iface = NULL;
	int syslog_options = LOG_PID | LOG_PERROR;
	unsigned int oldus = 0;
	struct lws_context_creation_info info;
	int debug_level = 7;
	
	memset(&info, 0, sizeof info);
	info.port = 7681;
	
	lws_set_log_level(debug_level, lwsl_emit_syslog);
	lwsl_notice("libwebsockets chat server -\n");

	info.iface = iface;
	info.protocols = protocols;
	info.ssl_cert_filepath = NULL;
	info.ssl_private_key_filepath = NULL;
	
	info.gid = -1;
	info.uid = -1;
	info.options = opts;
	
	signal(SIGINT, sighandler);
	
	setlogmask(LOG_UPTO (LOG_DEBUG));
	openlog("lwsts", syslog_options, LOG_DAEMON);
	
	context = libwebsocket_create_context(&info);
	if (context == NULL) {
		lwsl_err("libwebsocket init failed\n");
		return -1;
	}
	
	n = 0;
	while (n >= 0 && !force_exit) {
		struct timeval tv;

		gettimeofday(&tv, NULL);

 		n = libwebsocket_service(context, 50);
	}

	libwebsocket_context_destroy(context);

	lwsl_notice("libwebsockets-test-server exited cleanly\n");

	return 0;
}

プロトコル定義

static struct libwebsocket_protocols protocols[] = {
	{
		"chat",		/* name */
		callback_chat,		/* callback */
		0,			/* per_session_data_size */
		128			/* max frame size / rx buffer */
	},
	{ NULL, NULL, 0, 0 } /* terminator */
};

グローバル変数とコールバック関数(callback_chat)

static string _sendStr = "";

static int callback_chat(struct libwebsocket_context *context,
		struct libwebsocket *wsi,
		enum libwebsocket_callback_reasons reason, void *user,
							   void *in, size_t len)
{
	lwsl_notice("%s\n", reason_strings[reason]);
	
	switch (reason) 
	{
		// 新規接続
		case LWS_CALLBACK_ESTABLISHED:
			{
			}
			break;
		// クローズ
		case LWS_CALLBACK_PROTOCOL_DESTROY:
			{
			}
			break;
		// 送信処理
		case LWS_CALLBACK_SERVER_WRITEABLE:
			{
				libwebsocket_write(wsi, (unsigned char*)_sendStr.c_str(), _sendStr.length(), LWS_WRITE_TEXT);
			}
			break;
		// 受信処理
		case LWS_CALLBACK_RECEIVE:
			{
				lwsl_notice("ReceiveMessage=[%s]\n",(const char*)in);
				_sendStr = (const char*)in;
				libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol(wsi));
			}
			break;
		// えーと、えーと、
		case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
			{
				dump_handshake_info(wsi);
			}
			break;
		
		default:
			{
			}
			break;
	}

	return 0;
}

main()ではlibwebsocket_context型のインスタンスを作成し、libwebsocket_create_context関数でContextを作成し、libwebsocket_service関数を実行するというものである。今回定周期処理はないのでループ内で処理する動作はないのだが、よくわからんので、test-serverと同じようにした。

コールバック関数はライブラリ側が勝手にイベントを発行してくれるので、それに応じた処理を書けば良い。パラメータuserは1セッションでユーザーが管理できるデータで、パラメータinがクライアントからのデータ。
チャットサーバーではLWS_CALLBACK_RECEIVEイベント発生時、libwebsocket_callback_on_writable_all_protocol関数で接続中の全クライアントに送信処理を行う。送信処理はLWS_CALLBACK_SERVER_WRITEABLEイベントになるのだが、LWS_CALLBACK_SERVER_WRITEABLEイベント発生時、パラメータinはnullになってしまう。そのため、受信イベント(LWS_CALLBACK_RECEIVE)時にグローバル変数(_sendStr)に保持し、送信時のlibwebsocket_write関数にグローバル変数を指定している。サンプルソース(test-server.c)ではリングバッファによって管理していたので、C++ではSTLのqueueあたりで管理するのが良いのだろう。

新規接続のセッションを保持して管理する方法も可。
std::vectorを使用してLWS_CALLBACK_ESTABLISHEDイベントでセッションを保持すれば受信イベント時に送信してしまうことも可能。セッション管理を以前作成したサーバーと同じようにユニークに管理出来れば、特定のユーザーにのみ送信する処理が書けそう。

vectorで管理

static vector<libwebsocket*> _ws;
static int callback_chat(struct libwebsocket_context *context,
		struct libwebsocket *wsi,
		enum libwebsocket_callback_reasons reason, void *user,
							   void *in, size_t len)
{
	lwsl_notice("%s\n", reason_strings[reason]);
	
	switch (reason) 
	{
		case LWS_CALLBACK_ESTABLISHED:
			{
				_ws.push_back(wsi);
			}
			break;
		case LWS_CALLBACK_RECEIVE:
			{
				lwsl_notice("ReceiveMessage=[%s]\n",(const char*)in);
				_sendStr = (const char*)in;
				//libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol(wsi));
				
				for(int i=0;i<_ws.size();i++)
				{
					libwebsocket_write(_ws[i], (unsigned char*)_sendStr.c_str(), _sendStr.length(), LWS_WRITE_TEXT);
				}
				
			}
			break;
        /// (略)

コンパイル
libwebsocketsインストール済み環境でg++でコンパイル。

$ g++ cppserver.cpp  -lwebsockets -o cppserver

サーバーの実行
libtoolとかやっていないので、LD_LIBRARY_PATHを通してから実行。

$export LD_LIBRARY_PATH="/usr/local/lib"
$ ./cppserver
lwsts[18632]: Initial logging level 7
lwsts[18632]: Library version: 1.2
lwsts[18632]:  Started with daemon pid 0
lwsts[18632]:  static allocation: 5460 + (12 x 1024 fds) = 17748 bytes
lwsts[18632]:  canonical_hostname = ubuntu
lwsts[18632]:  Compiled without SSL support
lwsts[18632]:  per-conn mem: 172 + 1328 headers + protocol rx buf
lwsts[18632]: LWS_CALLBACK_ADD_POLL_FD
lwsts[18632]:  Listening on port 7681
lwsts[18632]: LWS_CALLBACK_PROTOCOL_INIT

クライアント
今回はC#(WebSocket4Net)で接続テストを実施。
WebSocket4Netでソケットを接続するときにサブプロトコルを指定する。今回はプロトコルに単に”chat”と文字列を指定するだけ。

var ws = new WebSocket("ws://192.168.1.22:7681","chat");

実行画面

今回作成したソースはこちら

[C/C++,Linux]sysinfo()で取得できるメモリ量についてメモ。

メモリの枚数に気をつける。
sysinfo()で取得できるメモリ量(totalramなど)は1枚あたりの量になるらしい。全体を求めるにはmem_unit分計算する必要がある。

C – sysinfo() returning bad values i686
http://stackoverflow.com/questions/4229415/c-sysinfo-returning-bad-values-i686

sysinfo(2) – Linux man page
http://linux.die.net/man/2/sysinfo

#include <stdio.h>
#include <linux/kernel.h>
#include <sys/sysinfo.h>

int main()
{
	struct sysinfo info;

	unsigned long totalram = 0;
	unsigned long freeram = 0;
	
	if( sysinfo(&info) != 0 )
	{
		return -1;
	}

	totalram = (info.totalram * info.mem_unit) / 1024;
	freeram = (info.freeram * info.mem_unit ) / 1024;
	
	printf("{\"val\":{\"totalram\":%ld,\"freeram\":%ld,\"useram\":%ld},\"err\":\"\"}",
		totalram,
		freeram,
		totalram - freeram);

    return 0;
	
}

VMのLinuxでテストしてから本物で動かすまで気づかなかった。これ、メモリ容量違うのが同時に刺さっていたらどうなるのだろうか。。。

[C/C++]libwebsocketsを試してみる。その1。

C言語実装のWebSocketsライブラリ libwebsocketsを試す。
導入メモ。

(更新)2013/2/21での最新版に更新しました。

・実行環境
Ubuntu 12.04 LTS
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
autoconf (GNU Autoconf) 2.68
テストブラウザ Google Chrome v24 for win

・libwebsocketsのダウンロード
http://git.warmcat.com/cgi-bin/cgit/libwebsockets/

現時点での最新版Downloadの libwebsockets-master.tar.gz をダウンロードし、解凍する。

・autogen.shの実行

$./autogen.sh

・configureの実行

$ ./configure

・make & make installの実行

$ make
$ sudo make install

・テスト
test-serverというフォルダがあるので、この中のサーバーを実行します。

$ ./libwebsockets-test-server
lwsts[7584]: libwebsockets test server - (C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - licensed under LGPL2.1
lwsts[7584]: Initial logging level 7
lwsts[7584]: Library version: 1.2 
lwsts[7584]:  Started with daemon pid 0
lwsts[7584]:  static allocation: 5460 + (12 x 1024 fds) = 17748 bytes
lwsts[7584]:  canonical_hostname = ubuntu
lwsts[7584]:  Compiled without SSL support
lwsts[7584]:  per-conn mem: 172 + 1328 headers + protocol rx buf
lwsts[7584]:  Listening on port 7681

ブラウザ(Chrome)でアクセスしてみます。
この環境の場合、http://192.168.1.22:7681 のようにhttpでアクセスします。
ウィンドウを2つ開き動作を確認します。下部のキャンバスエリアをなぞると、他のウィンドウと同期することがわかります。
Img20130221213716