つまり、400 バイトのレコードを send() し、400 バイトの Buffer 長を指定して recv() を行ったとしても、1回の recv() で、400 バイト全てのデータがとれる保証はありません。
800 バイトの Buffer 長を指定して recv() を行うと、2回分のレコードが1回の recv() で受信できるかもしれません。
固定長データの送受信ならば、受信したあとに足りないバイト数分の recv() を再度実行するようにコーディングしてください。
可変長データの送受信ならは、データの終わりを判別出来るような文字を最後につけましょう。 ( TCPIP の多くのアプリケーション(ftp,telnet,smtp...)は、レコードの終わりを CRLF で判別しています。)
spawn()関数を使用したプロセスでは、親プロセスと子プロセス間で、ファイルのディスクリプターを共有できます。
共有されたディスクリプターは、共有しているプロセスがあると、close()してもクローズ処理が延期されます。
上記のケースでは、親プロセスがソケットのディスクリプターを共有しているので、子プロセスでのclose()はすぐには処理されません。 そのため、ローカル・ソケットから FIN が送られず、上記の様な現象になります。共有している親プロセスの終了と同時にすべての ソケットがクローズされるでしょう。
そのため、このアドレスを使用すると
というエラーが発生してしまいます。
この辺の実装はプラットフォーム毎に差異のあるところで、特に他のプラットフォームからの移植では問題となるでしょう。
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のマイクロコードレベルで拒否されてしまっているのでしょう。
現状のAS/400の使われ方は商用サーバーとしての位置づけであり、上記の様な制限をかけること、あるいは、 サポートしていないことは、当然なのかもしれません。
特殊アドレスを必要とするのは、主に実験的なネットワークテストであり、そのプラットフォーム OS で AS/400 が選ばれることは ことはまずないでしょう。この様な用途にはやはり、unix (特にカーネルソースが公開されている unix )が最適です。 なんでもありですもんね。
シャットダウン関数はクローズ関数と違い、送信/受信を別々に禁止出来ます。
クローズしたい側は、まずソケットの送信を禁止し自分の送信バッファーを空にします。その後、相手からのクローズ要求(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 の戻りを期待しています。
1024以降を使用する場合も、他のソケットアプリケーションとバッティングしないように気をつけましょう。
サービステーブルに特定の名前で PORT 番号を登録し、プログラムで検索して PORTを使用するように作成すれば PORT 番号の管理、変更が容易になるでしょう。PORTを検索する関数は、 getservbyname() / getservbyport() になります。
AS/400は fork()関数をサポートしていません。その変わりは、spawn()関数になります。
スレッドはサポートされていますが、カーネルスレッドのサポートはバージョンV430以降になります。
このバージョン以前は、CPAツールキットが必要です。
また、これらの機能はバージョンごとに拡張されているので、ターゲットリリースを指定した コンパイルでは動作しないものと思っていたほうがよいでしょう。少なくとも利用するバージョンの OS上でのコンパイルが必要です。内容によっては、ソースコードの修正や、コンパイルの方法も 異なります。
(注)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では、スプールファイルに上記が出力されます。
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()を実行すると よいでしょう。
if((rc = getpeername(sd,(struct sockaddr *)&saddr,&saddrlen)) < 0){ perror("getpeername() failed"); exit(-1); }
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 コネクションの状態が、TIME_WAIT (時間待機)以外のステータスとなり、実際の PORT クローズの時間が長くなってしまいます。
つまり、read()/recv()でブロックしている場合に、単に相手がデータを送ってこないのか、あるいは、ダウンしたのか 知ることができません。
相手のダウンを知る方法の一つは、SO_KEEPALIVE オプションを設定することです。だだし、AS/400のデフォルトは 120分に設定されています。デフォルトの値を変更することも可能ですが、TCP通信全体に影響してしまうので、 システム・デフォルトの設定変更には、十分注意してください。できるだけ、socket オプションで、SO_KEEPALIVE を設定 した方が無難でしょう。
もう一つの方法は、プログラムでソケットをノンブロックに設定し、select 関数などで TIMEOUT を実装することです。 相手から、一定の時間応答がない場合は、相手かダウンしたものとして close()をしてしまう方法です。 一定時間の判断は、通信に要する時間+相手がデータを作成する時間 などを考慮して、最適な時間としてください。
write()/send()の場合は、簡単です。相手が ack を帰さないので、再送タイムアウト後に、関数呼出しが失敗するでしょう。 だだし、この再送タイムアウトは、数分を必要とするのが普通です。
ソケットへの書き出しは、いったん Buffer 置かれ Buffer 上のデータをいつ伝送するかは TCPIP が決めます。
Bufferの送り出しの強制は不可能ですが、事前に TCP_NODELAY オプションを設定して TCP に対して遅延なく パケットを送りだす様に指示することが可能です。多くの場合は、このオプションの設定により、問題は解決 するでしょう。
つまり、recv() はソケット専用であり、flagsの指定が可能ですが、read() はファイルの読み込みにも使用出来ます。
インクルードファイルも read() は <unistd.h> なのに対し、recv() は <sys/socket.h> です。
recv()関数で指定可能なflags