LoginSignup
22
28

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-04-10

注意:
個人的なメモ書きです。内容が正しいことを保証いたしません。
また、利用システムや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);

22
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
28