でネットワークプログラミングの基礎:Linuxののソケットプログラミングはでいくつかの機能ソケットプログラミングフレームワークを紹介します。このブログでは、ネットワークデータがユーザーに正常に届くようにすることができます。このブログでは、主にネットワーク通信データのやり取り、つまりネットワークデータの送受信とIOモデルについて説明します。
ネットワーク通信で使用されるIO機能
ネットワーク通信で使用されるIO関数は、recv()/ send()、readv()/ writev()、recvmsg()/ sendmsg()、read()/ write()、recvfrom()/ sendto()です。このうち、read / writeはすべてのファイルの共通操作インターフェースであり、ここでは説明しませんが、Recvfrom / sendtoについては前回の記事で説明しましたが、ここではあまり紹介しません。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const 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 sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
受信と送信のプロセスは同じですが、両者の違いは主に一度に送信されるデータの量と、相手の情報(IP、ポートなどのアドレス構造情報)を選択できるかどうかです。違いは次のとおりです。
- read / writeおよびreadv / writevはすべてのファイル記述子に使用できます。recvfrom/ sendto、recv / send、recvmsg / sendmsgはソケット記述子のみを操作できます。
- readv / writevおよびrecvmsg / sendmsgは複数のバッファーを操作でき、読み取り/書き込み、recv / send、recvfrom / sendtoは単一のバッファーのみを操作できます
- recvfrom / sendto、recvmsg / sendmsgは相手のIPアドレスを選択できます
- recvmsg / sendmsgにはオプションの制御情報があり、高度な操作を実行できます
これらの関数のフラグの値は次のとおりです。単一値またはビット単位で生成された複合値または
- MSG_DONTWAIT:ノンブロッキングIO操作。ただちに戻り、待機しません。単一の操作の場合、ファイルのフラグ値は変更されません。
- MSG_ERRQUEUE:ソケットエラーキューから受信したエラーメッセージ
- MSG_OOB:帯域外データを受信します
- MSG_PEEK:バッファー内のデータをクリアせずにデータを表示します
- MSG_TRUNC:すべてのデータを返します。バッファが小さすぎるとデータが失われます
- MSG_WAITALL:すべてのメッセージを待機します。つまり、バッファー内のデータが指定された長さに達してから戻ります。
受信操作の戻り値は正常に受信されたバイト数であり、エラーが発生したことを示すために-1が返されます。相手が通常の方法で接続を閉じた場合、戻り値は0です。
受信機能とカーネル相互作用プロセス:カーネルバッファー内のデータが指定されたバッファーより小さい場合、MSG_WAITALLが設定されていない場合、バッファー内のすべてのデータがユーザーバッファーにコピーされ、データの長さが返されます。カーネル受信バッファーにユーザーが指定したよりも多くのデータがある場合、ユーザーが指定した長さlenの受信バッファー内のデータはユーザーが指定したアドレスにコピーされ、残りのデータは次に受信関数が呼び出されるときにコピーされる必要があります。ユーザーが指定したデータをコピーした後、カーネルはコピーされたデータを破棄し、調整を行います。
データを送信するとエラーを示す-1が返され、相手が通常の方法で接続を閉じると、戻り値は0になり、残りは正常に送信されたバイト数になります。ユーザーバッファーbufのデータは、send関数を介して送信されるときに必ずしも送信されるとは限らないため、次の操作(前のデータを再送信する必要があるかどうか)のためにsend()関数の戻り値を確認する必要があります。送信バッファーがいっぱいのときに送信関数を呼び出すと、ENOBUFSエラータイプが返されます。ソケットが閉じられると、EPIPEエラータイプが返されます。
readvとwritevおよびrecvmsgとsendmsgの違い
これらの関数は、複数のバッファデータを受信するために使用できます。このうち、readvとwritevは、パラメーター内のバッファーベクトルの数を直接指定します。ここで、vectorはベクトルベクトルの配列です。データ構造は次のように定義されます。
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
使用時には、iovcのiov_baseの長さを指定する必要があり、値はiov_lenに格納されます。
recvmsgとsendmsgのパラメーターはより複雑で、構造体msghdrは次のように定義されています。
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags (unused) */
};
Msg_nameは送信元アドレスを表します。これは、struct sockaddrへのポインターであり、msg_iovとreadvは同じ構造を持っています。msg_iovlenは、msg_iovバッファーの数を示します。
ウェルカムメッセージの詳細な違いがある場合、readv / writevおよびrecvmsg / sendmsgが使用されるのはどのような場合ですか?
IOモデル
IOメソッドには、ブロッキングIO、非ブロッキングIO、IO多重化、信号駆動、非同期IOなどが含まれます。ブロッキングIOは最も一般的なタイプのIOです。このモデルをデータ受信に使用すると、プログラムはデータが到着しないまで待機します。カーネルはIOをブロックする代わりに、すべての要求をブロックするのではなく、データが到着したかどうかに関係なくすぐに戻ります。
IO多重化
IO多重化モデルを使用して、待機するときにタイムアウト期間を追加できます。タイムアウト期間が到来しない場合、それはブロッキング状況と一致します。スーパーマーケットの時間に到着し、データが受信されない場合、それは戻り、待機しなくなります。プロセスは次のとおりです
。関数には、select関数とpoll関数が含まれます。
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
このうち、readfds、writefds、exceptingfdsは、読み取り可能、書き込み可能、および異常であるかどうかに関係するファイル記述子セットです。関係のないものは、NULLに設定できます。Linuxでは、fd_setは大きなバイト配列であり、各ファイル記述子は1ビットのみを占有します(記述子0は0番目のビットを占有します...)。FD_CLRを使用してファイル記述子fdの対応するビットをクリアし、FD_SETを使用してファイル記述子fdの対応するビットを設定し、FD_ZEROを使用してファイル記述子セットのすべてのビットをクリアできます。FD_ISSETを使用してファイル記述子fdを検出しますファイル記述子セットに設定するかどうか。nfdsパラメータは、これら3つのファイル記述セットの中で最大のファイル記述子の値に1を加えた値です。この数は、記述子の範囲について懸念があることをカーネルに実際に通知します。FD_SETSIZE(1024)を使用できますが、これはリソースを浪費します。最後のパラメーターはタイムアウト設定です。
select関数には3種類の戻り値があります。return-1はエラーが発生したことを示し、信号はリッスン中にキャプチャされました。return0はタイムアウトを示し、正の数はファイルの準備ができていることを示し、FD_ISSETを使用して準備ができているファイルを判別します。select関数が戻ると、条件を満たさないファイル記述子fdに対応するビットがクリアされるため、再度使用する場合は再度追加する必要があります。
io再利用には、同じ機能を実現するポーリング関数もあります。その中に、各条件の記述子セットを作成する代わりに、pollはpollfd構造の配列を作成します。各メンバーは記述子番号を指定し、関心のある条件を説明してください。タイムアウト期間はミリ秒単位です。戻り値はselectと同じです。2番目のパラメーターはpollfdの数です。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
選択関数とポーリング関数は信号の影響を受けるため、関数の実行中に信号の干渉を回避するために、2つの関数pselectとppollを使用して信号の影響をシールドできます。
非同期IOモデル
最初のいくつかのioモードではあまりにも受動的であり、ioを再利用するには一定の時間が必要です(本質的にはポーリングで表示します)。ここでは非同期IOを使用でき、気になる条件が満たされるとシグナルで通知されます。あまり待つ必要はありません。原理は次のとおりです
。Linuxでは、非同期io信号はSIGIOとSIGURGの組み合わせです。SIGIOは一般的な非同期IO信号であり、SIGURGは、ネットワーク接続の帯域外データが到着したことをプロセスに通知するためにのみ使用されます。
非同期IO信号を受信するには、次の3つの手順を実行する必要があります。
- signalまたはsigactionを呼び出して、SIGIOシグナルのシグナルハンドラーを確立します。
- コマンドF_SETOWNを使用してfcntlを使用し、シグナルを受信するためのプロセスIDまたはプロセスグループIDを設定します。
- コマンドF_SETFLを使用してfcntlを呼び出し、O_ASYNCファイルステータスフラグを設定します
ステップ3は、端末またはネットワーク記述子でのみ実行できます。非同期ioには大きな制限があります。それらはすべてのファイルタイプで使用できるわけではなく、使用できる信号は1つだけです。複数の記述子で非同期IOを実行する場合、プロセスが信号を受信したときに、この信号がどの記述子に対応するかがわかりません。