注意:
個人的なメモ書きです。内容が正しいことを保証いたしません。
また、利用システムやC言語の知識の無い方は無闇に真似しないでください。危険です。
(編集中)
#基礎
###ネットワーク・プログラミング
パイプ(pipe(2))やFIFO(名前付きパイプ)など、同一の計算機上でプロセス間通信を行うことがある。
このプロセス間通信を原則として異なる計算機、自分が所属していないネットワーク上で
通信規約に従って行うものをいうことにする。
###アーキテクチャへの注意
構造体アライメントやバイトオーダー、アドレス・バスがどのような規則でアドレスを指すのか、
バス幅などは、利用するアーキテクチャごとに違う。
C言語はその違いを知っていることを、C言語ユーザーに託している。
ネットワーク上で異なるコンピューターと通信する場合、計算機に依存しないよう注意。
例えば、RISC型のプロセッサでは奇数アドレスへのアクセスはバス・エラーとなることがある。
###システム・コール
ネットワーク・プログラミングではシステムコール(uapi)を利用しアプリケーションを作成する。
ユーザー・アプリケーションではカーネルはユーザモードで動作し仮想メモリを利用する。
ネットワーク・モジュールを直接扱うのはオペレーティングシステムが行う。
これはカーネルモード(特権モード)で行われる。
ネットワーク・アプリケーションはソケットというインターフェイスを利用する。
ソケットは特殊ファイルとして扱える。ソケットを作成するにはsokect(2)を発行する。
ソケットもファイルであることには変わりない。
よって、read(2),write(2)のようなバイナリ入出力関数を利用することもできる。
作成されたソケットはbind(2)によってアドレス割り当てを行うことが出来る。
また、ソケットもファイルの一種であるためiノード番号により管理される。
ソケットを含むネットワーク・モジュールはデータを格納するバッファを持ち、
パケットをキューに入れる。オペレーティングシステムにデータをの送信依頼を行うには、
送信側アプリケーションでsend(2)を発行する。(依頼するものであって保証するものではない)
バッファに空きがあればキューに溜められる。
データは受信側アプリケーションでrecv(2)を発行することにより、
バッファーからデータ取り出しを依頼する。
受信データがキューに存在しない場合プログラムはブロックされる。
なお、アプリケーションを実行してないくともrecv(2)はバッファからデータを取り出している。
正常に終了しない場合、無限にデータの取り出しを行うことになるので注意。
NICがデータの送受信を行うのに割り込みが利用されている。これはカーネルが面倒を見てくれるが、
C言語ではユーザー向けに抽象化されたシグナルが用意されている。
###冗長性
複数クライアントからネットワーク・アプリケーションへの要求がある場合、
当該のアプリケーションでは処理能力を超える場合がある。
この場合、マルチプロセス、マルチスレッド、select(2)などで対応する。
###エラーへの対処
connect(2)はアプリケーション・レベルで通信相手を特定するのに利用する。
TCPであれ、UDPであれコネクションが確立された状態でデータの送受信処理が正常に終了されない場合、
そのままであれば、永遠に処理がブロックされるおそれがある。
悪い場合、キー入力すら受け付けない状態になる。
よって、alerm(2),setitimer(2)などでタイムアウトする。
sigaction(2)などで適切にシグナルハンドリングする。
###プロトコル・ヘッダ
多くのプロトコルはC言語の構造体で表現されている。
これらの詳細の多くは、以下のディレクトリにある。
/usr/include/net/*
/usr/include/netinet/*
#TCP/IP
##DGRAM型の通信とSTREAM型の通信
###UNIXドメインとINETドメイン
####データ
TCP/IPではパケット交換による通信が行われる。
パケットはヘッダとペイロードから成る。
####ヘッダ
ヘッダには送信先アドレス、送信元アドレス、ペイロードに関する情報などが含まれている。
####ペイロード
ペイロードは通信したいデータ自体
###メッセージ
OS上に通信用のアプリケーションを作成することになるが、このアプリケーション間でやりとりするデータをメッセージと呼ぶ。
###データ型
次に挙げるヘッダ内のデータ型はビット単位のサイズを保証してくれる。
なお、これはPOSIX準拠のOSでも同様に提供されている。
/usr/include/stdint.h
<stdint.h>
<inttypes.h> (C99)
例:
uint32_t /* 符号なし32ビットint型 */
どのように定義されているかはシステムに依る。
詳細は直接ヘッダを見て確認のこと。
###ネットワーク・バイト・オーダー
ネットワーク上ではTCP/IPプロトコルのプロトコル・ヘッダや
アプリケーション・メッセージはビッグエンディアンに統一する。
エンディアンの変換は次に示す関数が利用できる。
#include <arpa/inet.h> /* システムによっては<net/inet.h> */
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
uint32_t s = 0xff;
/* host to network endian */
uint32_t r = htonl(s);
printf("%#0.8x\n", r);
printf("%#0.8x\n", s);
return 0;
}
結果例
0xff000000
0x000000ff
###IPアドレスとドメイン名の名前解決
1. /etc/hosts ファイルを利用
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
#include <sys/socket.h>
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
2. DNSを利用
#include <sys/socket.h>
void sethostent(int stayopen);
void endhostent(void);
なお、DNSサーバーで名前解決する場合、事前に/etc/resolv.confにDNSサーバーへのアドレスを登録しておく。
###プロトコル・エントリー
/etc/protocols
#include <netdb.h>
struct protoent *getprotoent(void);
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
###サービス・エントリー
/etc/services
#include <netdb.h>
struct servent *getservent(void);
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);