• ベストアンサー

socket: recvはいつ,どれだけ受け取るのか?

 現在,参考書にしたがってC++でソケットプログラミングを書いています.  sendとrecvを非同期にするために,本では select関数やWSAAsyncSelect関数などを利用していて,実際,本のとおりに書いて上手く動いています.  ここで伺いたいのですが,recvは,どうやって「データが届いたか」を知るのでしょうか.  同期ならば,トランシーバでの会話のように送信側が「どうぞ」といって送受信を交代させることができますが,非同期ならばそれができません.  NICとかが,プログラムに「届いたぞ!( or これから届くぞ!)」と教えてくれるのでしょうか.あるいは逆に,プログラムがNICに「届いてる?」と聞いているのでしょうか.仮に,ここに書いたような方法で届いたことが分かったとしても,どれくらい受け取ればいいかは分かりません(それも併せて教えてもらっているのでしょうか.データを送るときには,どれだけ送ればいいか分かりますよね.受信するときはどうしてるのかを知りたいと思っています).

質問者が選んだベストアンサー

  • ベストアンサー
  • chirubou
  • ベストアンサー率37% (189/502)
回答No.1

Linux しか知らないので Linux で説明をします。 NIC が通信パケットを受け取ると割り込みが発生し、CPU は割り込みを受け付けて、対応するデバイスドライバを起動します。この時、ドライバはソケットバッファと呼ばれる構造体にパケットの中身をコピーして、Linux カーネルの本体に渡し、そこで TCP 等の上位プロトコル処理が行われます。 一方、ユーザプログラムの方は、 select() なり read() で待っている訳ですが、OS はもちろんプロセスが何を待っているかを知っているので、対応する待ちの条件が満たされると、この場合は select() や read() が、抜けてくる(return する)訳です。 という事で、ユーザのプログラムは select() なり read() なりで受信データを「待つ」ことが必要です。もちろん select() や read() が呼ばれた時点で既に受信しているのならば、それらは直ぐに帰ってきます。read() や recv() はデータが届いた事を知る、というよりは、届いているかチェックして、まだ届いていなければ届くまで待つ(read() が抜けてこない)という処理になります。また NIC とユーザプログラムが直接やり取りをするのではなく、間にバッファがあって、対応するソケットのデータがある(受信済み)/ないか(未受信)、という問い合わせを行っているだけです。 ソケットの場合、データの送受信は非同期であり、送受信のタイミングのずれは(ソケット)バッファである程度吸収されます。もちろん、送受信バッファが満杯になった場合は流量制御が働いて、結果的に送信側の write() や send() が待ちに入ることになります。 Linux (Unix) のソケットの受信では、read() 等で指定されたバッファが常に満杯で返されるとは限らない設計になっています。つまり、その時に受信しているデータを返すだけなので、read() で返されたバイト数を必ず見ないと間違った動きになるので注意してください。

その他の回答 (1)

  • m_mik
  • ベストアンサー率26% (31/117)
回答No.2

recv自身には、「データが届いた」か知る機能はありません。 何をするのかと言いますと、「届いているデータをバッファから取り出す」ことしかできません。 もしメッセージが届いていなかった場合には、「届くまで待つ」状態になります。(ブロッキングモード) また、届いていなかった場合には即座にエラーとなることもあります。(ノンブロッキングモード) では、どうやって届いたのかを知るのかと言いますと、サンプルにあったselectなどで、「データが届いているのか?」をバッファに対して問い合わせているのです。 NICは受信したデータを内部のバッファへと格納します。 Select関数により内部バッファにデータがあるのかを確認します。 recv関数により内部バッファからユーザのバッファへデータを転送します。 (ブロッキングモードの場合には、recvが内部バッファにデータが格納されるまで待ちます) 次にどれだけのデータを受信すればいいのかですが… 適当な量のバッファを用意して、recvに入れてもらうことになります。 ただし、recvでは「実際にバッファに入れることができたデータサイズ」を返します。実際に通信で取得できたデータサイズとは異なります。 例えば、recvで10kのバッファを用意して10kのデータを受信しようとしても、2k、4k、2kと受信が行われる場合もありえます。 この場合には、3回recvを呼び出さないといけないことになります。 どれだけのデータを通信するのかは、あらかじめ決めておくか(HTTPなどでは、ヘッダ中にデータの長さが書いてあるなど)、データがなくなる(コネクションを切断する)まで受信するという風に決めておく必要があります。

関連するQ&A

  • winsock のrecvデータの終わりを知るには

    Winsockの非同期プログラムで サーバーからクライアントへ,100MB前後のデータを送るプログラムを書いています. サーバーでsend()して,クライアントのcase FD_READ のイベント応答としてrecv()を繰り返すプログラムです. サイズが大きいので何度もrecev()して,データを結合していくことになりますが, 送信データの最後を知るエレガントな方法がないかと思っています. すべてが送り終わったら空send()をして,ゼロサイズのデータを送ることができればよいのですが. よろしくお願いします.

  • C言語のsendとrecv

    C言語でクライアントとサーバのプログラムを作りたいのですが、かなりの初心者なので、とりあえず練習用として、クライアント側に整数を入力すると、サーバ側でその整数を二乗して、結果をクライアント側に返すという簡単なプログラムを作ろうと思いました。 ソケットの作成、通信ドメインの指定等は、参考書を見ながら(というか、ほとんど丸写しみたいな感じなんですが…)、なんとかできたんですが、sendとrecvで整数を送受信するにはどうすればいいのかが分かりません。 私が持っている参考書には、 int send(SOCKETS const char FAR *buf int len int flags); int recv(SOCKETS char FAR *buf int len int flags); とあるのですが、const char FAR *buf、int len、char FAR *bufのところがよく分からないのです。 例えば、a=1000を送信したい場合、aはchar型ではないのでsendでは送れないのですか? ほんと初心者ですいませんが、どなたか教えてください。 よろしくお願いします。

  • Linuxでsocket接続をしているのですが・・・。

    簡単なポートフォワードのプログラムを作ろうと思って、 下のようなプログラムを組んでみました。 struct sockaddr_in cli ; fd = SetupForClient(元IP, proto, port) ; で、クライアントから接続を待って clen = sizeof(cli) ; recvfrom(fd, recv_data, sizeof(recv_data), 0,(struct sockaddr *)&cli, &clen) ; でデータを受信して fd2 = ConnectToServer(先IP, proto, port) ; write(fd2, recv_data, sizeof(recv_data)) ; で反対側のNICにデータを書き出す・・・。 という基本的なものなのですが、書き出す側に接続しようとすると Connectの関数内で Address already in use というエラーが出てしまいます・・・。 fdを解放していないのかとも思ったのですが、再起動してもダメでした。 どういうことなのでしょうか・・・?

  • SocketのSend関数でのCLOSEの検知 [Linux]

    Linux環境でSocket(dm:PF_INET,type:SOCK_STREAM)を使用しての、 Client&ServerプログラムをCで作成しているのですが、 そこでのSend関数の使い方についてご助力ください。 Client&Serverプログラムは下記のような動きをします。 [Client] ServerへConnectした後、複数のDataを数秒間隔でServerへ 送信(send関数使用)します。受信(recvやread関数等)は、 一切行いません。 [Server] ClientからのConnectを受け付けた後、Clientから受信(recv関数 使用)したDataを標準出力へ表示する。送信(sendやwrite関数 等)は、一切行いません。 さて、ここでもしClientプログラムがCloseを発行したり、マシン DOWN等の理由でConnectionが切断され、Server側のSocketが CLOSE_WAIT状態になった場合、Bufferに溜まっていたDataを すべて受けきった後、recv関数が0を返してくれるので 相手が終了したことがわかります。 ここからが質問のMainです。 では、もしServerプログラムがCloseを発行したり、マシン DOWN等の理由でConnectionが切断され、Client側のSocketが CLOSE_WAIT状態になっても、CLOSE_WAIT直後のsend関数が なぜか正常に処理されてしまいます。無論このDataは、 Server側は受け取りません。この次のsend関数実行時に EPIPEが返ってくるので、ここでようやくSocketが切断された ことが判ります。 これを何とかCLOSE_WAIT状態になった直後から、send関数で 切断を検知できるようにできないでしょうか。 よろしくお願いします。 以上

  • 配列とポインタについて

    こんばんわ。 WinSockを利用してネットワークプログラミングを行っています。 以下のようなプログラムを作成したのですが、実行できません。 以下のプログラムは質問箇所を抜き出したものです。 [プログラム] int Receive(u_short portNo) { char recv_Buf[1025],recv_Buff[1025]; size = recvfrom(s2, recv_Buf, (int)sizeof(recv_Buf) - 1, 0, (SOCKADDR *)&from, &fromlen); recv_Buff = recv_Buf; Sender(portNo,szServer,recv_Buff); } int Send(unsigned short portNo,char *szServer,char *recv_Buff) { sendto(s1, recv_Buff, (int)strlen(recv_Buff), 0, (LPSOCKADDR)&addrin1, sizeof(addrin1)); } このプログラムは、Receive関数内のrecvfrom関数で1024Byte(recv_Buf[1025])受信したデータをSend関数内のsendto関数で送信するというプログラムです。 recv_Buf = recv_Buffにてエラーが発生します。なにか解決策はりますでしょうか? また、Receive関数およびSend関数は何回も呼ばれるのですが、配列は初期化する必要があるのでしょうか? よろしくお願いします。

  • ”select”,”FD_ISSET”という関数はMicrosoft Visual C++ 2008 Express Editionで使えますか?

    今、Microsoft Visual C++ 2008 Express Editionでソケット通信のサーバ、クライアントプログラムを作りたくていろいろ調べていて、特に複数クライアントが接続できるサーバを作りたいと思っています。 ただ、どうしてもrecv関数に入ると、1台の端末からのデータ受け取りでステップが停止してしまって、通信を占有してしまい、他のクライアントからのデータを受け付けることができないので、どうすればよいのかと思い、 ググッていたら http://x68000.q-e-d.net/~68user/net/c-echo-2.html このページにselect関数とFD_ISSET関数というのがあって、select関数の引数にデータを設定すれば、FD_ISSET関数の戻り値で作成したソケットに読み取り可能なデータがあるのかを検知できて、検知したらrecv関数でとれるので、これでrecv関数での立ち止まりがないので、複数のクライアントからのデータを行えるというプログラムを見つけました。 ただ、このFD_ISSET関数、select関数はまさにマルチスレッドのやり方だと思うので、Microsoft Visual C++ 2008 Express EditionではMFCがないとのことなのですが、使用することはできるのでしょうか?

  • ソケットプログラミング

    こんにちは。 ソケットプログラミングを勉強していて、音声を送りあえるIP電話らしきものを作ろうとしています。音声のやりとりは何とかできていますが、同時に文字列も送れるようにしたいです。ソケットをもう一つ作ればいいのではないかと思っていますが、実際に書いてみると、recv()の部分がうまく書けません。 音声データの送受信の部分は: while(1){ n=recv(sock,data1,N,0); //受信 if (n== -1){perror("recv");exit(1);} if(write(dsp, data1,n)==-1){perror("write"); exit(1);} //スピーカーから出力 n=read(dsp,data,N);  //マイクから入力 if (n== -1){perror("read");exit(1);} if(send(sock,data,n,0)==-1){perror("send");exit(1});} //送信 } 送信の時は n=read(0,tdata_2,N); send(sock_2,data_2,n,0); のように標準入力から文字列を読み込んで、data_2に格納し、新しく作ったソケットsock_2で送ればいいのですが、受信の時はどうしたらいいでしょうか。recv()をつかって、文字列と音声のデータを違うソケットで受け取って、違う配列に格納する方法があるでしょうか。 つまり、このような感じ(?): n1=recv(sock1,data1,N,0); //音声 n2=recv(sock2,data2,N,0); //文字列 とても悩んでますので、よろしくお願いします!!

  • IO-Socketで…

    最初に開かれた1つのソケットで、 サーバーから不定期に送られてくるデータを処理→表示しつつ、 ユーザーから入力があった場合にそのソケットへ送信するといったメッセンジャーを作ろうと思っているのですが、「入力待ちと受信待ちを同時に進める」という事がどうも上手くいってくれません… use threads;を使い、送信用・受信用と分けたのですが、やはりどちらか一方が止まってしまいます…。 どの様に書いたらよいのでしょうか… 何方かご教授下さい…。 use threads; use IO::Socket; ($test_socketを生成) sub send{ while($input = <STDIN>){ chomp($input); print $test_socket "$input"; } sub recv{ while(1){ if($receive = <$test_socket>){ print "$receive\n"; } } } my $sendthread = threads->new(\&send); $sendthread->join(); my $recvthread = threads->new(\&recv); $recvthread->join();

  • recv関数でフリーズしてしまう

    現在Winsockを用いた簡単なパケット送受信のソフトを作成しています。 開発環境はVisual Studio2008で、ダイアログベースで作成しています。 パケットの受信側の処理なんですが、いつパケットが来てもいいように、 Ontimerで、定期的に受信処理をしようと思っています。 そこでOntimer内に以下のようにプログラムしたところ、コンパイルエラー はないものの、数秒経つと応答なしとなりフリーズしてしまいます。 原因を探ったところrecv関数が原因で、recv関数をコメントアウトした ところ、フリーズはしなくなりました。また、エラー処理は省略していま すが、ソケットの作成失敗などはありませんでした。 なぜrecv関数でフリーズが起きてしまうのか、原因が分かる方は いらっしゃいますか? OnTimer(UINT nIDEvent){ WSAData wsaData; SOCKET sock; struct sockaddr_in addr; char buf[2048]; WSAStartup(MAKEWORD(2,0), &wsaData); sock = socket(AF_INET, SOCK_DGRAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.S_un.S_addr = INADDR_ANY; bind(sock, (struct sockaddr *)&addr, sizeof(addr)); memset(buf, 0, sizeof(buf)); recv(sock, buf, sizeof(buf), 0); closesocket(sock); WSACleanup(); }

  • send-recvで複数データの送受信

    初歩的な質問で恐縮ですがよろしくお願いいたします。 send側 :WindowsVista VC6 recv側 :Linux2.6.18-at9 Debian PowerPC でunsigned longのデータ数千個を順次送受信するプログラムを組んでいるのですが不調です。 ともにBlockingモードで動作しているのだから双方にWhileループを組めば特段のHandShakeは不要で受信側所定バッファに逐次取り込めるのだと思っていました。しかし: 1.受信側ループにprintf、sleep(1)等を入れないと受からない。  なお受信側はMainとは別のスレッドにしています。 2.毎回内容ゼロのデータがもう一つ加わってしまう。  recvが毎回データ到着までBlockつまり待ちにしていると期待したのですが、2回通り抜けたような効果があり、各データにゼロデータがもう一行付加されてしまう。つまりデータ量が2倍になる。 プログラム: 送信側: SOCKET s; unsigned long dataBuf char buf[20]; int ok; while ( count < DATANO) ){  fread(&dataBuf, sizeof(dataBuf),1,fp);      // ファイルより読込み sprintf(buf, "%d", dataBuf); ok = send(s, buf, sizeof(buf), 0); // Blocking Mode ? if(ok==SOCKET_ERROR){ printf("Command送信不良"); exit(1); } count++; } 受信側: void* dataReceiveThread(void* pParam) {  char buf[10];  int recvSize, count = 0;  unsigned long val, memBuf[4096];  while(1){   memset(buf, 0, sizeof(buf));   recvSize = recv(conn_fd, buf, sizeof(buf), 0); // Blocking Mode   if(recvSize == 0){    printf("conn_fd broken by Host\n");    close(conn_fd);    break;   }   else if (recvSize == -1) {    perror("recv");    exit(EXIT_FAILURE);   } val = atol(buf); ⇒ printf("val %x\n", val);   if (count < DATANO){ memBuf[count] = val; else break; count++; } このプログラムは初回のデータは問題なく受信できます。 以降のデータは⇒でループ速度を下げないと受信できません。しかしこれは仕様に合いません。 ただしデータ量が2倍になってしまうのでFlipFlopを入れてループを間引いて強引に辻褄合わせをしたところデータはそれらしく受信できます。しかし指定回数ループできません。 recvのBlock機能を誤解しているのかもしれない考え、selectを入れるなどしたのですが解決しません。send-recvの基本仕様を理解していないためと思われます。 ご教示願えれば幸いです。

専門家に質問してみよう