Ubuntu(AmazonEC2)でWebサーバを公開するときにハマった事象2

apacheのインストールを終えて、普通ならばブラウザから疎通できるはず・・・
と確認しても404エラーが発生してどうしようもない。


Not Found

The requested URL / was not found on this server.
Apache Server at ec2-xxxxxxxxxxxx.ap-northeast-1.compute.amazonaws.com Port 80



動かないものですね!
で、ログ。

> cat /var/log/apache2/error.log

[Sun Oct 27 02:18:31 2013] [error] [client xxx.xxx.xxx.xxx] File does not exist: /etc/apache2/htdocs
[Sun Oct 27 02:18:33 2013] [error] [client xxx.xxx.xxx.xxx] File does not exist: /etc/apache2/htdocs
[Sun Oct 27 02:20:44 2013] [error] [client xxx.xxx.xxx.xxx] File does not exist: /etc/apache2/htdocs
[Sun Oct 27 02:20:48 2013] [error] [client xxx.xxx.xxx.xxx] File does not exist: /etc/apache2/htdocs

ドキュメントルートにファイルが存在してないよって・・・・/etc/apache2/htdocsなんてルートに設定した覚えはないよ。

/etc/apache2/conf.d/defaultにはちゃんとしたドキュメントルートを設定してます。あれ。



これ、結局わからずじまい

Ubuntu(AmazonEC2)でWebサーバを公開するときにハマった事象

apacheのインストールを終えて、普通ならばブラウザから疎通できるはず・・・
と確認してもタイムアウトして何も表示されない

様々なページではポート開放していないんじゃないの? EC2ログイン画面左にあるSecurityGroupsで設定できるよ!やってみな!って書いてあるけど、それやってんだよねー。なんでかいなー

小一時間同じ事象で困ってる人いないかなーってネットで探してたけど、見当たらない。
しょうがないので自分で探そうとログをみたら、バッチリ原因でてた。

/var/log/ufw.log
Oct 26 16:51:28 XXXXXXXXXXXX kernel: [30834851.194851] [UFW BLOCK] IN=eth0 OUT= MAC=[アクセス元MACアドレス]:ff:ff:ff:ff:ff:08:00 SRC=[アクセス元IP] DST=[自分のローカルIP] LEN=64 TOS=0x00 PREC=0x00 TTL=53 ID=9913 PROTO=TCP SPT=60347 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0

UFWって、Ubuntuに標準で搭載してるファイアウォール。試しに

>ufw status

と打ってみたら、80番ポートがどこにもない。

>ufw allow 80

で送受信許可設定にしてみたら、疎通はできるようになったよ!(404エラーだけどさッ)

文章を横幅固定で表示するのサンプル

画面イメージは
http://d.hatena.ne.jp/skyjoker/20121226/1356490978
を御覧ください。

#import <UIKit/UIKit.h>

@interface myViewController : UIViewController{

}
@end


@implementation myViewController

-(void)viewDidLoad {

	//スクロールビューの作成
	UIScrollView* sv = [[UIScrollView alloc] initWithFrame:CGRectMake( 配置するX座標 , 配置するY座標 , 横幅 , 縦幅 )];


	//ラベルの作成
	UILabel* lv = [[UILabel alloc]initWithFrame:CGRectMake( 任意の値, 
                      任意の値,
                      任意の横幅,
                      任意の縦幅)];

	//ラベルに複数行表示します、と設定
	lv.lineBreakMode   = UILineBreakModeCharacterWrap;
	lv.numberOfLines   = 0;
	
	
	//ラベルに表示する文字列を設定
	lv.text            = @"サンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列ですサンプルの文字列です";
	
	
	//文字列をすべて表示できるようにラベルの大きさを自動調整する
	lv.sizeToFit;
	
	
	//ラベルの横幅を固定化する
	lv.frame           = CGRectMake(スクロールビューの左上隅を0とした、ラベルを配置するX座標,
                      スクロールビューの左上隅を0とした、ラベルを配置するY座標,
                      固定したい横幅,
                      lv.frame.size.height);
	
	
	//スクロールビューにラベルの大きさを設定(スクロールビュー自体の大きさは変わらない)
	sv.contentSize     = CGSizeMake(lv.bounds.size.width , lv.bounds.size.height);
	
	
	//スクロールビューにラベルを配置
	[sv addSubview:lv];


	//ビューコントローラにスクロールビューを配置
	[self.view addSubview:sv];


	[lv release];
	[sv release];
}

@end

通信速度を制限する

自宅のネット回線はフレッツ光、そこそこ速度がでます。

iPhoneシミュレーターでネットワーク通信を行うアプリを試験するとき、3G回線の速度(それも悪名高いソフトバンクの回線みたいなもの)で試験をやれないものかな、と思いました。


いちいち実機を使うのは手間がかかる。そこでMacOSのほうに帯域制限をかけました。
簡単にできるみたいです。たとえば80Kbit/sの回線をシミュレートしようとするならば、

sudo ipfw pipe 1 config bw 80Kbit/s delay 200
sudo ipfw add 1 pipe 1 src-port 8080

上記はポート8080(http)を80Kbit/sで通信遅延が200ミリ秒に制限しています。


(どうして80KBit/sにしたのかというと
http://iphone.f-tools.net/QandA/au-softbank-speed.html
のサイトでソフトバンク回線の速度0.08Mこんなもんかい、と突っ込まれていたためです。
3Gとはいえ80kBit/sは驚異的な遅さ。これくらいで試験すれば実機に近くなると思います。
また遅延速度は単純に電流や電波が発信されて受信するまでに生ずるロス時間。電波でも光でも速度は有限のため遅延が発生します。これもおそらく実機は100〜200ミリ秒くらいだと思います)


ipfwコマンドはどうやらIPレベルでパケットを制御できるコマンドのようです。
ファイアウォール構築のために利用されているらしいけれども、Macユーザはここらへんフワユルでも使えてしまうのですね。フルフワ。

一度上記のコマンドを実行したあと、通常の速度に戻すのは

sudo ipfw delete 1

いちいちコマンドを入力するのも煩雑なので、スクリプトにしてみました。

#!/bin/sh
echo "[0] 帯域制限開始"
echo "[1] 帯域制限終了"
read a
case "$a" in
0 )
sudo ipfw pipe 1 config bw 80Kbit/s delay 200
sudo ipfw add 1 pipe 1 src-port 8080
;;
1)
sudo ipfw delete 1
;;
esac

実行すると

[0] 帯域制限開始
[1] 帯域制限終了

という画面がでるので、制限するときは0を押してください。
終わるときも実行して、1を押してください。

acceptエラーの原因がpipe

正月浮かれのなか、自宅サーバで動かしているサーバプロセスにエラーが大量発生して驚きました。
自宅サーバの環境はMacOSⅩでソースはCで組んだものでした。

エラーログ

[ERROR]Bad file descriptor
[ERROR]***************************************************
[ERROR]7時44分55秒
[ERROR]accept() にてエラー [pID:10126]
[ERROR]Bad file descriptor
[ERROR]***************************************************
[ERROR]7時44分55秒
[ERROR]accept() にてエラー [pID:10126]
[ERROR]Bad file descriptor
[ERROR]***************************************************
[ERROR]7時44分55秒
[ERROR]accept() にてエラー [pID:10126]
[ERROR]Bad file descriptor
[ERROR]***************************************************

acceptは、ソケット通信において、クライアントからアクセスがあったら、そのクライアントと1対1の通信ができるfile descriptorを生成してくれる関数です。[詳細説明]


ディスクリプタの上限数に達した?


エラーに出てきたようにaccpt関数ででも、ファイル・ディスクリプタを取得できます。クライアントマシンとの入出力に使うファイル・ディスクリプタです。

初めは1プロセスで使用可能なファイル・ディスクリプタの上限に引っかかったのかな?と思いました。

$ ulimit -n
256

これで1つのプロセスで利用可能なディスクリプタ数を確認できます。MacOSⅩでは256個がデフォルトで、ソケット通信時にはこれが支障になります。

accept関数により1クライアント1ファイル・ディスクリプタに関連付けされますが、上限があるのでクライアントが256人以上アクセスしてきた場合、acceptはエラーを返してしまうのです。

しかしログを見る限り、256ものクライアントは接続してきていません。
(そして上限も拡張済みだった)


原因はpipe


プログラムでは共有パイプを利用していました。
これがバグをはらんでいました。

http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/pipe.2.html
によると、

プロセス間通信に使用できる単方向のデータチャネルである。

「単方向」であることが義務づけられているということを知りませんでした。

入力側でデータを取り出した後、取り出したことをpipeに書き込んでしまっていました。入力側で書きこみしてしまうと単方向になりません。(入力側は入力しかできない、当然)

考えれば当然、UNIXコマンドでもデータが逆戻りすることなんてありません。

そして、おそらくは逆戻りしてしまったデータがファイル・ディスクリプタの管理領域を破壊していたのだと思います。

その状態で、パイプをcloseしたら、直後からacceptにエラーが発生しています。

パイプを単方向にちゃんとしたら、このエラーは発生しなくなりました。

fopen(高水準入出力)とopen(低水準入出力)の違い

ファイルに出力する1例

int fd = open("ファイル名",O_WRONLY,S_IWRITE);

返し値はファイル・ディスクリプタです。
システムが0〜2を使っているので、openのたびに3から連番でfdに格納されます。

ちなみに

file descriptor(ファイル・ディスクリプタ)とは、UNIXシステム上で動くプログラムにて、ファイル入出力・ディスプレイ出力・ネットワーク入出力などを行うときに使用する識別子。
簡単にいうと、入力元や出力先を一意に特定するための番号です。

システムは当初、3つを自動で割り当てており、
0:標準入力(stdin)、1:標準出力(stdout)、2:標準エラー出力(stderr)
となっています。
C++やシェル・スクリプトを書く方ならば、どこかで見たことあるぞと感じたのではないでしょうか。ディスプレイやキーボードからの入出力に使う3つの番号が、最初から自動で作成されてるということになります。

プログラムは、どれかを指定することで、ユーザと対話ができることになります。たとえば、printfで文字列を出力するときは、システムが内部で「stdoutに出力」(vfprintf (stdout, format, arg);)という処理を行なっています。


そしてfopenも使えます。

FILE* fp = fopen("ファイル名","w");

返し値はファイルポインタです。


ファイルをオープンするときは通常、高水準入出力(fopen)を利用するかと思いますが、openは低水準入出力。

fopen内部でopen関数が呼び出されています。

fopenとopenの違いは、ざっくりいうとバッファの有無です。

fopenはメモリ上に入出力のためのバッファ領域を設け、そこを介して入出力を行います。
たとえばfputsで文字列を出力したとき、直接ハードディスクに書き込まず、一旦メモリ上のバッファに貯めます。そしてバッファが一杯になってから、ハードディスクに書きこみます。

どうしてこんな事をするのか?は、そうしたほうが高速な出力が可能だからです。
たとえば、数バイト出力するfputsが100回続いていたとしたら、バッファがなければ100回ハードディスクにアクセスする必要があります。そのたびにHDDのディスクの円盤をぐるぐる回して書き込む場所を特定するので、低速になってしまうのです。

しかし(fopenを使い)バッファにデータを貯めこんで、1回でHDDに書き込むことができるならば、書き込む場所を特定するのは1度で済みます。書きこみが高速化できます。

もちろん、open(バッファなし)にも利点があり、リアルタイムでの出力が可能になるということがあります。

1つのファイルに、違うプロセスやスレッドが書きこみを行う場合、データが時系列順になっていないことがあります。

これは、fopenやfputsでアクセスすると一旦バッファに置いて満杯になるまで出力しないためで、プログラムで出力した順番ではなく、バッファが一杯になった順番でHDDに書きこみが始まるためです。

もしデバッグのためにログを出力しているならば、この順不同なログは致命的で、まともなログ追いは難しくなってしまいます。

ちなみにfopenでも直後に
setvbuf(fp , NULL, _IONBF, 0);
としてやれば、バッファリングなしでの入出力が可能です。

文章を横幅固定で表示する(UILabel)

UILabelに複数行表示する際、横幅は固定として文字数に応じて縦幅を自動で伸縮させてやります。

//本文表示ラベル(frameの値は適当でも構いません。後ほど再設定するので)
LabelObject. = [[UILabel alloc]initWithFrame:CGRectMake(==X==, ==Y==, ==WIDTH==, ==HEIGHT==)];

//複数行表示OK
LabelObject.lineBreakMode   = UILineBreakModeCharacterWrap;
LabelObject.numberOfLines   = 0;

//長文テキストを設定(ニュース記事を載せました)
LabelObject.text            = @"安倍首相を指名へ=防衛・小野寺氏、総務・新藤氏&#8212;自公連立内閣、夜に発足......";

//テキストに合わせてUILabelをリサイズする
LabelObject.sizeToFit;

//横幅を固定させる
LabelObject.frame           = CGRectMake(==X==,==Y==,==WIDTH==,LabelObject.frame.size.height);

//View Controllerに追加
[self.view addSubview:LabelObject];

>>表示イメージ