自社の製品でUDPを使ってRequest/Responseのデータを返す機器を採用することになったのだが、RequestのSourcePortにResponseを返す作りになっていたので、その時の実装をメモ。
こことここをベースにサンプルを作成。エラー処理も同様に割愛。
要は送受信のsocketを共用にして、bindする前にPortを指定しなければいい話。
サンプルコードは送信は1回だけ。★が今回書きたかったところ。
世の中、SNMP AgentをC言語で実装する記事はあるのだが、SNMPマネージャをC言語で書く記事がないなぁと思って、C言語からコマンドラインのsnmpgetをpopenで呼んだりしてたのだが、オーバーヘッドの大きさに閉口。
本格的に探したところ、サンプルが公式にあったorz
公式のサンプルからさらに最低限必要な処理を抽出する。
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <string.h>
int main(int argc, char ** argv)
{
struct snmp_session session;
struct snmp_session *sess_handle;
struct snmp_pdu *pdu;
struct snmp_pdu *response;
struct variable_list *vars;
oid id_oid[MAX_OID_LEN];
oid serial_oid[MAX_OID_LEN];
size_t id_len = MAX_OID_LEN;
size_t serial_len = MAX_OID_LEN;
int status;
/*********************/
init_snmp("SNMP Test");
snmp_sess_init( &session );
session.version = SNMP_VERSION_1;
session.community = "public";
session.community_len = strlen(session.community);
session.peername = "127.0.0.1";
sess_handle = snmp_open(&session);
pdu = snmp_pdu_create(SNMP_MSG_GET);
read_objid(".1.3.6.1.2.1.1.1.0", id_oid, &id_len);
snmp_add_null_var(pdu, id_oid, id_len);
status = snmp_synch_response(sess_handle, pdu, &response);
for(vars = response->variables; vars; vars = vars->next_variable)
print_value(vars->name, vars->name_length, vars);
snmp_free_pdu(response);
snmp_close(sess_handle);
return (0);
}
このサンプル見てておもったのだが、snmp_pdu_create()で返ってきたポインタって、開放してないんだよね。
requestとsess_handleは開放してるっぽいAPI呼んでるからいいんだけど…
いろいろグクっても開放しているところが見当たらないから、たぶんsnmp_synch_responseで開放してると思うんだけど、一応ソースコード確認しておくかな・・・
Flash8のswfでXMLクラスを用いたUTF-8のXMLファイルはBOM(0xEF, 0xBB, 0xBF)を付けないと読んでくれない。
WindowsではShiftJISを使ってXMLを出していたが、Linuxを使うにあたり、 UTF-8に移植する際に発覚。
こんな感じでBOMをつけるようにした。
static const char BOM[3] = {0xEF, 0xBB, 0xBF};
fstream strm("hoge.xml", std::ios::out);
strm.write(BOM, sizeof(BOM));
strm << "XMLデータ" << endl;
strm.close();
これと似たような問題だったのだが、strftimeを使用したSQLをデバッグのためSYSLOGで出力したところ、gccの-O0では何も問題なく出力されるのだが、-O1以上の最適化をかけるとセグメンテーション違反で落ちた。
環境は以下。
以下のようなコードで発生。
char *sql = sqlite3_mprintf("SELECT strftime(\'%%s\', \'now\');");
syslog(LOG_DEBUG, sql);
sqlite3_free(sql);
どうやら原因はsyslog関数の中でvfprintfを行っており、エスケープした%sを再評価して存在しない可変引数を参照して落ちてるらしい。
最適化させないと、うまいことメモリ空間かレジスタを初期化して変なところに飛ばないようにしてくれてるんだと思われ。詳細は追わず。
仕方が無いので、エスケープしなおすのも面倒だから、strftimeを使用したSQLはデバッグ出力しないようにした。
長めの定型文を出さなきゃならんコードを書く必要があるのだが、perlではパフォーマンス的に厳しいので、c++/stl環境でsprintfっぽい動きをしてくれるものを(自分で作るのはかったるいので)探していた。
すると、こちらで公開されているでないか。ありがとうございます。
バックアップがてらそのまま引用させていただく。
/**********************************************************************
CのsprintfをC++/STLで実現する
使い方
std::string str = cformat( "%d:%s", intvalue, charpointer);
試したコンパイル環境
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition.
GCC C++ 3.3.6 / glibc 2.3.4 / Vine Linux 4.2
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <vector>
#include <string>
#include <stdexcept>
#ifdef _WIN32
#define vsnprintf _vsnprintf
#endif
std::string cformat( char *format, ...)
{
int bufsize = 1024; // 適当なサイズ
std::vector<char> buff(bufsize);
va_list args;
// 適当なバッファサイズで先ずは、vsnprintfを試す。
// 出力がバッファサイズ以上の場合、VC++ .NET 2003の場合は -1、
// glibc2.1以降は、書き込みに必要なサイズを返す。
va_start(args, format);
int vssize = vsnprintf( &buff[0], bufsize, format, args);
va_end(args);
// vsnprintfが成功した場合終了する。
if ( vssize >= 0 && vssize < bufsize ) {
buff.resize(vssize);
return std::string( buff.begin(), buff.end() );
}
#ifdef _WIN32
// VC++ .NET 2003 書き込みに必要なサイズを取得する。
va_start(args, format);
vssize = _vscprintf( format, args);
va_end(args);
#endif
if ( vssize < 0 ) throw std::runtime_error(format);
// サイズを再割り当てし、再度試す
buff.resize(vssize + 1);
va_start(args, format);
vssize = vsnprintf( &buff[0], vssize + 1, format, args);
va_end(args);
if ( vssize < 0 ) throw std::runtime_error(format);
buff.resize(vssize);
return std::string( buff.begin(), buff.end() );
}
↑と、stringstreamを使って定型文を無事簡潔に出力することができた。
Debian LennyのパッケージとなっているSQLite 3.5.9でstrftime関数とsqlite3_mprintfの組み合わせでどうもバグを踏んだっぽい。
↓な感じのコードを書いたら、再現。
char str[] = "SELECT strftime('%%Y-%%m-%%d 00:00:00', '%u', 'unixepoch', 'localtime');"
char *sql = sqlite3_mprintf(str, 1272628349);
そしたらこんなふうにsqlには文字列が展開されてた。
select strftime('%Y-No such file or directory--1076202408 00:00:00', '1272628349', 'unixepoch', 'localtime');
どうも%%のエスケープがうまく解釈出来ないようなので、strftimeを使用しない方法で回避した。
最新のSQLiteで治っているかは未確認。時間があったら調べる。
SQLite3で大量(といっても数百件だが、組み込みでCPUが貧弱かつトランザクションが必要なため出来る限り処理を早くする必要がある)にSelect/Insert文を発行する可能性があるパターンが出てきてしまったので、高速化のためPreparedStatementを使うことにした。
ここを参考にさせて貰いつつ、本家のドキュメントを読むと↓のような手順になるようだ。
原文と一緒に意訳も載せたが、意訳意味ないな…
- Create the object using sqlite3_prepare_v2() or a related function.
sqlite3_prepare_v2を使ってsqlite3_stmtハンドルを作成する。- Bind values to host parameters using the sqlite3_bind_*() interfaces.
sqlite3_bind_*を使ってSQL内の「?」「?n」「:v」「@v」「$v」を実際に値に割り当てる。nは任意の数値、vは任意のラベル名- Run the SQL by calling sqlite3_step() one or more times.
sqlite3_stepを使ってSQLを実行する。Selectなどの場合は複数回回して一行づつレコードをもらう。- Reset the statement using sqlite3_reset() then go back to step 2. Do this zero or more times.
sqlite3_resetでstatementをリセットし、2から繰り返すことで、Bindingを変更してSQLを実行できる。- Destroy the object using sqlite3_finalize().
一通り終わったら sqlite3_finalizeを使って、1で作ったsqlite3_stmtハンドルを開放する。
sqlite3_stmtはsqlite3_freeで開放しないってところが注意点かな。
ここによると、
sqlite3_prepareとsqlite3_stepを使ってINSERT処理を最適化すると、最適化しない場合より2.2~2.3倍速くなる。
とのことなので、十分効果は見込める予想。
先日のコードが2時間でできた物だったので、制限時間の残り1時間を使ってリファクタリング。
yahooニュースとかについてたコメントで麻雀を知らない人が不利とかかかれてたりしたけど、そういう人は文章をきちんと読まず早とちりするとか、仕様から外れた実装するとか別の意味でプログラマとしての最低用件を満たせないような気がする。
ちゃんと↓が書かれてるんだけどねぇ。
麻雀を知らない人は、順子・刻子・アタマ・待ちといった用語の意味だけ調べてから解答に取りかかってください。これを調べる時間は計算外とします。
麻雀を知らない人は開始する時間が遅れるだけで、よけいな思考が増えるわけでは無いのだが・・・
あとハッシュタグ界隈ではチートイツ対応とか4枚のカラテン対応とか機能拡張してるみたいだけど、自分はスルーで。
これらの対応も隠れた要件と見ることもできるけど、麻雀を知らない人でもできるテストなんだし、この辺を3時間の制限時間内で実装する価値はなさそうという判断。
と、いったところで続きにコードをぺたり。
Tags: c/c++
あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定というものをやってみた。
丁度2時間くらいで完了。
出題の出力例を比べても問題なさそう。
あと1時間あれば、バッファオーバフローの危険があるところ、マジックナンバーとか変数名を直して、コメントとか整形して、アタマとメンツの修正を一個のロジックにまとめるようなリファクタリングを行えるかな?
考えたポイントは以下。
perlとかでやればコーディングの時間はもうちょっと縮められそう。
kuma@localhost ~ $ ./a 1112224588899 (99)(111)(222)(888)[45] kuma@localhost ~ $ ./a 1122335556799 : (55)(123)(123)(567)[99] (99)(123)(123)(567)[55] (99)(123)(123)(555)[67] kuma@localhost ~ $ ./a 1112223335559 (123)(123)(123)(555)[9] (111)(222)(333)(555)[9] kuma@localhost ~ $ ./a 1223344888999 (123)(234)(888)(999)[4] (234)(234)(888)(999)[1] (44)(123)(888)(999)[23] kuma@localhost ~ $ ./a 1112345678999 (234)(567)(111)(999)[8] (234)(678)(111)(999)[5] (345)(678)(111)(999)[2] (11)(123)(456)(789)[99] (11)(123)(456)(999)[78] (11)(123)(678)(999)[45] (11)(345)(678)(999)[12] (99)(123)(456)(789)[11] (99)(234)(567)(111)[89] (99)(234)(789)(111)[56] (99)(456)(789)(111)[23]
続きにソースをぺたり。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// アタマ待ち(タンキ)※ノベタンはタンキ待ち二つで表現
int wait_head[] = {1,2,3,4,5,6,7,8,9};//9
// シュンツ待ち(リャンメン、ペンチャン、カンチャン)
int wait_mem[] = {11, 12, 13, 22, 23, 24, 33, 34, 35, 44, 45, 46, 55, 56, 57, 66, 67, 68, 77, 78, 79, 88, 89, 99};//24
int master[9] = {0};
int head_pt[] = {11,22,33,44,55,66,77,88,99};//9
int mem_pt[] = {123,234,345,456,567,678,789, 111,222,333,444,555,666,777,888,999};//16
int match_vals(int target, int *vals)
{
int test[9];
memcpy(test, vals, sizeof(test));
while(target){
int tmp = (target % 10) -1;
target /= 10;
test[tmp]--;
if(test[tmp] < 0) return 0;
}
return 1;
}
void sub_vals(int target, int *vals)
{
while(target){
int tmp = (target % 10) -1;
target /= 10;
vals[tmp]--;
}
return;
}
int is_last(int *vals)
{
int tmp = 0;
int i;
for(i = 0; i < 9; i++){
tmp += vals[i];
}
if(tmp == 1 || tmp == 2) return 1;
else if(tmp > 2) return 0;
else return -1;
}
void match_head(char *base, int *vals, int lastmem)
{
int i;
char tmp[100];
if(is_last(vals)){
for(i = 0; i < 9; i++){
if(match_vals(wait_head[i], vals)){
printf("%s[%d]\n", base, wait_head[i]);
// 他にマッチしようがない
return;
}
}
} else {
for(i = lastmem; i < 16; i++){
if(match_vals(mem_pt[i], vals)){
int test[9];
char out[100];
strcpy(out, base);
memcpy(test, vals, sizeof(test));
sprintf(tmp, "(%d)", mem_pt[i]);
strcat(out, tmp);
sub_vals(mem_pt[i], test);
match_head(out, test, i);
}
}
}
return;
}
void match_mem(char *base, int *vals, int lastmem)
{
int i;
char tmp[100];
if(is_last(vals)){
for(i = 0; i < 24; i++){
if(match_vals(wait_mem[i], vals)){
printf("%s[%d]\n", base, wait_mem[i]);
// 他にマッチしようがない
return;
}
}
} else {
for(i = lastmem; i < 16; i++){
if(match_vals(mem_pt[i], vals)){
int test[9];
char out[100];
strcpy(out, base);
memcpy(test, vals, sizeof(test));
sprintf(tmp, "(%d)", mem_pt[i]);
strcat(out, tmp);
sub_vals(mem_pt[i], test);
match_mem(out, test, i);
}
}
}
return;
}
int getdata(void)
{
int i;
int tmp;
for(i = 0; i < 13; i++){
tmp = getchar();
if(tmp == EOF) return -1;
tmp = tmp - 0x30 - 1;
if(tmp < 0 || tmp > 8) return -1;
master[tmp]++;
}
return 0;
}
int main(int argc, char *argv[])
{
char out[100] = {0};
if(getdata() != 0) return -1;
// アタマ待ちのパターン
match_head(out, master, 0);
// シュンツ待ちのパターン
// 先にアタマを確定させる
int i;
for(i = 0; i < 9; i++)
{
if(match_vals(head_pt[i], master)){
int test[9];
sprintf(out, "(%d)", head_pt[i]);
memcpy(test, master, sizeof(test));
sub_vals(head_pt[i], test);
match_mem(out, test, 0);
}
}
return 0;
}
Tags: c/c++
昨日の続きになるのだが、Linux版の開発環境にしているDebian Lennyに付属しているEclipseは3.2でSubclipseのVersionが古くsvn:externalsがうまく動いてくれなかった。
Subclipseだけ新しいものにしようとしても、ライブラリが古いためうまくインストール出来ないと言われてしまった。
そこで仕方なくEclipse3.5をインストールしたら、ガンガン落ちて使い物にならなかったので、Eclipse3.4に切り替え。
日本語パッチを当てて動かそうとしたら、
The Eclipse executable launcher was unable to locate its companion shared library.
という、エラーが出て立ち上がらず。
ここを見て解決。
dropinsディレクトリ(デフォルトでは空)にcdして日本語パックをunzipで展開し、dropins/eclipse/pluginsおよびdropins/eclipse/featuresのディレクトリ構成になるように展開しました。
ありがとうございます。
んでもって、http://subclipse.tigris.org/update_1.6.x/から最新のSubclipseを持ってきてインストール。
そしたらまたトラブルorz
An error occured during provisioning.
Cannot connect to keystore.
JKS
↑なダイアログが出てきてインストールが止まる。
ここを見て解決。ありがとうございます。
JDK1.6が必要とのこと。オラクルからJDK1.6を落としてきて、バイナリを実行。
JDKの切り替えに↓のコマンドを実行する。
# update-alternatives –config java
インストールできたので、SVNにつなごうとしたら、↓のエラーが発生。
Failed to load JavaHL Library.
These are the errors that were encountered:
no libsvnjavahl-1 in java.library.path
no svnjavahl-1 in java.library.path
no svnjavahl in java.library.path
java.library.path = /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/client:/usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386::/usr/java/packages/lib/i386:/lib:/usr/lib
SVNの設定でインタフェースをJNIからSVNKitに変更にした。
そしたら、チェックアウトが何時まで経っても終わらねぇ。もうSubclipseは諦めて、Subversiveを利用することにする。
Subclipseをアンインストールし、↓からSubversiveをインストール。クライアントはSVNKitのみを選択。
ここを参考にさせてもらった。
http://download.eclipse.org/technology/subversive/0.7/update-site/
http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/
Eclipse3.2から3.4へのプロジェクトファイルのVersion更新もあるが、SVNからチェックアウトするタイミングでは行わない。これを行うとチェックアウトが終わらなくなる。
先にチェックアウトを完了させてから、プロジェクトファイルの変換を行う。一回固まっても、強制終了させ立ち上げなおすと変換が終わってるみたい。
昨日の件から含めて15時間くらいハマった。その内3割くらいは非力なマシンでVMWare上のEclipseが糞重いせいだがw 長かった・・・
Tags: c/c++, Eclipse, linux, Subclipse, svn:externals