いつも設定するphp.ini

<? 〜 ?>が使えるようになる
short_open_tag = On
エラーレベル。php5.3未対応のエラーは面倒だから、E_DEPRECATEDはのぞいてる
error_reporting = E_ALL & ~E_DEPRECATED	
エラーを表示
display_errors = On	
ライブラリの場所
include_path = ".:/php/includes"
携帯開発の場合。session idをget値にもちまわるようにしてくれる
session.use_trans_sid = 1
携帯開発の場合cookieが使えないことがあるからoffにする。

このあたりはhttp://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1210119827がよさそう。

session.use_only_cookies = 0
なんか文字コード関係。いろいろ変わる
output_handler = mb_output_handler

mbstring.language = Japanese
mbstring.internal_encoding = SJIS
mbstring.http_input = auto
mbstring.http_output = SJIS
mbstring.encoding_translation = Off
mbstring.detect_order = auto
mbstring.substitute_character = none;

共有メモリにはポインタは含められない。

perlからcに乗り換えて、cでリスト構造体を作成し、共有メモリへ配置。別のcから共有メモリにあるリスト構造体の頭から読む。というプログラムを組んだのですが、動きませんでした。

現象
struct item{
        int count;
        struct item *aft;
};

リストの仕組みです。bef、aftで前後の構造体と連結しています。書きこみ側はうまくいったのですが、問題は読み込み側です。item[0].bef->count のようにアクセスすると落ちてしまいます。

gdb) print &list[0]
$1 = (struct item *) 0x7ffff7fde000
(gdb) print list[0]
$2 = {cnt = 16, aft = 0x7ffff7f8f018}
(gdb) print &list[1]
$3 = (struct item*) 0x7ffff7fde018
(gdb) print list[1]
$4 = {cnt = 2, aft = 0x7ffff7f8f030}
(gdb) print &list[2]
$5 = (struct item*) 0x7ffff7fde030
(gdb) print list[3]
$6 = {cnt = 0, aft = 0x0}

どうやらアドレスが作った時とずれているみたいです。試しに書きこみ側を見てみると、

gdb) print &list[0]
$1 = (struct item *) 0x7ffff7fde000
(gdb) print list[0]
$2 = {cnt = 16, aft = 0x7ffff7f8f018}
(gdb) print &list[1]
$3 = (struct item*) 0x7ffff7f8f018
(gdb) print list[1]
$4 = {cnt = 2, aft = 0x7ffff7f8f030}
(gdb) print &list[2]
$5 = (struct item*) 0x7ffff7f8f030
(gdb) print list[3]
$6 = {cnt = 0, aft = 0x0}

とアドレスが一致しています。なんでだろう?共有メモリというくらいだから同じアドレスを見ているはずだ。ということはアドレスも同じじゃないの???

たぶんの原因

よくはまだ分かっていないのですが、プロセスの起動時に仮想メモリの管理表をプロセスが持ちます。この管理表を経由するおかげで、物理メモリ、ハードディスクなどの違いや、物理メモリ上の管理などを意識する必要がありません。

共有メモリはたしかに物理メモリの場所を共有はしていますが、このプロセスごとにもつ仮想メモリシステムの管理票上のアドレスはプロセスごとに変わります。だからアドレスがずれるように感じたのです。

対処

配列の添え字で管理するようにしました。

cとperlの共有メモリの使い方の違いについて

今回仕事でやり方ったことはperlのプロセスで大量テキストからリスト構造体を共有メモリ経由でcへデータ渡したいというものでした。perlもcもプロセス間通信もほとんどやったことがないので七転八倒しながら色々調査して、結局だめだという結論に至りました。

perlで共有メモリに書いたらアドレスずれない?

cの共有メモリの使い方

  1. shmgetで共有メモリIDを取得する
  2. shmatでアタッチして共有メモリのアドレスを取得する
  3. アドレスに対して操作

perlの共有メモリの使い方

  1. shmgetで共有メモリIDを取得する
  2. shmwrite/shmreadで共有メモリに対して読み込み書き込みを行う

以上のことから想像するにperlでもしリスト構造体を作ったとしてもshmwriteする瞬間にアドレスがずれて使い物にならなくなるのでは?と推測しました。

というかperlってアドレス使えなくない?

perlでのポインタ的なものを探しているとリファレンス型というものを見つけました。なんだアドレス型あるじゃんと思ったら、どうやらこれは変数の別名を実現するもので、実メモリアドレスを示すものではなかったようです。

というかperlとcって構造体の構造が違うのではないの?

これに関してはhttp://pub.ne.jp/wakapon/?entry_id=1244136が参考になりました。perlでcの構造体を作るにはpackでメモリ上にデータを並べてあげればOKのようです。ってかsemopするときにやってた

struct mystruct{
	int id;
	int cnt;
};

の型にid=2,cnt=4を入れたものを作りたい場合、

my $bin = pack ("ii", (2,4));

でOKです。

結論

大前提としてどうもperlはアドレスが取れないみたいです。あととれたとしても共有メモリに書くときにずれてしまいます。ということでperlは使えないという結論に至り、cでテキスト操作に変更になりました。

お蔵入りになったperlのソースをさらしてみます。

セマフォでロックして共有メモリを読み書きするソースです。

#! /bin/perl -w

#use IPC::SysV qw(IPC_CREAT IPC_EXCL SETVAL);
use IPC::SysV qw(IPC_CREAT IPC_EXCL SETVAL ftok);

####################
##  初期処理
####################

#keyを作る。ユニークなキーを作るにはこの関数を使うといいらしい。
#今回はweb系だからこのディレクトリと、110番にしてみたw。
$SIZE = 10000;
$KEY = ftok("/var/www/html/", 110);
print "key=$KEY\n";

#共有メモリの取得
$MEMID = shmget($KEY, $SIZE, IPC_CREAT | 0644);
unless (defined $MEMID) {
     print "MEMID error $!\n";
     exit(1);
}
print "MEMID=$MEMID\n";

#セマフォを取る。なければ作る。
$SEMID = semget($KEY, 1, 0644|&IPC_CREAT|IPC_EXCL);
if (defined($SEMID)) {
	print "SEMID = $SEMID\n";
	$RC = semctl($SEMID, 0, &SETVAL, 1);
	if (! defined($RC)) {
		print "SETVAL failure $!\n";
		exit(1);
	}
} else {
	print "semget create failed $!\n";
	$SEMID = semget($KEY, 1, 0644);
	if (defined($SEMID)) {
		print "Exsiting SEMID = $SEMID\n";
	} else {
		print "semget failed2 $!\n";
		exit(1);
	}
}
print "SEMID=$SEMID\n";


####################
##  ロック
####################
sub lockSmp{
   $OP = pack("sss", 0, -1, 0);
   semop($SEMID, $OP) || die "semop failre";
   print "Got lock\n";
}

####################
##  アンロック
####################
sub releaseSmp{
   $OP = pack("sss", 0, 1, 0);
   semop($SEMID, $OP) || die "semop failre";
   print "Lock released\n";
}

####################
##  共有メモリにかく
####################
sub writeShm{

	($data) = @_;	

	unless(shmwrite($MEMID, $data, 0, 9)) {
		&logwrt("shrwrite error $!");
		return;
	}

	return 1;
}

####################
##  共有メモリからよみこみ
####################
sub readShm{

	undef $data;
	unless(shmread($MEMID, $data, 0, 9)) {
		&logwrt("shrread error $!");
		return;
	}

	return $data;
}

####################
##  書きこみ
####################
sub datawrite{

	($data) = @_;

	#1 lock
	&lockSmp;

	#2 write
	&writeShm($data);

	#3 release
	&releaseSmp;
}

####################
##  読み込み
####################
sub dataread{

	#1 lock
	&lockSmp;

	#2 write for shared memory
	$data = &readShm;

	#3 release
	&releaseSmp;

	return $data
}


####################
##  main
####################

print "main start\n";

&datawrite("testetset");
$aa = &dataread;
print "data=$aa\n";

メールアドレスの長さ。

メールアドレスにhash(md5)の値をくっつけて処理をしていました。rfcによると@より前の長さは64文字までいいとのこと。hashは32桁で何の問題もないと思っていたらある携帯では届かないといわれました。

そこでいろいろ調べてみると、どうやら携帯キャリアでは@より前は30文字に制限を掛けているようです。(参考:メールアドレスの長さの限界は?)

地味に大変だ。確率はとても低いけれど文字を削ればhash値の価値はぐっと下がります。ということで短いhash値を作ってみることにしました。

md5で何も指定しない場合、16bitのバイナリが帰ってきます。それを16進にしてbase64化。24桁の文字列が得られます。しかしbase64は(/|+|=)が含まれるので利用目的によって適宜置換したほうがいいでしょう。メールアドレスに使う場合は/を別の#などの半角記号に置換しました。(参考:メールアドレスで使える文字)

<%
function md5_base64($v){
  return base64_encode(pack('H*',md5($v)));
}

echo md5_base64("test");
%>

CY9rzUYh03PK3k6DJie09g==

携帯キャリアはどれくらいメールアドレスのサーバの信頼性をチェックしているか

よく、「spfレコード書かないと携帯キャリアから蹴られる」という話を聞きます。その場合のドメインはxxx@foo.jpのfoo.jpのドメインだと思っていのですが、違いました。smtpコマンドでHELOのときに嘘のドメインも通るし、MAILコマンドで無茶苦茶なアドレスを指定しても平気で届きます。アドレスも嘘が良くて、送信サーバも嘘がいいなら、いったいどこのドメインspfみてるんだろう。

参考
SMTPコマンドを打ってみよう
telnetでメール送信
■□ SMTP セッション □■

テストでsmtpでつないでみました。testtesttest.jpは存在しません。

telnet mx.softbank.ne.jp. 25
Trying 123.108.236.168...
Connected to mx.softbank.ne.jp (123.108.236.168).
Escape character is '^]'.
220 tgms.softbank.ne.jp ESMTP server ready Thu, 9 Jun 2011 14:51:06 +0900
HELO testtesttest.jp
250 tgms.softbank.ne.jp
MAIL FROM: 
250 Sender  Ok
RCPT TO: <存在する@softbank.ne.jp>
250 Recipient <存在する@softbank.ne.jp> Ok
DATA
354 Ok Send data ending with .
From:test@testtesttest.jp
To:存在する@softbank.ne.jp
Subject:test

hello
.

URL文字列を空文字に置換

まぁメモ(毎回だな)

$text = preg_replace('/(http|https|ftp):\/\/[!-~]*/', '', $text);

空白以外半角英数字ってのが「!-~」で表現できるみたい。楽ちん。urlと続けて英数字ないはず。

参考(正規表現

node.jsでnet.Socket使うときのソースIPの指定

単純に来たものを別のサーバに打ち返すルータのようなものを作りたかった。大量のセッションが予想されてevent poolなnode.jsなら要件を満たしそうと思って調べてみることにした。

socket通信も当然できる。しかしnet.Socketの部分で問題が。ただ打ち返すだけじゃなく、ソースIPを色々と変更しながら打ち返したい。だけれどlocal addressを割り当てるところがない。

githubのnode/src/node_net.ccをみてみると、connectを呼んでいるみたい。connectシステムコールはip,portを自動割り当てしてくれる。でもIPを割り当てたいんだ。ということでsocket()された後にbind()を呼んでみることにする。

まだノーテスト。とりあえずメモとして乗っけておく。

var net = require("net");

/**
 *	createConnectionだけどローカルのIPが複数ある場合、socketにbindするローカルipを指定したい。
 *	の為に作った。まだ動かしてない。概念だけ。
 */
net.createConnectionBindLocal = function(port, host, localaddress) {

  var s = new Socket();

  //bindシステムコール使ってて、port=0は空きポートを自動で探すはず。
  this.bind(s.fd, 0, localaddress);

  /* 
    こうかくかもしれない。
    var binding = process.binding('net');
    binding.bind(s.fd, 0, localaddress);
  */
 
  s.connect(port, host);
  return s;
};

//作るぞー
socket = new net.createConnectionBindLocal(port, host, "192.168.1.1");

参考(コネクション型通信 サーバプログラムの作成