Socket FAQ ( Frequently Asked Questions )

[Return]


【掲載日】 1999/10/11

【質問】

たまに、受信したレコードの最後が途中で切れていることがあります。どうしてでしょうか?

【回答】

SNA であればフレーム単位の伝送なのでこのようなことは起こりませんが、TCPIP はバイト単位で伝送を行います。
そのため、伝送中にレコードが複数のパケットに分割され、受信したバッファーにレコードの途中までしか入っていない事が あります。

つまり、400 バイトのレコードを send() し、400 バイトの Buffer 長を指定して recv() を行ったとしても、1回の recv() で、400 バイト全てのデータがとれる保証はありません。

800 バイトの Buffer 長を指定して recv() を行うと、2回分のレコードが1回の recv() で受信できるかもしれません。

固定長データの送受信ならば、受信したあとに足りないバイト数分の recv() を再度実行するようにコーディングしてください。

可変長データの送受信ならは、データの終わりを判別出来るような文字を最後につけましょう。 ( TCPIP の多くのアプリケーション(ftp,telnet,smtp...)は、レコードの終わりを CRLF で判別しています。)


【掲載日】 1999/10/5

【質問】

サーバーアプリケーションで使用する子プロセス(BCI)などでソケットをcloseしても、終了待機でソケットが残ってしまう。 同じロジックで、バッチ/対話ジョブだと正常にクローズできる。なぜ?

【回答】

「終了待機」の状態は、ローカルのソケットがクローズ処理(FIN送信)を行っていないと発生します。

spawn()関数を使用したプロセスでは、親プロセスと子プロセス間で、ファイルのディスクリプターを共有できます。

共有されたディスクリプターは、共有しているプロセスがあると、close()してもクローズ処理が延期されます。

上記のケースでは、親プロセスがソケットのディスクリプターを共有しているので、子プロセスでのclose()はすぐには処理されません。 そのため、ローカル・ソケットから FIN が送られず、上記の様な現象になります。共有している親プロセスの終了と同時にすべての ソケットがクローズされるでしょう。


【掲載日】 1999/10/3

【質問】

宛先アドレスに 255.255.255.255 のアドレスを指定すると、呼出しエラーになってしまいます。

【回答】

255.255.255.255 はブロードキャストアドレスになりますので、他のアドレスと扱いが異なります。

そのため、このアドレスを使用すると

というエラーが発生してしまいます。

この辺の実装はプラットフォーム毎に差異のあるところで、特に他のプラットフォームからの移植では問題となるでしょう。

AS/400では、255.255.255.255のアドレスは、以下のソケットオプションの設定により許可されます。

このオプション設定を行うと、上記エラーは発生しなくなりますが、実際にネットワークに出力されるデータが他のプラットフォーム の解釈と同じとはかぎりません。

AS/400は INADDR_BROADCAST(255.255.255.255 / 0xFFFFFFFF)宛先の解釈に、そのソケットの bind IPの有無/ 使用するインターフェースのサブネット/ソケットのタイプ/同報通信の有無 などからネットワークに送出するデータを作成します。

ですから、AS/400の場合、ネットワークに送出されるデータが、実際に255.255.255.255のIPアドレスを持つことはまずないでしょう。

多くの場合、送出を許可されない、または、宛先IPアドレス 10.255.255.255 宛先MACアドレス 0xFFFFFFFF をもつパケットの 送出という結果になるでしょう。
(10 はネットワークアドレス + 255.255.255 はブロードキャスト = 10 のネットワーク全体へのブロードキャストの意味)

あるいは、別の手段として、SOCK_RAW を指定したソケットで、IPデータグラムの組み立てを ユーザープログラムで行う方法が考えられます。

しかし、実際に私のテストしたところこでは、上記内容のIPデータグラムを作成して送出しようとするとエラーになります。
エラーの内容からして、おそらくAS/400のマイクロコードレベルで拒否されてしまっているのでしょう。

【私見】

255.255.255.255 や その他の特殊アドレスなどをOSが制限しているのは、ネットワークの安全性という観点で十分な 理由があると思います。もし、実際に 255.255.255.255 の宛先IPにパケットを送ったとしたら、最悪の場合ネットワーク上の すべてのホストから返信があるかもしれません。この状況はおそらくネットワークダウンを引き起こすでしょう。

現状のAS/400の使われ方は商用サーバーとしての位置づけであり、上記の様な制限をかけること、あるいは、 サポートしていないことは、当然なのかもしれません。

特殊アドレスを必要とするのは、主に実験的なネットワークテストであり、そのプラットフォーム OS で AS/400 が選ばれることは ことはまずないでしょう。この様な用途にはやはり、unix (特にカーネルソースが公開されている unix )が最適です。 なんでもありですもんね。


【掲載日】 1999/10/5

【質問】

ソケットの状態が、TIME_WAIT (時間待機)以外のステータスとなり、PORTの開放に時間がかかることがあります。確実に終了する方法は?

【回答】

いろいろな方法が考えられるのでしょうが、ここではシャットダウン関数を使用してのクローズを紹介します。

シャットダウン関数はクローズ関数と違い、送信/受信を別々に禁止出来ます。

クローズしたい側は、まずソケットの送信を禁止し自分の送信バッファーを空にします。その後、相手からのクローズ要求(FIN) を recv関数 で待ちます。受信バッファーが空になり、相手もクローズしたのを確認後に、送信を禁止しクローズ処理します。

以下に、シャットダウン関数を使用したサンプルコード例を掲載します。

【例】

int how;

/* まず、ソケットの送信を停止する */
  how = 1;
  rc = shutdown(sd, how);

/* 相手のクローズを待つ */
  rc = recv(sd, buf ,buflen, 0);
  if (rc > 0) {
    printf("データが破棄されました");
  } 

/* そして、ソケットの受信も停止する */
  how = 2; /* 0(送受信の禁止)でもOK */
  rc = shutdown(sd, how);

/* 安心してクローズ */
  rc = close(sd);

上記サンプルコードでは、[EPIPE]のエラーの発生を無視したいため、戻りコードの確認をあえて省略しています。

上記の recv() のあとに戻り値を確認しているのは、もしデータが正常に受信されてしまった場合に警告をだす為です。 ここの recv()では、-1 or 0 の戻りを期待しています。


【質問】

サーバーアプリケーションで使用する PORT 番号はどのようにして決めればよいのか?

【回答】

TCPIP のアプケーションで予約されている PORT 番号(well-known-port 1〜1023)を避けて、65535までの 整数値から選びます。

1024以降を使用する場合も、他のソケットアプリケーションとバッティングしないように気をつけましょう。

サービステーブルに特定の名前で PORT 番号を登録し、プログラムで検索して PORTを使用するように作成すれば PORT 番号の管理、変更が容易になるでしょう。PORTを検索する関数は、 getservbyname() / getservbyport() になります。


【質問】

AS/400でソケットのプログラムを作成したい、必要なものは?

【回答】

必要なライセンスプログラムは以下のものです。


【質問】

AS/400をサーバーとするプログラムを作成したいのですが、注意点は?

【回答】

TCPのサーバーは通常デーモンとして常駐し、接続要求を受けると子プロセスを起動して処理します。
AS/400のTCPソケットは、ほぼ BSDソケットとコンパチブルなので、通信自体は unix と同じと 考えて構わないでしょう。むしろ unix との差異は子プロセスの起動方法に大きく現れます。

AS/400は fork()関数をサポートしていません。その変わりは、spawn()関数になります。
スレッドはサポートされていますが、カーネルスレッドのサポートはバージョンV430以降になります。 このバージョン以前は、CPAツールキットが必要です。

また、これらの機能はバージョンごとに拡張されているので、ターゲットリリースを指定した コンパイルでは動作しないものと思っていたほうがよいでしょう。少なくとも利用するバージョンの OS上でのコンパイルが必要です。内容によっては、ソースコードの修正や、コンパイルの方法も 異なります。


【質問】

通信エラーでプログラムが終了してしまいました。原因を調べるにはどうすればよいのでしょうか?

【回答】

プログラムで errno をひろってください。errno のエラー番号から内容を調べるには、 QSYSINC ライブラリーの SYS ソースファイルの ERRNO メンバーの中を検索してください。
エラー番号のマクロ定義とコメントが入っています。
コメントから判断するか、あるいは、マクロの名前でソケットのマニュアルを見て下さい。

(注)ERRNOはOS/400バージョンによって異なる事がありますので、同じバージョンのものを 見て下さい。

また、プログラムで perror() 関数を使用すると stderr にエラーの内容が日本語で表示されます。
perror() は、ソケット関数がエラーになった場合のみ呼ばれるようにしてください。

【例】

#include <errno.h>


/* コネクトが失敗した場合に理由を表示する */
  if((rc = connect(sd,(struct sockaddr *)&saddr, sizeof(saddr))) < 0){
    perror("connect() failed");
    exit(-1);
  }

この例の場合、connect()関数が失敗すると、対話型JOBでは

と画面に表示され、バッチJOBでは、スプールファイルに上記が出力されます。


【質問】

文字形式のIPアドレス” xxx.xxx.xxx.xxx "をIPアドレスに変換するにはどうすればよいのでしょうか?

【回答】

inet_addr() 関数を使用します。戻り値が、IPアドレスとなります。
ちなみに、反対は inet_ntoa() です。


【更新日】 1999/10/3

【質問】

ホスト名とIPアドレス” xxx.xxx.xxx.xxx "のどちらでも入力できるようにしたいのですがどうするばよいのでしょうか?

【回答】

いろいろなやり方があるでしょうが、まず、gethostbyname() で、HOST名として検索し、失敗したら gethostbyaddr()で、IPアドレスとして検索します。両方失敗したらエラーとします。

【例】

  hostp = gethostbyname(hostnm);

  if (hostp == (struct hostent *)NULL){  /* 失敗? */
    inaddr = inet_addr(hostnm); 		 /*IPアドレスに変換 */
    hostp  = gethostbyaddr((char *)&inaddr,4,AF_INET);
      if (hostp == (struct hostent *)NULL){
        /* 両方失敗はエラー */
        exit(-1);
      }
  }

上記のサンプルでは、ホスト名またはIPアドレスで、名前引きが出来る事が前提となります。

名前引き出来ないアドレスを TCP で使用すると言った発想が私にはなかったのですが、PCのダイアルアップ ユーザーなどは、DNSをプロバイダーに向けていると、名前引きで電話を掛けにいってしまうそうです。

もし名前引きしたくない場合は、 inaddr = inet_addr(hostnm); で戻り値 inaddr をチェックするだけにし、 正しくないIPアドレスならば-1 が戻りますので、その後、gethostbyname()を実行すると よいでしょう。


【質問】

TCPソケットで接続相手のIPアドレスを調べたいのですが、どのように行えばよいのでしょうか?

【回答】

getpeername()関数を使用して、接続中のソケットディスクリプターを渡すと、接続相手のIPとPORT番号が わかります。

【例】

  if((rc = getpeername(sd,(struct sockaddr *)&saddr,&saddrlen)) < 0){
    perror("getpeername() failed");
    exit(-1);
  }


【質問】

サーバーのプログラムを再立上げしたところ、正常に bind() 出来ずにエラーとなってしまいました。

【回答】

前回 bind() していた port がクローズ出来ていないと思われます。netstat コマンドを利用して、 該当の port 番号を削除してから、立上げ直して下さい。 また、socket 作成時に setsockopt()にて、SO_REUSEADDR オプションを付けておくと、回避できるかもしれません。

【例】


int        sd;
int        on = 1;
int        rc;

      /* SO_REUSEADDR  */
      if ((rc = setsockopt(sd, SOL_SOCKET,
                           SO_REUSEADDR,
                           (char *)&on,
                           sizeof(on))) < 0)
      {
        perror("setsockopt() SO_REUSEADDR failed");
        exit(-1);
      }


【質問】

TCPソケットの終了はどのように行えばよいのでしょうか?

【回答】

TCPソケットの通信は全二重のため、通信の両サイドにて、close() を発行する必要があります。 close() は自分がもうこれ以上送信するデータがない場合に発行しますが、close() 発行後も相手はまだ送信可能状態です。

セッション切断の手順がおかしいと、TCP コネクションの状態が、TIME_WAIT (時間待機)以外のステータスとなり、実際の PORT クローズの時間が長くなってしまいます。


【質問】

相手側のソケットが閉じられたことをどうやって知ることができますか?

【回答】

read() / recv() は、0 を戻すでしょう。write() / send() は、その呼び出しは成功する事があるかも知れませんが、少なくとも、 その次の呼出しで EPIPE が帰るでしょう。


【質問】

セッション確立後に相手がダウンしたのを検出できますか?

【回答】

TCPIP 通信は SNAなどの様にセッション確立後もポーリングなどで状況を確認はしません。 送るべきデータがない場合、TCP セッション上に流れるパケットはありません。 そのためデータ通信中でないセッションは、相手がダウンしていても気がつきません。

つまり、read()/recv()でブロックしている場合に、単に相手がデータを送ってこないのか、あるいは、ダウンしたのか 知ることができません。

相手のダウンを知る方法の一つは、SO_KEEPALIVE オプションを設定することです。だだし、AS/400のデフォルトは 120分に設定されています。デフォルトの値を変更することも可能ですが、TCP通信全体に影響してしまうので、 システム・デフォルトの設定変更には、十分注意してください。できるだけ、socket オプションで、SO_KEEPALIVE を設定 した方が無難でしょう。

もう一つの方法は、プログラムでソケットをノンブロックに設定し、select 関数などで TIMEOUT を実装することです。 相手から、一定の時間応答がない場合は、相手かダウンしたものとして close()をしてしまう方法です。 一定時間の判断は、通信に要する時間+相手がデータを作成する時間 などを考慮して、最適な時間としてください。

write()/send()の場合は、簡単です。相手が ack を帰さないので、再送タイムアウト後に、関数呼出しが失敗するでしょう。 だだし、この再送タイムアウトは、数分を必要とするのが普通です。


【質問】

数バイトのデータを送信するだけなのに、非常に時間がかかってしまいます。

【回答】

多くの場合、write()/send() したデータは、TCP バケットとなって、実際にLANセグメントに送出されるでしょう。 しかし、TCPの場合は、このセグメントへの送り出しが即時である保証はありませんし、bufferの送り出しを強制すること もできません。(SNA の場合は、Buffer FLASH する命令がありますし、Confirm などの処理では必ず Bufferの送り出しが 行われます。)

ソケットへの書き出しは、いったん Buffer 置かれ Buffer 上のデータをいつ伝送するかは TCPIP が決めます。

Bufferの送り出しの強制は不可能ですが、事前に TCP_NODELAY オプションを設定して TCP に対して遅延なく パケットを送りだす様に指示することが可能です。多くの場合は、このオプションの設定により、問題は解決 するでしょう。


【質問】

read() と recv() の違いは何ですか?

【回答】

read() 関数は、ファイルまたはソケットからデータを受信する場合に使用します。
recv() 関数は、ソケットを介してデータを受信する場合に使用します。

つまり、recv() はソケット専用であり、flagsの指定が可能ですが、read() はファイルの読み込みにも使用出来ます。

インクルードファイルも read() は <unistd.h> なのに対し、recv() は <sys/socket.h> です。

recv()関数で指定可能なflags


[Return]