ネットワーク・プログラミング(基礎)


注意：
個人的なメモ書きです。内容が正しいことを保証いたしません。
また、利用システムや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言語の構造体で表現されている。
これらの詳細の多くは、以下のディレクトリにある。

<b>/usr/include/net/\*</b>
<b>/usr/include/netinet/\*</b>

#TCP/IP

##DGRAM型の通信とSTREAM型の通信

###UNIXドメインとINETドメイン

####データ

TCP/IPではパケット交換による通信が行われる。

パケットはヘッダとペイロードから成る。

####ヘッダ

　ヘッダには送信先アドレス、送信元アドレス、ペイロードに関する情報などが含まれている。
####ペイロード

　ペイロードは通信したいデータ自体

###メッセージ

　OS上に通信用のアプリケーションを作成することになるが、このアプリケーション間でやりとりするデータをメッセージと呼ぶ。


###データ型

次に挙げるヘッダ内のデータ型はビット単位のサイズを保証してくれる。
なお、これはPOSIX準拠のOSでも同様に提供されている。

<b>/usr/include/stdint.h</b>

<b>\<stdint.h\></b>
<b>\<inttypes.h\> (C99) </b>

例：

```c

uint32_t /* 符号なし32ビットint型 */

```

どのように定義されているかはシステムに依る。
詳細は直接ヘッダを見て確認のこと。

###ネットワーク・バイト・オーダー

ネットワーク上ではTCP/IPプロトコルのプロトコル・ヘッダや
アプリケーション・メッセージはビッグエンディアンに統一する。
エンディアンの変換は次に示す関数が利用できる。

```c 

#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);

```

```c

#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 ファイルを利用

```c 

#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を利用

```c

#include <sys/socket.h>
void sethostent(int stayopen);
void endhostent(void);

```

なお、DNSサーバーで名前解決する場合、事前に/etc/resolv.confにDNSサーバーへのアドレスを登録しておく。

###プロトコル・エントリー

/etc/protocols

```c 
#include <netdb.h>

struct protoent *getprotoent(void);
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);

```

###サービス・エントリー

/etc/services

```c 

#include <netdb.h>

struct servent *getservent(void);
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);

```