ディレクトリ
LinuxのNIOシリーズ(02)IOをブロック
まず、環境を準備
yum install -y gcc nc
man socket # 帮助手册
インストールし、Linux上で使用し、manページはこちらをクリック
1.1コードショー
(1)コンパイル
[root@localhost test]# gcc socket.c -o socket
(2)サーバを起動します
[root@localhost test]# ./socket
(3)NCアナログ端末カスタマーサービス
[root@localhost ~]# nc 127.0.0.1 8888
hello,world
ここでは、サーバは、こんにちは、世界のリクエストを受信しますが、ノースカロライナ州の複数に応じて開くことができません。
[root@localhost test]# ./socket
hello,world
第二に、ソケットは何ですか
2.1ソケットソケット
ソケットは、Unixの起源、およびUnix / Linuxは、あなたが使用することができ、「すべてはファイルである」という基本理念の一つである「オープン開く - 読み/>読み書き - >クローズクローズ」動作モードを。ソケットは、モデルの実現である、すなわちソケットがそれらのいくつかのオペレーティング・ソケット機能が行われ、特殊なファイルです(読み取り/書き込みIO、オープン、クローズ)。
はっきりソケット・ソフトウェア・アプリケーションは、中間層との界面の集合である抽象化層を、通信するためにTCP / IPプロトコルスイートです。デザインモードでは、ソケットは、実際に、それはソケットアダプタの後ろに隠されているTCP / IPプロトコルスイートの複雑ファサードパターンであるすべて、ソケットが指定を遵守するために、データを整理してみましょうされ、ユーザー、シンプルなインタフェースのセットが合意。
注意:実際には、プログラミングが容易になるように、それは、単にアプリケーションのデザインパターンのファサードです、何のソケット層の概念はありません。これは、ソフトウェアの抽象化層です。ネットワークプログラミングでは、我々は多くのソケットを介して実現されます。
2.2ソケット記述子
実際に、それは整数であり、我々は3つのハンドルが0,1,2であると最もよく知られている、0は標準入力、1つの標準出力、2標準エラー出力です。0,1,2整数表現は、対応する構造は、FILE *はSTDIN、STDOUT、標準エラーで表されます
ソケットAPIは、独自に開発したUNIXオペレーティングシステムの一部として開発されたので、他のI / OデバイスとソケットAPIは、システムを統合しました。具体的には、アプリケーションがソケット(ソケット)を作成したい場合、インターネット通信で、オペレーティング・システムは、ソケットを識別するために、ディスクリプタ(記述子)のような小さな整数を返します。次に、ディスクリプタの引数としてアプリケーション、関数を呼び出すことによって(例えば、データがデータ転送または入力のためにネットワークを介して受信された)アクションを実行します。
多くのオペレーティングシステムでは、ソケット記述子および他のI / O記述子が一体化され、アプリケーションがファイルの読み取り/書き込み操作へのソケットI / OやI / Oすることができます。
アプリケーションがソケットを作成するとき、オペレーティングシステムは小さな整数として記述を返し、記述子はソケットを指すために使用されるアプリケーションは、ファイルを開くために、オペレーティングシステムを要求しているアプリケーションのI / O要求を必要とします。オペレーティングシステムは、ファイルにアクセスするアプリケーションにファイルディスクリプタを作成します。アプリケーションの観点からは、整数のファイルディスクリプタで、アプリケーションがファイルを読み書きするために使用することができます。以下の表は、オペレーティング・システム・ファイル記述子がポインタの配列として実装方法を示し、内部データ構造へのポインタ。
各プログラム・システムのための別々のテーブルを有しています。正確には、各実行中のプロセスのためのシステムは、個別のファイルディスクリプタ・テーブルを維持します。プロセスがファイルを開くと、システムは、ファイルの内部データ構造体へのポインタは、ファイルディスクリプタテーブルを書き込まれており、インデックステーブルの値が呼び出し側に戻されます。ただ、このアプリケーション記述子を覚えているし、ファイルの将来の操作でそれを使用。インデックスプロセスの記述子テーブルとして記述子にアクセスするためのオペレーティングシステム、ファイルポインタによって保存されているすべての情報のデータ構造を見つけます。
ソケットのシステムデータ構造:
1)、ソケットAPI、機能ソケットがあり、ソケットを作成するために使用されます。一般的な考え方は、ソケットを設計することで、あなたはソケットが非常に一般的であるため、任意のソケットを呼び出す単一のシステムを作成することができます。ソケットが作成されると、アプリケーションはまだ詳細を指定するには、別の関数を呼び出す必要があります。このような新しい記述子のエントリを作成するソケットの呼び出しと:
ソケットの内部データ構造は、多くのフィールドが含まれていますが、システムがソケットを作成するものの2)、文字のほとんどは、フィールドに記入しないでください。あなたが使用する前に、アプリケーションがソケットにソケットを作成した後、あなたは、これらのフィールドを埋めるために別のプロシージャを呼び出す必要があります。
2.3とファイルポインタディスクリプタの違い
ディスクリプタファイル:ファイル記述子を取得するLinuxシステムでファイルを開き、それは非常に小さい正の整数です。各プロセスは、ファイルディスクリプタテーブルのコピーにPCB(プロセス制御ブロック)に格納され、ファイルディスクリプタテーブルのインデックスであり、各エントリは、ファイルポインタを開くためのリンクを有しています。
ファイルポインタ:C言語は、ファイルI / Oハンドルへのポインタとして使用されています。FILE構造と呼ばれるユーザ領域のデータ構造内のプロセスへのファイルポインタ。バッファとファイルディスクリプタを備えたFILE構造。ある意味で(Windowsシステム上で、ファイルディスクリプタはファイルハンドルと呼ばれている)ファイルポインタハンドルを処理するためにあるように、インデックスファイル記述子は、ファイルディスクリプタテーブルです。
第三に、基本的なインタフェース機能ソケット
生活の中で電話にBに、A、ダイヤルアップA、Bリフト電話を携帯電話のリングを聞いた後、その後、AとBは接続を確立するために、AとBは話すことができます。そして為替のもう一方の端は、この会話を終了するには電話を切ります。「オープン・書き込み/読み出しクローズ」モード:この電話は動作原理を説明するために非常に簡単です。
その後、サーバーは(バインド)結合ポートは、ポート(聞く)、クライアント接続を待って、ブロックされたコールを受け入れるに耳を傾け、ソケットを初期化します。接続が成功した場合は、クライアントはソケットを開始している場合は、この時点では、サーバー(接続)に接続し、その後、クライアントを接続し、サーバが確立されています。クライアントは、サーバがリクエストを受信し、要求を処理し、クライアントに応答データを送信し、クライアントがデータを読み込み、そして最終的に接続を閉じ、対話の終了、データ要求を送信します。
インタフェースの実装が完了するまでにカーネルです。具体的に達成するためにどのように、あなたはLinuxカーネルを見てみることができます
- ソケット
- バインド
- 聴く
- 受け入れます
- コネクト
3.1ソケット()関数
int socket(int protofamily, int type, int protocol); //返回 sockfd(文件描述符)
ソケット関数は、通常のファイルオープン操作に対応しています。通常のファイルオープン操作は、ファイル記述子を返し、ソケット()一意のソケットを識別するソケットディスクリプタ(記述子ソケット)を作成するために使用されます。その後の動作を説明するために、同じ単語とソケット記述子ファイルは、それを使用する必要があり、いくつかの読書とそれを介して書き込みを行うには、引数としてそれを取ります。
別のファイルを開くためのfopenに渡された異なるパラメータ値することができます。ソケットを作成するときに、あなたはまた、別のソケット記述子を作成するために、異なるパラメータを指定することができ、3つのパラメータソケット関数は、次のとおりです。
protofamily:つまり、プロトコルフィールドとも呼ばれるプロトコルファミリー(家族)。一般的に使用されるプロトコルスイート、AF_INET(IPV4)、AF_INET6(あり IPV6)、AF_LOCAL( ようにもAF_UNIX、UNIXドメインソケットとして知られている)、AF_ROUTEとは。プロトコルファミリはAF_INET使用IPv4アドレス(32ビット)とポート番号(16ビット)、AF_UNIX絶対パスを使用するように決定した組成物を決定し、アドレスは、対応する通信である必要があり、ソケットのアドレスタイプを決定しますアドレスと名前を付けます。
タイプの:ソケットタイプを指定します。一般的に使用されるソケットタイプ、SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET 、SOCK_SEQPACKET など(何の種類のソケット?)。
プロトコル:イタリアの名前が示唆するには、プロトコルを指定することです。TCPプロトコル、UDPトランスポートプロトコル、STCP伝送プロトコル、伝送プロトコルTIPCに対応する共通のプロトコル、IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等があります。
注:いない上記の種類とプロトコルを自由に組み合わせることができ、SOCK_STREAMをIPPROTO_UDPと組み合わせることができないなど。プロトコルは自動的にデフォルトのプロトコル・タイプのタイプに応じて選択される0。
我々はソケットを作成するソケットを呼び出すと、ソケット記述は、それが特定のアドレスをプロトコルスイート(アドレスファミリ、AF_XXX)空間に存在するではなく、返されました。あなたはそれにアドレスを割り当てたい場合は、バインド()関数を呼び出す必要がありますあなたは、Connect()の呼び出し時に聴くと、それ以外の場合、システムが自動的に)(ランダムなポートを割り当てます。
# tcp/ipv4
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
3.2バインド()関数
上述したように、アドレスにバインド()関数は、アドレスファミリ特定のソケットに割り当てられています。例えばAF_INET、AF_INET6に対応するまたは組成物はソケットに割り当てられたIPv4アドレスとポート番号をIPv6へあります。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3つのパラメータはの関数であります:
数sockfd:それはソケット()関数を介してであるソケット記述子は、一意のソケットを識別する作成します。バインド()関数は、名前の結合を記述するために言葉を与えることです。
ADDR:数sockfdに結合するプロトコルアドレスへの*ポインタのconst sockaddr構造体を。対応のIPv4であるように、異なるアドレスでプロトコルの異なるアドレスファミリに応じてアドレス構造は、ソケットを作成します。
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; # ipv6对应的是: struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ }; # Unix域对应的是: #define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
addrlenは:アドレスの長さに相当します。
通常、起動時にサーバがサービスを提供するため、顧客はサーバのシリーズにそれを使用することができ、(IPアドレス+ポート番号など)のよく知られたアドレスをバインドします。クライアントが指定されていないだろう、システムが自動的にポート番号を割り当てていそして、自分のIPアドレスの組み合わせ。あなたは呼び出しが(バインド聞く前に、これは)なぜ通常サーバーであり、クライアントと呼ばれるが、ランダム接続でシステム(とき)によって生成されることはありません。
ネットワークホストエンディアンバイト順
ホストバイトオーダーは、我々は通常ビッグエンディアンとリトルエンディアンモードを言うことである:異なるCPUの種類がために、メモリに保存されたバイトオーダの整数を指し異なるバイト順序を、持っている、これはホストシーケンサーと呼ばれています。次のようにビッグエンディアンとリトルエンディアン定義された参照標準は以下のとおりです。
a)は、リトルエンディアンの低バイトが、メモリの低端部にメモリのハイエンドのアドレス上位バイト排出アドレス放電です。
b)は、ビッグエンディアンは、ハイエンドメモリのメモリのローエンド及び低バイトアドレス放電時にアドレス放電の上位バイトです。
ネットワークバイト順:次の順序で送信された4バイトの32ビット値:8〜15bit、次いで16〜23bit、そして最後に24〜31bit続いまず、0〜7ビット、。この送信順序はビッグエンディアンと呼ばれています。TCP / IPは、この順に必要なので、それはネットワークと呼ばれているためときに、トランスポートネットワーク内のすべてのバイナリ整数のヘッダバイトオーダー。エンディアン、名前のバイト順が示す、メモリ内のデータ型のバイトの格納されたシーケンスよりも大きい、1バイトのデータは、シーケンス内の問題ありません。
だから、:アドレスはソケットにバインドする場合、最初のホストバイトオーダーがネットワークバイトオーダーに変換し、同じネットワークとホストエンディアンバイト順はビッグエンディアンを使用していることを前提としていません。この問題が原因で流血の原因とされています!原因この問題の存在に会社のプロジェクトのコードなので、ソケットを割り当てた後、ネットワークバイトオーダーに変換し、必ず、何らかの仮定をしないようにバイト順序をホストするために覚えていてください、原因不明の多くの問題につながります。
3.3聞く()()関数を接続
サーバとして、()ソケットを呼び出した後、バインド()は、クライアントが接続要求)(接続を呼び出した場合は、この時点で、ソケットをリッスンするように)(聞くと呼ばれ、場合、サーバはこの要求を受信します。
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
最初のパラメータは、ソケット記述子を聞いて耳を傾ける機能である、2番目のパラメータは、それぞれのソケットをキューに入れることができる接続の最大数です。ソケットのデフォルトを作成するためのソケット()関数は、アクティブ型で、関数は、クライアントの接続要求を待って、ソケットのパッシブ型となります聞きます。
最初のパラメータが関数は、クライアントソケット記述子に接続され、第2のパラメータは、サーバソケットのアドレスであり、第3のパラメータは、ソケットアドレスの長さです。connect関数を呼び出すことにより、サーバとのTCP接続を確立するクライアント。
3.4()関数を受け入れます
(ソケットを呼び出すためにTCPサーバー後)、バインド()、(聞く)、それは指定されたソケットアドレスを監視します。順番にTCPクライアントはソケットを()を呼び出した後に、(接続)サーバへのTCP接続要求を送信します。この要求を聞いた後、それは()関数を受け入れるTCPサーバを呼び出します。このような接続が十分に確立され、要求を受信するのにかかります。ネットワークI / O操作の後に開始し、それが普通のファイル読み込みに似ており、I / O操作を書くことができます。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接 connect_fd
数sockfd:
パラメータ数sockfdは、サーバーに接続されたクライアントがある場合に、それは時間nと、スリーブで、ポート番号、ポート番号を使用して、リスニングソケット、ポートを監視するために使用されるソケットで上に説明されていますその後、関連する言葉。もちろん、これらの顧客は、それが唯一のアドレスとポート番号を知っている、ソケットの詳細を知りません。ADDR:
これは、戻り値を受け入れるために使用されるパラメータ、の結果であり、それはクライアントの指定されたアドレスの値を返します、もちろん、アドレスは構造を記述することにより、アドレスがあり、ユーザーはどのようなアドレスの構造があることに注意してください。顧客のアドレスが興味を持っていない場合は、NULLにこの値を設定することができます。最大len:
私たちは思ったとおり、それはまた、addrの構造によって占められるバイトのいくつかの数を示すパラメータの結果、ADDRを受信するための構造体のサイズ、です。同様に、それはまた、NULLに設定されてもよいです。
あなたが成功したリターンを受け入れた場合は、サーバーとクライアントが接続を確立する権利を有する、とソケットが返さ受け入れを経て今回のサーバーは、クライアントとの通信を完了させます。
注:デフォルトでは、接続が確立された後、それは新しいソケットが利用可能である返し、このソケットはソケットに接続されている顧客が戻るまでのプロセスをブロックします受け入れます。
この時点で、我々は二つのソケットを区別する必要があり、
ソケットをリスニング:リスニングソケットが数sockfdパラメータとして受け入れ、それが機能を聴い呼び出した後、リスニングソケットは、ソケット()ファンクションジェネレータを呼び出してサーバの起動である、(リスニングソケットリスニングソケット記述子と呼ばれます)
ソケットコネクタ:ソケットからソケットがリスニングソケットコネクタとしてアクティブオンされ、及び機能は、ソケット記述子が接続されている戻り受け入れる(コネクタソケット)は、ネットワークを表します既存のピア・ツー・ピア接続。
通常、1台のサーバのみ通常はリスニングソケット記述子を作成している、それは常にサーバーの生涯の中に存在していました。カーネルは、サーバが顧客にサービスを完了した場合、接続を受け入れるようにサーバプロセスによって、クライアントごとに接続されたソケット記述子を作成し、接続されたソケットを記述するために適切なワードを閉じました。
依頼するナチュラル質問は:なぜソケットの2種類がありますか?その理由は、あなたが説明的言葉を使用している場合、それはあまりにも多くの機能ですので、カーネルはこれを記述するために新しい単語を生成しなかった一方でそれは、使用することは非常に直感的で、簡単です。
まだ同じリスニングソケットのポート番号を使用して、新しいポートとクライアントの通信を取りませんでしたsocketfd_new接続ソケットsocketfd
3.5読み取り()、()関数などを書きます
すべては、サーバとクライアントが良好な接続を確立しているもたらし、所定の位置にだけ強い風です。あなたは、I / Oの読み取りおよび書き込み操作を、わずかネット内の異なるプロセス間の通信を実現するネットワークを呼び出すことができます!ネットワークI / O操作は、次のグループがあります。
- 読み書き()
- RECV()/(送信)
- READV()/ writev()
- recvmsg()/にsendmsg()
- recvfrom()/はsendto()
私は、これら2つの関数のrecvmsg()/にsendmsg()関数を使用することをお勧め最も一般的なI / O機能で、実際には他の機能は、上記のこれらの二つの機能によって置き換えられることができます。次のように彼らの文は次のとおりです。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
関数がでFDからコンテンツを読み込むための責任がある。成功を読み取るときの読み取り、返しますバイトの数を読んで、実際に読んで、と返された値が0の場合は0未満、ファイルの終わりが読み込まれたことを意味し、エラーを示しています。エラーがEINTR説明読むの中断によって引き起こされている場合、それはECONNREST LAN接続であれば、問題外です。
書き込み機能は、ファイルディスクリプタfdを書くために、バイトBUFをNBYTES。書き込まれたバイト数を成功に返されます。失敗すると-1を返し、errno変数を設定します。ネットワークプログラムでは、可能な2我々はソケットファイル記述子への書き込みがあります。1)戻り値が0よりも大きい書き込み、全体または書き込みデータ部分を表します。2)返された値が0未満である場合、エラーが発生しました。私たちは、エラーの種類に応じて処理する必要があります。エラーがエラーの場合はEINTRを書いている時点でその中断を示しています。EPIPEは、ネットワーク接続に問題があることを示している場合(反対側は接続を閉じました)。
4.6クローズ()関数
サーバがクライアントとの接続を確立するためにした後、いくつかの読み取りを行い、読み取りを完了し、あなたが近くに開いているファイルを開くfcloseを呼び出したいファイルの完全な操作と同様に、対応するソケット記述子を閉じます書き込み操作への書き込み。
#include <unistd.h>
int close(int fd);
デフォルトの動作ではオフにマークされたソケットにTCPソケットを閉じてから、呼び出し元のプロセスにすぐに戻すことです。記述子は、呼び出し元プロセスで使用することはできません、それはもはや、最初のパラメータの読み取りや書き込みなどはありません。
注:のみ対応ソケット記述子の参照カウントのクローズ操作-1参照カウントが0の場合のみ、クライアントはTCPの接続を終了するには、サーバーに要求をトリガする送信します。
附属書1:ソケットネットワークプログラミングコード
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888
#define BACKLOG 10
#define BUF_SIZE 1024
void main() {
//网络地址结构 server
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(SERVER_PORT);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
char recv_buf[BUF_SIZE] = "";
//客户端
struct sockaddr_in client_addr;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
listen (listenfd, BACKLOG);
socklen_t cliaddr_len = sizeof(client_addr);
int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &cliaddr_len);
//char client_ip;
//inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
//printf("client ip=%s,port=%d\n", client_ip, ntohs(client_addr.sin_port));
while(1) {
int k = read(clientfd, recv_buf, sizeof(recv_buf));
if(k > 0) {
printf("%s\n", recv_buf);
}
}
}
参考:
- Javaの詳細シニア靖江-NiOネットワークプログラミングでの詳細な分析(ルバニ研究所が作成)
- LinuxのプログラミングIOのマルチプレクサ選択/世論調査/ epollを
- ソケットプログラミングは、Linuxを説明します
毎日少しを記録する意向。おそらく、内容は重要ではありませんが、習慣は非常に重要です!