困ったときはmanコマンド
標準関数なんかは、manコマンドに乗っているので、引数、返り値の確認とかマッハでできて便利。ググるより高速。これ見てわからなければ、ググる。
man
日本語が出ないときは、-aオプションを付けてみる。
man -a
変数は、宣言と同時に初期化すること。
配列はmemsetで初期化
char str[255] = {'\0'};
では、gdbで見た感じ初期化されていない感じ…(確か)
char str[255]; memset(str, 0x00, sizeof(str));
こんなコードが好かれるみたい。
プログラムは構造化すること
- エラー処理をばっちりやること
- 出口が一つのプログラムにすること
- return が一つにすること
- 途中でreturn飛ばして終了するのはNG。最後まで処理を流して、出口は一つにすること。
- 処理が追い易いコードを心がけること
gdb使った
使い方(流れ)
- gcc hello.c -o hello -g -Wall
- デバック用のコンパイルオプション-gをつける
- gdb hello
- gdbの起動
- break
- ブレークポイントの設定(例.break main)
- info break
- 設定しているブレークポイントの情報が見れる
- delete (番号)
- ブレークポイントの削除
- run (コマンドライン引数)
- プログラムの起動、この時にコマンドライン引数を渡せる。
- list
- ソースコードが見れる
- next
- プログラムを一行進める
- step
- プログラムを一行進める&関数があれば、関数の中まで入っていく。
- cont
- ブレークしているプログラムの再開
- print (変数名)
- 変数の中身を見れる。*strみたいに、C言語と同じように使える
- set (変数名=値)
- 変数に値をセットできる
- bt
- コール元の関数hogehoge.コアダンプしたときhogehoge.
そんな感じ。
if文では処理を直接書かずに、変数を評価するようにしたほうが、トレースしやすい。gdbのsetコマンドで変数を変えてif文の中に入れるため。
Socketプログラムの流れ
ネットワークを流れるTCPヘッダはビックエンディアンで送ることが決められている(ネットワークバイトオーダー)
IPアドレス、ポート番号などの情報は、ビックエンディアンに変換して構造体に格納するので注意。
「ソケット」はIPv4専用ではないので、いろいろと設定がややこしかったり、くどかったりするけど、手順覚えてコネクション張るまでの処理を関数化しとけば楽ちん。
ソケットは、プログラムでcloseした後もしばらく開かれたままになり、次回起動時にソケット生成エラーが出ちゃったりするので、オプションですぐにソケットを捨てるように設定しておく。
/* ソケットを、すぐに再利用できるようオプション設定 */ const int one = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
サーバー側の流れ
- socket()
- ソケットを生成
- bind()
- ソケットに接続を許可するクライアントのIPアドレス、ポート番号などの情報を結びつける。普通はall allowな感じのINADDR_ANYを使う。変わるのはポート番号ぐらい。
- listen()
- 接続受付専用のポートを作成する。ここで作成されたファイルディスクリプタでは通信は行わず、acceptので接続受付用として使う。
- accept()
- send()
- クライアントにデータを送信する。unsigned charとかに構造体のデータをmemcpyで入れて、バイナリデータでやり取りを行う、なんていう荒業(?)もあり。クライアントとのコネクションが切れたときは0が返り値、異常時は-1
- recv()
- クライアントからデータを受信する。バイナリデータで受け取ったなら、構造体にmemcpyでコピーして展開して使うなど。バイナリデータ使うと、telnetでデバックできなくなるという難点が…。これもsendと同じでコネクションが切れると0を返し、異常時は-1を返す。
- close()
- でコネクションを切断する。ソケットを閉じる。
クライアント側流れ
- socket()
- connect()
- send()
- recv()
- close()
リッスンポートを作成する関数の例
センス無いコードなので、あくまでも例として…
/* * 機能 :リッスンポートを作成する * 引数 :ポート番号とキューの最大数 * 戻り値:リッスンポートのファイルディスクリプタ */ int create_socket(int port_no, int listen_num) { /* リッスンポートのファイルディスクリプタを保持する変数 */ int listen_fd = 0; int result = 0; /* サーバー情報の構造体を初期化 */ struct sockaddr_in serv_addr; memset(&serv_addr,0x00,sizeof(struct sockaddr_in)); /* ソケットの用意 */ listen_fd = socket(AF_INET, SOCK_STREAM, 0); /* ソケットのバインド処理 */ if (listen_fd == -1) { result = -1; } else { /* ソケットの設定 */ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port_no); serv_addr.sin_addr.s_addr = INADDR_ANY; /* ソケットを、すぐに再利用できるようオプション設定 */ const int one = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); /* バインド処理 */ if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { result = -1; } else { /* リッスン処理 */ if ((listen(listen_fd, listen_num) == -1)) { result = -1; /* 返り値をリッスンポートのファイルディスクリプタとする */ } else { result = listen_fd; } } } return result; }
今疑問に思っていること
ブロックスコープで変数を局所化
よくループで使う int i=0; とかは、狭いブロック内で宣言して、狭いスコープで宣言したほうがいいのか?
それとも、関数の頭で使用する変数はすべて宣言したほうがいいのか…。
例外処理やりだすと、if文がごちゃごちゃ…
センスがなさすぎる構造化…
main(){ if(!処理成功) { // 失敗時の処理 } else { // 成功時の処理 if (!処理成功) { // 失敗時の処理 } else { //成功時の処理 } } return 結果; }
ネストが少ない例
main() { if(!前処理成功) { // 失敗時の処理 } else { // 成功時の処理 } if(!前処理成功) { // 失敗時の処理 } else { // 成功時の処理 } return 結果; }
とか
main() { if(前処理成功) { // 成功時の処理 } if(前処理成功) { // 成功時の処理 } return 結果; }
と、前処理が成功なら、処理ブロックに入っていく、のほうがまだネストが少なくなるので見やすい。goto使って例外は飛ばしたほうがいい気がするけど、gotoがコーディング規約で禁止されていたら駄目だし、構造化は守らないといけないし…どーしましょ。
utf-8はマルチバイトコード
printf使ったとき、%30sとかやっても表示が合わない。utf-8はマルチバイトコード(だっけ?)なので、日本語は3バイトで表現されていたりする。日本語が出力されると、一文字ずつ表示がずれちゃったりするので、必要。
ワイド文字?とか解決する方法はありそうなんだけど、戦えなかった…orz
文字列を比較して「utf8の文字があれば、printfの書式を一つ増やす」という方法でやっつけな解決。
/* 書式を動的に変更する方法 */ printf("%*s",35+i,str);
ナイスなコーディング
どういうのがいいコーディングなんだろう…
コーディングの本買おう。いい本ないかな?
「良いコードを書く技術」とかいいのかな?
後は・・・
後は、pthreadとMySQLのC APIをやったけど、もーいーやー(´・ω・`)
pthread - マルチスレッドのための仕組み
pthreadは
- pthread_create
- スレッドの生成、関数をスレッドとして動かす
- pthread_detach
- スレッドを切り離す
- pthread_join
- スレッドの終了を待つ
って感じ。
メイン関数が終了すれば、スレッドも解放されるので、特にゾンビプロセスなどの問題はなさそうだけど、正常にスレッドを終了させる必要があると、プログラムが大変になるかも。
スレッド間のやり取りとか、スレッド管理、スレッドセーフ
そこまではできなかった。
その辺は、次回への課題ということで