C言語でStringBufferもどきを作成してみる

c言語は速いのはいいのですが、いかんせん標準ライブラリ群が貧弱な面は否めません。

文字列周りの処理だとC++のSTLあたりを使用すれば簡単ですが、純粋にCで簡単に作成してみました

  • string_buffer.h
#ifndef _STRING_BUFFER_H
#define _STRING_BUFFER_H
#include 

struct string_buffer {
	unsigned int item_size;
	char *item;
	unsigned int max_item_size; // 実際のメモリ確保量
};


extern struct string_buffer *create_string_buffer();
extern void delete_string_buffer(struct string_buffer *buf);
extern int append_string_buffer(struct string_buffer *buf, char *val, unsigned int val_len);


#endif
  • string_buffer.c
#include "string_buffer.h"
#define ITEM_SIZE 1024
struct string_buffer *create_string_buffer(){
				struct string_buffer *buf;
				buf=(struct string_buffer *)malloc(sizeof(struct string_buffer));
				if(buf==NULL)return NULL;
				buf->item=(char*)malloc(sizeof(char)*ITEM_SIZE);
				if(buf->item==NULL){
								free(buf);
								return NULL;
				}
				buf->item[0]='\0';
				buf->item_size=0;
				buf->max_item_size=ITEM_SIZE;
				return buf;
}
void delete_string_buffer(struct string_buffer *buf){
				if(buf!=NULL){
								if(buf->item!=NULL) free(buf->item);
								free(buf);
				}
}
int append_string_buffer(struct string_buffer *buf, char *val, unsigned int val_len){
				int size;
				char *newbuf;
				int diff=buf->max_item_size-buf->item_size;
				if(diff>val_len){
								strncat(buf->item,val,val_len);
								buf->item_size+=val_len;
				}else{
								size=buf->max_item_size+ITEM_SIZE;
								newbuf=(char*)malloc(sizeof(char)*size);
								if(newbuf==NULL) return -1;
								newbuf[0]='\0';
								strncpy(newbuf,buf->item,buf->item_size);
								free(buf->item);
								buf->item=newbuf;
								buf->max_item_size=size;
								return append_string_buffer(buf,val,val_len);
				}
				return 0;
}

実行してみます

  • main.c

static void print_string_buffer(char *header,struct string_buffer *buf){
				printf("%s:item_size=%d,char=%s,max_item_size=%d\n",header,buf->item_size,buf->item,buf->max_item_size);
}
int main(){
				struct string_buffer *buf;
				buf=create_string_buffer();
				if(buf==NULL)exit(0);

				append_string_buffer(buf,"test",sizeof("test"));
				print_string_buffer("1",buf);

				append_string_buffer(buf," 1111",sizeof(" 1111"));
				print_string_buffer("2",buf);

				append_string_buffer(buf,"",sizeof(""));
				print_string_buffer("3",buf);

				append_string_buffer(buf,"a",sizeof("a"));
				print_string_buffer("4",buf);

				append_string_buffer(buf,"12345678901234567890",12);
				print_string_buffer("5",buf);

				append_string_buffer(buf,"123",sizeof("123"));
				print_string_buffer("6",buf);
				delete_string_buffer(buf);
}
  • 実行結果
$ a.out
1:item_size=5,char=test,max_item_size=1024
2:item_size=11,char=test 1111,max_item_size=1024
3:item_size=12,char=test 1111,max_item_size=1024
4:item_size=14,char=test 1111a,max_item_size=1024
5:item_size=26,char=test 1111a123456789012,max_item_size=1024
6:item_size=30,char=test 1111a123456789012123,max_item_size=1024

StringからEnumの作成

文字列からEnum型へ変換するサンプルです

public class test{ 
 
	public static enum Channel{PC,WEB; 
		static Channel get(String s){ 
			Channel[] channels=Channel.values(); 
			for(Channel channel:channels){ 
				if(channel.toString().equals(s))return channel; 
			} 
			return null; 
		} 
}; 
 
	public static void main(String[] argv){ 
		System.out.println(Channel.WEB); // Enumをそのまま出力
		System.out.println(Channel.get("PC")); // 文字列からEnumを取得してみる
	} 
} 

Linuxでミリ秒まで取得

ansiのC言語で用意されているtime関数では現在時刻をミリ秒まで取得できません。そこでLinuxならではの取得方法の覚え書きです

gettimeofday関数を用いてミリ秒を取得します

以下のサンプルではミリ秒単位で現在時刻を取得し、2つの時間の差を求めています

#include
#include
#include
#include
#include

struct my_tm {
	time_t tim; // yyyymmddhhmmss
	long msec;	// milli sec
};

static struct my_tm *get_now_tm(){
	struct my_tm *qt;
	struct tm *tmp;
	struct timeval tv;

	qt=(struct my_tm*)malloc(sizeof(struct my_tm));
	if(qt == NULL)return NULL;
	gettimeofday(&tv,NULL);
	tmp=localtime(&tv.tv_sec);
	qt->tim=mktime(tmp);
	qt->msec=tv.tv_usec/1000;
	printf("%04d/%02d/%02d %02d:%02d:%02d:%3d\n",
		tmp->tm_year + 1900, tmp->tm_mon + 1,
		tmp->tm_mday, tmp->tm_hour,
		tmp->tm_min, tmp->tm_sec,
		tv.tv_usec/1000);
	return qt;
}

// return milli second
static inline long my_tm_cmptime(struct my_tm* now_t,struct my_tm* prev_t){
	long diff_sec;
	long diff_msec;
	diff_sec=difftime(now_t->tim,prev_t->tim);
	diff_msec=now_t->msec-prev_t->msec;
	return diff_sec*1000+diff_msec;
}

int main(){
	int i,b;
	struct my_tm *q1;
	struct my_tm *q2;
	long dif;

	q1=get_now_tm();
	sleep(2); // 2秒あける
	b=0;
	for(i=0;i<1000000;i++) b++; // +αあける
	q2=get_now_tm();
	dif=my_tm_cmptime(q2,q1);
	printf("%d\n",dif);

	free(q1);
	free(q2);
}

PlayFramework+Redis+websocketでプッシュ配信(データ圧縮編)

Websocketに最近はまっています。

前回プッシュ配信をRedisのPub/Subを用いて実現した訳ですが、データ量が増えてくるとどうしても配信するデータを圧縮したくなります。

特にAWSなんかだと、従量課金となっていますので、データ転送量は少なければ少ないほど○

Websocket自体はまだデータの圧縮はサポートされていないとのことですので、自前でデータの圧縮伸長を実装してみました。

また、それだけではつまらないので、Reidsにチャンネルを作成し、ReidsのチャンネルごとにPub/Subする仕組みを入れています

図解するとちょっとわかりにくいですがこんな感じ

			user1							userActor			 redisActor	 sub		Redis
		|code1,code2| ---	|	 user1	 | ---	|	code1	| --- |
											 |					 |			|				 |
			user2						|					 | ---	|	code2	| --- |
		|code2,code3| ---	|	 user2	 | ---	|	code3	| --- |

User別に作成したActorと、ユーザが選択したチャンネル(code1,code2,code3)別に作成したActorを別々に作成し、チャンネルがReidsから更新された際には、購読しているUserのActorへ更新をかけるという感じです

こうすることにより、チャンネル数がユーザ数より圧倒的に少ない場合には、Reidsに対するコネクションも削減でき、効率が良くなります。

  • route
GET		 /board													 controllers.Application.board(code:Option[String])	 # 画面作成用
GET		 /board/data											controllers.Application.data(code) #WSでデータ取得用
GET		 /asset/javascripts/board.js		 #Javascript controllers.Application.boardJs(code:String)
  • Compress.scala

データ圧縮用に作成します。String型をZIP圧縮し、BASE64でエンコードします

package models

import java.util.zip._
import java.io._
import org.apache.commons.codec.binary._

object Compress {
	def encode(str:String):String={
		val out = new ByteArrayOutputStream()
		val defl = new DeflaterOutputStream(out, new Deflater(Deflater.BEST_COMPRESSION, true))
		defl.write(str.getBytes())
		defl.close()
		val compressed = Base64.encodeBase64(out.toByteArray())
		new String(compressed)		
	}
}
  • UserActor.scala
...
	
	def notifyAll(code:String,data:String){
		// 実際にWebSocketでブラウザに送るデータ
		val msg=JsObject(
				Seq(
						"code"->JsString(code),
						"data"->JsString(Compress.encode(data)) // JSONの一部データ部分のみ圧縮
..
  • Javascriptで解凍

JavaScriptでZIP解凍、Base64でコードするために↓からinfrate.js,base64.js,utf.jsなどをダウンロードしておきます。

http://www.onicos.com/staff/iz/amuse/javascript/expert/

@(code:String)(implicit r: RequestHeader)

$(function() {

		var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
		var socket = new WS("@routes.Application.data(code).webSocketURL()")

		
		var receiveEvent = function(event) {
				var data = JSON.parse(event.data)

				var bs=base64decode(data.data) // Base64デコード
				var dec=zip_inflate(bs)				// ZIP解凍
				var p=JSON.parse(dec)
				$("#data").text(p.data);
			 
		}

		socket.onmessage = receiveEvent

})

サンプルの一式は以下においておきました

https://github.com/anagotan/play_websocket_compressed