Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

60分でわかるソケットプログラミング

10,545 views

Published on

ソケットプログラミングの基礎の基礎。C言語ができて、UNIXのシステムコール
プログラミングができる人が対象です。

  • Be the first to comment

60分でわかるソケットプログラミング

  1. 1. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 60分でわかるソケットプログラミン グ 60分でわかるソケットプログラミン グ 木本雅彦 <kimoto@soum.co.jp> <kimoto@ohnolab.org> 株式会社創夢 第三開発部 シニアプロジェクトマネージャ 初版:2010年4月作成、改定版:2015年3月作成
  2. 2. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 創夢とはどういう会社か UNIXとTCP/IPネットワークに強い会社 全員がUNIXに詳しい 全員がIPに詳しい ということは → 全社員がネットワークプログラミングくら い出来るよね!? ということで、60分レクチャーです 1/47
  3. 3. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 この発表での前提条件 C言語でのプログラミングはできる system call programming file I/O basics TCP/IPの基礎は理解している OSI階層モデル IPアドレス、ヘッダの構造など 2/47
  4. 4. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 IPというプロトコルについて IP = InternetProtocol はじめに: ホストにはIPアドレスがついているらしいぜ (以下略。知らない人は自力で勉強してください) 3/47
  5. 5. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 TCPとUDPというプロトコル TCP: Transmission Control Protocol 送達確認(ACK)の利用による信頼性のある通信 輻輳制御による「フェアな」通信量の制御 UDP: User Datagram Protocol 信頼性のない通信 パケット単位でデータを投げるだけ 4/47
  6. 6. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 port番号 TCP, UDPで用いる通信口の番号 符号なし16bit 通信相手のアプリケーションを同定する 5/47
  7. 7. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 well-known portとephemeral port well-known port 代表的なサービスに割り当てられた番号 0 - 1023 IANAが管理している ephemeral port 使い捨てポート RFCでのdynamic port: 49152〜65535 Linuxの実装: 32768〜61000 古いBSDの実装: 1024〜4999 6/47
  8. 8. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 一応IPv6について、ひとこと 次世代インターネットプロトコル といいつつ、すでに実用化されている IPアドレスが128bitに拡張されたという一点がもっとも 重要 アドレスの自動設定 マルチキャストの積極的な利用 7/47
  9. 9. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 UNIXのプロセス間通信 pipe 双方向パイプ FIFO 共有メモリ(SYSV由来) セマフォ(SYSV由来) signal socket(BSD由来) 8/47
  10. 10. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 socket 4.2BSDで導入されたTCP/IPの抽象化および実装 通信路の片方の末端を指し示す ファイルデスクリプタで表される →ネットワーク通信をファイルI/Oと同様に記述できる 9/47
  11. 11. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 余談:socketという敗北 4.2BSDでsocketが導入されたのは、従来のUNIXの ようにファイルシステム上にネットワークI/Oをマッピングで きなかったため 主に性能上の理由 これが後々まで影響を及ぼす 例:jailやDocker(コンテナ型仮想化)において、ネットワークの制約の ための枠組みが別途必要になる 10/47
  12. 12. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 サーバクライアントモデル ネットワーク通信のモデル化の一つ vs Peer to Peer サーバ サービスを提供する側 要求を待って処理した結果を返す クライアント サービスを利用する側 サーバに接続し要求を出し処理結果を受け取る 11/47
  13. 13. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 サーバクライアントの通信の流れ 12/47
  14. 14. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 サーバ側の処理の流れ socket(2)でsocketを作る bind(2)でsocketに待受アドレスとポートを指定する listen(2)で待受開始 select(2)で複数ソケットのイベントループを回す 接続があったらaccept(2)で受信して通信用ソケット を得る read(2), write(2)で送受信 13/47
  15. 15. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 socket(2) ソケットを作成するsystemcall ソケットを表すファイルデスクリプタを返す int socket(int domain, int type, int protocol); domain: PF_INET, PF_INET6, .... type: SOCK_STREAM, SOCK_DGRAM, .. protocol: IPPROTO_IP, ... 14/47
  16. 16. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 bind(2) ソケットに名前をつける 名前とは: IPアドレス ポート番号 int bind(int s, const struct sockaddr *addr, socklen_t addrlen); 第2引数で名前=アドレスを指定する 15/47
  17. 17. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 listen(2) 接続を待ち受ける int listen(int s, int backlog); backlogは最大保留数 16/47
  18. 18. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 accept(2) TCPの接続を受けつけ、dynamic portを割り当てる int accept(int s, struct sockaddr * restrict addr, socklen_t * restrict addrlen); 17/47
  19. 19. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 イベントドリブン型プログラミング 外部からのイベントを契機に処理を駆動するプログラミ ングモデル イベント待ち受け イベント受信 ディスパッチ if文の羅列による分岐 ハンドラ/ジャンプテーブル タイマ処理と合わせて擬似スレッドも実現できる 18/47
  20. 20. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 select(2)システムコール fd_set: fdの集合(ビットマップ) read/write/errでチェックするfdを指定(複数可) selectで待つ fd_setにイベントが発生したsocketのfdがセットされる 19/47
  21. 21. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 典型的なselect loop sock = accept(....); for (;;) { FD_ZERO(&fds); FD_SET(sock, &fds); result = select(getdtablesize(),&fds,NULL,NULL, NULL); if (FD_ISSET(sock,&fds)) { **** read from socket ****** } } 20/47
  22. 22. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 多重待受のselect loop listenしているポートもselectの対象にする listen(lsock, ....); for (;;) { FD_ZERO(&fds); FD_SET(lsock, &fds); if (sock >= 0) {FD_SET(sock, &fds);} result = select(getdtablesize(),&fds,NULL,NULL, NULL); if (FD_ISSET(lsock,&fds)) { sock = accept(lsock, ...); continue; } if (sock >= 0 && FD_ISSET(sock,&fds)) { **** read from socket ****** } } 21/47
  23. 23. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 クライアント側の処理の流れ socket(2)でsocketを作る bind(2)でsocketに通信先アドレスとポートを指定す る connect(2)で接続する read(2), write(2)で送受信 22/47
  24. 24. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 connect(2) ソケット経由で接続開始する int connect(int s, const struct sockaddr *name, socklen_t namelen); 23/47
  25. 25. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 readn, writen socketからの読み書きは1度のread/writeで完了す る保証がない 指定した長さが完了するまでread/writeを繰り返す 必要がある readn() / writen() 関数を使う cf. Stevens' UNIX Network Programming ただしブロックしてしまうので注意が必要 24/47
  26. 26. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 sample code of writen: int writen(int fd,char *ptr,size_t size) { int i; while(size > 0) { if ((i = write(fd,ptr,size)) < 0) return i; ptr += i; size -= i; } return size; } 25/47
  27. 27. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 UDPとTCPのプログラミングの違い UDPクライアントはbindしなくていい(してもいい) UDPクライアントはconnectしなくていい(してもいい) UDPはsendto(2)/recvfrom(2)で相手を指定して 送受信できる connectしていればread/writeでもよい 26/47
  28. 28. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 文字列からIPアドレスを得る古い方法 inet_aton IPアドレスの文字列を数値に変換する gethostbyname ホスト名からIPアドレスを得る gethostbyname2 AddressFamilyを指定してホスト名からIPアドレスを得る getservbyname サービス名からポート番号を得る 27/47
  29. 29. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 IPアドレスを得る新しい方法 getaddrinfoを使ったクライアントの例 getaddrinfo(peer, buf, &hints, &res0)); s = -1; for (res = res0; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) continue; connect(s, res->ai_addr, res->ai_addrlen); break; } 28/47
  30. 30. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 ソケットが使っているアドレスを得る方法 getsockname ソケットの自分側のsockaddrを得る getpeername ソケットの相手側のsockaddrを得る 29/47
  31. 31. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 poll, libevent selectを使った実装は重くなる fdごとにif文でチェックするため poll(struct pollfd fds[], nfds_t nfds, int timeout); チェックのコストが低い libevent イベントハンドラを記述するだけ socket以外のイベントも監視できる 30/47
  32. 32. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 タイマ処理 タイマ割り込みによる定期的な処理 alarm(unsigned int seconds); setitimer(int which, .....); selectのtimeoutを用いてタイマ処理を実装すること もある 31/47
  33. 33. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 selectとsignal signalを受信してselectを抜ける場合があるので注 意 タイマ割り込みなど selectがエラーで終了したら、errnoの値を調べる必要がある do { result = select(getdtablesize(),&fds,&wfds,NULL, &tv2); } while((result < 0) && (errno == EINTR)); 32/47
  34. 34. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 bind: address already in use bindしようとしたら、他のプロセスが既にportを使って いる プロセスが異常終了した後、サーバーを再起動すると 出ることがある setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (char *)&sockopt, sizeof(sockopt)); 33/47
  35. 35. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 setsockopt socketの動作を設定する SO_REUSEADDR enables local address reuse SO_REUSEPORT enables duplicate address and port bindings SO_KEEPALIVE enables keep connections alive SO_DONTROUTE enables routing bypass for outgoing messages SO_LINGER linger on close if data present ..... 34/47
  36. 36. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 non blocking socket 複数のサーバに同時に非同期にconnectしたい場合 サーバとクライアントを兼ねている場合 e.g. bgpd, P2P application connectでblockしない f = fcntl(s, F_GETFL); fcntl(s, F_SETFL, f | O_NONBLOCK); 35/47
  37. 37. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 自分のIPアドレスを得る方法 ダメな例 gethostname() → gethostbyname() 多くの場合は127.0.0.1になる 実用的な例 UDP socketを作る 遠くのアドレスに向けてconnectする getsocknameする ただし経路がない場合はこの方法は使えない 36/47
  38. 38. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 struct sockaddr 異なるプロトコルのアドレスを格納するための構造体 struct sockaddr { unsigned char sa_len; /* total length */ sa_family_t sa_family; /* address family */ char sa_data[14]; /* actually longer; address value */ }; OSの違いによるsa_lenの有無に注意が必要 領域を確保するためには、sockaddr_storageを使う 37/47
  39. 39. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 UNIX domain socket いわゆる名前付きパイプ 単一ホスト内の通信に使う PF_UNIX or PF_LOCAL bindする時にパス名を使う 実際にファイルが作成される e.g. try, ls -la /tmp/ 38/47
  40. 40. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 socketpair(2) 名前なしパイプ 連結した二つのsocketを返す パイプと同様に使える 双方向に通信可能 39/47
  41. 41. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 コネクション切断を識別する selectでreadableなのにreadすると0byte → connectionが切れている この処理をしないとselect loopが回り続けるので注 意 40/47
  42. 42. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 inetd: Internet super daemon クライアントからの待ち受けをinetdが行う 接続があったら、プロセスに引き渡す socketがプロセスの標準入出力に割り当てられる 標準入出力のI/Oだけでサーバが記述できる 41/47
  43. 43. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 daemon化するということ daemon化には決まった手順がある 2度forkした後、途中のprocessを終了させ、initの子供になる 標準入出力を/dev/nullに割り当てる 制御端末を切り離す 通常はroot directoryに移動する see daemon(3) 42/47
  44. 44. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 raw socket IPペイロードを直接入出力できる ICMP, OSPFなどで使われている see ping root権限が必要 よって、pingはsetuidされている 43/47
  45. 45. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 BPF: Berkley Packet Filter 生のEthernet Frameを送受信できる /dev/bpf?をopenする ioctlでインタフェースを割り当てる readするとBPFヘッダがついてくる 複数のパケットが読める場合もある writeするとそのままEthernet I/Fに出て行く read時にfilterを記述できる filterは「状態マシン」 44/47
  46. 46. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 Packet Socket : LinuxでL2のパケットを 送受信する方法 PF_PACKET, SOCK_RAWを指定してsocketを作 る L2のパケットを直接送受信できる インタフェースはbind()で割り当てる 簡単なフィルタはあるが、主にPPPoE用と思われる 送受信はread()/write()で行う sock = socket(PF_PACKET, SOCK_RAW, 0); 45/47
  47. 47. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 パケット構造設計の注意点 独自でバイナリ形式のプロトコルを設計する場合 バージョン番号はつけること パケットの長さは先頭部分で明示したほうがよい メモリ領域の確保のため ヘッダとボディとに分けるとよい ボディはTLV(Type, Length, Value)でいいかもしれない 46/47
  48. 48. (c) Masahiko KIMOTO, Ph.D. - http://www.earthlight.jp/ Powered by Rabbit 2.1.6 Where should you start from? inetd 1 session server and client multi-session server and more... 47/47

×