2

最初のスタックオーバーフローの質問! 探しました...約束します。私は自分の苦境に対する答えを見つけていません。私は...控えめに言っても深刻な問題を抱えています。簡単に言うと、モバイル アプリケーション (Android アプリと iOS アプリ) がソケットを使用してサーバーと通信し、データをデータベースに送信するゲームのインフラストラクチャを開発しています。バックエンド サーバー スクリプト (私は BES またはバック エンド サーバーと呼んでいます) は、数千行のコードです。基本的に、ソケットへの着信接続を受け入れてフォークする main メソッドと、ソケットから入力を読み取り、それをどう処理するかを決定するメソッドがあります。ほとんどのコードは、データベースとの間でデータを送受信し、それをモバイル アプリに送り返すメソッドにあります。私が追加した最新の方法を除いて、それらはすべて正常に機能します。このメソッドは、データベースから大量のデータを取得し、JSON オブジェクトとしてエンコードしてモバイル アプリに送り返します。モバイル アプリは、JSON オブジェクトからデータをデコードし、必要な処理を行います。私の問題は、このデータが非常に大きく、ほとんどの場合、1 回のデータ書き込みでソケットを通過できないことです。したがって、受信しようとしている JSON オブジェクトのサイズをアプリに通知する 1 つのデータ書き込みをソケットに追加しました。ただし、この書き込みが発生した後、次の書き込みで空のデータがモバイル アプリに送信されます。受信しようとしている JSON オブジェクトのサイズをアプリに通知する 1 つのデータ書き込みをソケットに追加しました。ただし、この書き込みが発生した後、次の書き込みで空のデータがモバイル アプリに送信されます。受信しようとしている JSON オブジェクトのサイズをアプリに通知する 1 つのデータ書き込みをソケットに追加しました。ただし、この書き込みが発生した後、次の書き込みで空のデータがモバイル アプリに送信されます。

奇妙なことに、JSON オブジェクトのサイズを送信するこの最初の書き込みを削除すると、JSON オブジェクトの実際の送信は正常に機能します。それは非常に信頼性が低く、一度の読み取りですべてが送信されることを願っています. 状況にさらに奇妙な点を加えると、2 回目の書き込みで送信されるデータのサイズを膨大な数にすると、iOS アプリはそれを適切に読み取りますが、それ以外の場合は空の配列の真ん中にデータが格納されます。

世界で何が起こっているのですか?どんな洞察も大歓迎です!以下は、サーバー側での 2 つの書き込みコマンドの基本的な抜粋です。

このスクリプトの他のどこでも読み取りと書き込みは正常に機能することに注意してください。ただし、2 つの書き込み操作を続けて行うのはこの場所だけです。

サーバー スクリプトは、バークレー ソケットを使用するネイティブ C の Ubuntu サーバー上にあり、iOS は AsyncSocket というラッパー クラスを使用しています。

int n;
//outputMessage contains a string that tells the mobile app how long the next message
//(returnData) will be
n = write(sock, outputMessage, sizeof(outputMessage));
if(n < 0)
   //error handling is here
//returnData is a JSON encoded string (well, char[] to be exact, this is native-C)
n = write(sock, returnData, sizeof(returnData));
if(n < 0)
   //error handling is here

モバイル アプリは 2 つの読み取り呼び出しを行い、outputMessage問題なく動作しますが、非常に大きな数returnDataに上書きしない限り、常に空のデータの集まりにすぎませんsizeof(returnData)。その場合、iOS は空のデータの途中でデータを受信します。オブジェクト (正確には NSData オブジェクト)。AsyncSocket クラスの iOS 側で使用するメソッドは、最初の書き込み呼び出しから受け取った長さまでデータを読み取ることに注意することも重要です。したがって、たとえば 10000 バイトを読み取るように指示すると、そのサイズの NSData オブジェクトが作成され、ソケットから読み取るときにバッファーとして使用されます。

どんな助けでも大歓迎です。よろしくお願いします!

4

4 に答える 4

6

それは非常に信頼性が低く、一度の読み取りですべてが送信されることを願っています.

TCP でのプログラミングを成功させる鍵は、アプリケーション レベルでデータの TCP "パケット" または "ブロック" の概念がないことです。アプリケーションは、境界のないバイトストリームのみを認識します。送信側でデータを呼び出すとwrite()、TCP レイヤーは、複数のブロックを結合するなど、適切と思われる方法でデータを細かく分割することを選択する場合があります。

10 バイトを 2 回書き込み、5 バイト、次に 15 バイトを読み取る場合があります。または、受信者が一度に 20 バイトをすべて見ることになるかもしれません。できないことは、送信したバイトの一部のチャンクが同じチャンクで相手側に到着することを「希望」することです。

特定の状況で発生する可能性があるのは、2 つの連続した書き込みが 1 つに結合され、読み取りロジックがそれを処理できないことです。

于 2011-04-12T04:22:02.213 に答える
1

すべてのフィードバックに感謝します! みんなの答えをソリューションに組み込みました。の代わりにiovec使用する構造体をソケットに書き込むメソッドを作成しました。私がiOS側で使用しているラッパークラスであるAsyncSocket(ちなみに、これは素晴らしいです...ここで確認してください-> AsyncSocket Google Code Repo)は、iovecの受信を問題なく処理し、舞台裏で明らかにすべてのデータを正しく読み取るために、私の側で追加の作業を行う必要はありません。AsyncSocket クラスは、構造体で指定されたすべてのデータを受け取るまでデリゲート メソッドを呼び出しません。writevwritedidReadDataiovec

改めまして、皆様ありがとうございました!これは大いに役立ちました。文字通り一晩で、私が 1 週間直面していた問題に対する回答を受け取りました。今後、stackoverflow コミュニティに参加できることを楽しみにしています。

ソリューションのサンプル コード:

//returnData is the JSON encoded string I am returning
//sock is my predefined socket descriptor
struct iovec iov[1];
int iovcnt = 0;
iov[0].iov_base = returnData;
iov[0].iov_len = strlen(returnData);
iovcnt = sizeof(iov) / sizeof(struct iovec);
n = writev(sock, iov, iovcnt)
if(n < 0)
   //error handling here
while(n < iovcnt)
   //rebuild iovec struct with remaining data from returnData (from position n to the end of the string)
于 2011-04-14T15:22:44.633 に答える
0

write_completeバッファをソケットに完全に書き込む関数を実際に定義する必要があります。の戻り値を確認してくださいwrite。これも正の数である可能性がありますが、バッファのサイズよりも小さいです。その場合write、バッファの残りの部分を再度取得する必要があります。

ああ、使用sizeofもエラーが発生しやすいです。したがって、上記のwrite_complete関数では、指定されたサイズを出力して、期待するサイズと比較する必要があります。

于 2011-04-12T04:17:14.007 に答える
-1

理想的には、ヘッダー(サイズ)とデータをアトミックに書き込みたいサーバー上で、writev()複数のスレッドが同じソケットに同時に書き込む可能性がある場合は、スキャッター/ギャザー呼び出しを使用してそれを行います。書き込み呼び出しのミューテックス。

writev()また、戻る前にすべてのデータを書き込みます (ブロッキング I/O を使用している場合)。

クライアントでは、バッファーの長さを読み取り、すべてのデータが受信されるまでループ読み取りを行うステート マシンが必要になる場合があります。これは、大きなバッファーが断片化され、さまざまなサイズのブロックに入るためです。

于 2011-04-12T04:27:43.260 に答える