Home > Webサーバ
Webサーバ Archive
C言語(NW)とWebサーバのお勉強「Portable minimal web servers」を読む
- 2010-02-21 (Sun)
- Webサーバ
「Web Sockets云々の前にTCPのソケット通信わかってんの?」
「う゛…」
というやりとりが動機ってわけではないんですが、
最近、Webサーバの実装に興味深々で、
C言語でのネットワークプログラミングについて勉強を始めました。
そんな中見つけたIBMの軽量 Web サーバーという記事で、
「Portable minimal web servers」なる論文が紹介されていました。
非常に短くて素敵だったので、手始めにこいつをコードリーディングしてみることにしました。
予習
いきなりコードを見てもC初心者な僕にはさっぱりだと思ったので、
以下の4冊から関連しそうな部分を流し読みして、わかった気になっておきました。
これでもう怖くありません。
(嘘です、まだまだ読み返さないとわからないことだらけです。)
論文の内容
翻訳しようかと思いましたが、あんまりコードの説明もなさそうだったのでやめときました。
たぶん、
Apacheとか複雑なのもいいけど、シンプルさも大事でしょ?
ちょっとCとbashで作ってみたから、紹介するよ!
GPLだからご自由にどうぞ。
みたいなことが書いてあるはずです。
動作確認
ソースを読む前に、まずは動くか確認。
…以下の手順で問題なく動きました。
準備
cd /Users/t/c/szerver
mkdir htdocs
echo test > htdocs/index.html
設定変更
vi szerver.c
#define PORT 8080
#define DOCUMENTROOT "/Users/t/c/szerver/htdocs"
コンパイル
gcc szerver.c -o szerver
実行
./szerver.c
NW通信の警告popupが出たら「許可」
ブラウザから
http://localhost:8080/
testと表示されればOK
コードリーディング
元のソースはこちら(FTPです)
一部ハンガリー語があったので、
英訳→日訳しつつ読んでいきました。
最終的には下記のようにコメントをつけまくって理解したつもりになってます。
間違ってる部分があればご指摘いただけると幸いです。
いやぁやっぱ公開するコードの変数名とかは英語で書かないと駄目ですね。
非日本語圏の人が「gokei_keisan」とかに出くわしたら、やっぱテンパるんだろうなぁ…。
/*
ヘッダファイルのinclude
TODO: 各ヘッダを何のために使ってるか
*/
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h> //syslog出力
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <libgen.h>
// デバッグログを表示したい時はdefineする
/*#define DEBUG*/
// listenポートとドキュメントルート
#define PORT 8080
#define DOCUMENTROOT "/Users/t/c/szerver/htdocs"
char request_file[1024]; //
void *memory_buffer; // 応答するファイルの内容を読み込むバッファ
size_t buffer_size; // 読み込むファイルのサイズ(バッファのサイズ)
int sock; //
/*
エラーをsyslogに吐く。
hibaはerrorの意味
*/
void hiba( const char *hiba ){
syslog( 0, hiba );
exit(1);
}/*hiba*/
/*
応答するファイルをmemory_bufferに読み込む
*/
int loadfile( ){
struct stat size; // ファイル情報
int infile; // ディスクリプタ
// ファイルの情報を取得
if( stat( request_file, &size ) != 0 )
hiba( "File not found.¥n" );
// サイズを取得
buffer_size = size.st_size;
// サイズ文のバッファを確保
memory_buffer=malloc( buffer_size );
if( memory_buffer==NULL )
hiba( "Out of memory.¥n" );
// ファイルを開いて読み込む
infile=open( request_file, O_RDONLY );
if( read( infile, memory_buffer, buffer_size ) != buffer_size )
hiba( "Unable to read file." );
// ファイルを閉じる
close( infile );
}/*sendfile*/ /* loadfileのタイポ */
/*
socketのbindまでを行う
kapuはport
nyitはopen
-asaは謎
*/
int kapu_nyitasa( void ){
int kapu;
struct sockaddr_in barki; // サーバが接続待ちするIP、ポート情報(barki=any)
// socketの作成
// PF_INET(IPV4)
// SOCK_STREAM(ストリーム)
// 0(IPPROTO_TCPが指定される)
kapu = socket (PF_INET, SOCK_STREAM, 0);
if(kapu < 0)
hiba( "Error open socket.¥n" );
barki.sin_family=AF_INET; // アドレスファミリ(インターネット)
barki.sin_port=htons(PORT); // portをNWバイトオーダーで指定
barki.sin_addr.s_addr=htonl(INADDR_ANY); // ホスト上の全IP
// ディスクリプタ(kapu)とポートのム結び付け
if(bind(kapu, (struct sockaddr *) &barki, sizeof(barki)) < 0)
hiba( "Error binding socket.¥n" );
return kapu;
}/*kapu_nyitasa*/
/*
olvasas = read
*/
int olvasas( int kapu ){
char memoria[1024]; // リクエスト読み込みバッファ(memoria = memory)
int n;
// request_fileにDOCUMENTROOTを書き込む
sprintf( request_file, DOCUMENTROOT );
// memoriaにrequestを読み込む
n=read(kapu, memoria, 1024);
if( n < 0 )
hiba( "Error reading socket.¥n" );
// リクエストラインからファイル名を取得して、DOCUMENTROOT以降に追記
sscanf(memoria, "GET %s",
&request_file[strlen(DOCUMENTROOT)] );
// スラッシュで終わってたらindex.htmlを付ける
if( request_file[strlen(request_file)-1]=='/' )
sprintf(&request_file[strlen(request_file)], "index.html" );
// デバッグログ
#ifdef DEBUG
syslog( 0, "File request: %s¥n", request_file );
#endif
return( n );
}/*olvasas*/
/*
iras = writeっぽい
*/
int iras( int kapu ){
// レスポンスのテンプレ
char head_template[]="HTTP/1.0 200 OK¥r¥n¥
Server: miniserver/0.1¥r¥n¥
Connection: close¥r¥n¥
Content-Type: %s¥r¥n¥r¥n";
char head[512]; // レスポンスするヘッダ
char *type;
int irt;
// Content-Typeの設定
type="text/html";
if( fnmatch("*.gif", basename(request_file), 0) == 0 )
type="image/gif";
if( fnmatch("*.jpg", basename(request_file), 0) == 0 )
type="image/jpeg";
if( fnmatch("*.png", basename(request_file), 0) == 0 )
type="image/png";
// ヘッダを作成
sprintf( head, head_template, type );
// ヘッダの送信
irt=write( kapu, head, strlen(head));
// データの送信
if( memory_buffer != NULL )
irt=write( kapu, memory_buffer, buffer_size );
else
syslog( 0, "Error: no data to send.¥n" );
return(1);
}/*iras*/
/*
メインループ
*/
int mainloop( void ){
int new; // 接続完了後のディスクリプタ
int i;
struct sockaddr_in clientname; // 接続したクライアントの情報
size_t size;
// socketの作成
sock=kapu_nyitasa();
while( 1 ){
// socketをlisten可能状態に
if( listen (sock, 1) < 0 )
hiba( "Error while listening." );
// クライアントと接続
size = sizeof (clientname);
new = accept(sock, (struct sockaddr *) &clientname, &size);
/*waitpid( WAIT_ANY, NULL, WNOHANG );*/
// 子プロセスでリクエストを処理
if( fork() != 0 ){
olvasas( new ); // リクエストからファイル名取得
loadfile(); // ファイルの読み込み
iras( new ); // ヘッダ・データの送信
shutdown( new, 2 ); // TODO: closeとの差とか
if( memory_buffer != NULL )
free(memory_buffer);
return(0);
}
}/*while*/
}/*mainloop*/
/*
終了系のシグナルを受けた時の処理
*/
void signal_handler( int signal ){
shutdown( sock, 2 );
hiba("Exiting...¥n");
}/*signal_handler*/
/*
main関数
*/
int main( int argc, char *argv[] ){
// syslog準備&スタートメッセージ
openlog( "webserver", LOG_PID, LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG) );
syslog( 0, "Miniserver started." );
// 終了系シグナルの処理
signal( SIGINT, signal_handler );
signal( SIGKILL, signal_handler );
signal( SIGTERM, signal_handler );
daemon( 0, 0 ); // daemon化(TODO: 初めて見た)
mainloop(); // mainloop実行
return(0);
}/*main*/
うーん、やっぱ自分でコード書いてみないとダメだなー。
頑張るかー。
Home > Webサーバ
- Search
-
Loading
- Feeds
- Links
- スポンサードリンク