8

こんにちは、私は Python の初心者です。pygame モジュールを使用して、単純な LAN ゲーム (私にとっては単純ではありません) を作成しています。

ここに問題があります - 私は 2 台のコンピューターを持っています (1 台は古い intel Atom ネットブック、もう 1 台は intel i5 NTB)。少なくとも 5 FPS を達成したい (ネットブックは NTB を遅くしていますが、それほどではなく、現在は約 1,5 FPS です)、メインループで recv() 関数を 2 回呼び出すと、それぞれ合計で約 0.5 秒かかります機械。Wi-Fi 信号は強く、ルーターは 300Mbit/s で、約 500 文字の短い文字列を送信します。ご覧のとおり、時間の測定には time.clock() を使用します。

これは、私が通常i5 NTBで実行する「サーバー」コードの一部です。

while 1:
    start = time.clock()
    messagelen = c.recv(4)      #length of the following message (fixed 4 character)
    if " " in messagelen:
        messagelen = messagelen.replace(" ","")
    message = cPickle.loads(c.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
    arrowsmod = message[0]
    modtankposan = message[1]
    removelistmod = message[2]
    for i in removelistmod:
        try:
             randopos.remove(i)
        except ValueError:
            randopossv.remove(i)

    print time.clock()-start


    tosendlist=[]
    if len(arrows) == 0:  #if there are no arrows it appends only an empty list
        tosendlist.append([])
    else:
        tosendlist.append(arrows)
    tosendlist.append([zeltankpos, 360-angle])
    if len(removelist) == 0:   #if there are no changes of the map it appends only an empty list
        tosendlist.append([])
    else:
        tosendlist.append(removelist)
        removelist=[]
    tosend=cPickle.dumps(tosendlist)
    tosendlen = str(len(tosend))
    while len(tosendlen)<4:
        tosendlen+=" "
    c.sendall(tosendlen)   #sends the length to client
    c.sendall(tosend)      #sends the actual message(dumped list of lists) to client

    ...something else which takes <0,05 sec on the NTB

「クライアント」ゲーム コードの一部を次に示します (先頭を反転させただけです - 送信/受信部分):

while 1:
    tosendlist=[]
    if len(arrows) == 0:  #if there are no arrows it appends only an empty list
        tosendlist.append([])
    else:
        tosendlist.append(arrows)
    tosendlist.append([zeltankpos, 360-angle])
    if len(removelist) == 0:   #if there are no changes of the map it appends only an empty list
        tosendlist.append([])
    else:
        tosendlist.append(removelist)
        removelist=[]
    tosend=cPickle.dumps(tosendlist)
    tosendlen = str(len(tosend))
    while len(tosendlen)<4:
        tosendlen+=" "
    s.sendall(tosendlen)   #sends the length to server
    s.sendall(tosend)      #sends the actual message(dumped list of lists) to server

    start = time.clock()
    messagelen = s.recv(4)      #length of the following message (fixed 4 character)
    if " " in messagelen:
        messagelen = messagelen.replace(" ","")
    message = cPickle.loads(s.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
    arrowsmod = message[0]
    modtankposan = message[1]
    removelistmod = message[2]
    for i in removelistmod:
        try:
             randopos.remove(i)
        except ValueError:
            randopossv.remove(i)

    print time.clock()-start
    ... rest which takes on the old netbook <0,17 sec

i5 NTB の 1 台のマシン (ソケット モジュールなし) でシングル プレイヤー バージョンのゲームを実行すると、マップの左上隅に 50 FPS、右下隅に 25 FPS が表示されます (1000x1000 ピクセル マップ)。には 5x5 ピクセルの正方形が含まれています。座標が大きいために速度が遅いと思いますが、それほど信じられません.ところで、マップの右下隅で LAN ゲームとして実行されている間の受信には、ほぼ同じ時間がかかります) Atom ネットブックには 4 ~ 8 FPS があります。

では、なぜこんなに遅いのか教えてください。コンピューターは同期されていません。一方は高速で、もう一方は低速ですが、互いに待機しているわけではありません。最大 0.17 秒の遅延になりますよね? さらに、長い recv 呼び出しは、より高速なコンピューターでのみ行われますか? また、送信/受信機能がどのように機能するか正確にはわかりません。受信に0.5秒かかるのに、送信に文字通り時間がかからないのは奇妙です。プログラムの残りの部分が先に進んでいる間に、sendall がバックグラウンドで送信しようとしている可能性があります。

4

3 に答える 3

5

Armin Rigo が述べたようにrecv、パケットがソケットによって受信された後に返されますが、 を呼び出した直後にパケットを送信する必要はありませんsend。すぐにsend戻りますが、OS はデータを内部的にキャッシュし、実際に送信する前に、ソケットにさらにデータが書き込まれるまでしばらく待機する場合があります。これはNagle のアルゴリズムと呼ばれ、ネットワークを介して多数の小さなパケットを送信することを回避します。これを無効にして、パケットをより迅速にネットワークにプッシュできます。これを呼び出して、送信ソケット (または通信が双方向の場合は両方) でTCP_NODELAYオプションを有効にしてみてください。

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

recvこれにより、データがないためにスリープしている時間が短縮される可能性があります。

ウィキペディアが述べているように:

このアルゴリズムは、1980 年代初頭にほぼ同時期に別のグループによって TCP に導入された機能である TCP 遅延確認応答とうまく相互作用しません。両方のアルゴリズムが有効になっている場合、TCP 接続に対して 2 回連続して書き込みを行い、その後、2 回目の書き込みからのデータが宛先に到達するまで実行されない読み取りが続くアプリケーションでは、最大500 ミリ秒の一定の遅延が発生します。 ACK遅延」。このため、TCP 実装は通常、Nagle アルゴリズムを無効にするインターフェースをアプリケーションに提供します。これは通常、TCP_NODELAY オプションと呼ばれます。

ベンチマークに表示されている 0.5 秒について言及されているため、これが理由である可能性があります。

于 2013-11-02T14:00:18.977 に答える
2

はい、send() または sendall() はバックグラウンドで発生します (接続が現在飽和していない限り、つまり送信待ちのデータがすでに多すぎる場合を除きます)。対照的に、 recv() は、データが既に到着している場合にのみデータをすぐに取得しますが、データが到着していない場合は待機します。次に、おそらくその一部を返します。(c は UDP ソケットではなく TCP ソケットであると想定しています。) recv(N) が N バイトを返すと想定しないでください。次のような関数を書く必要があります。

def recvall(c, n):
    data = []
    while n > 0:
        s = c.recv(n)
        if not s: raise EOFError
        data.append(s)
        n -= len(s)
    return ''.join(data)

とにかく、ポイントに。問題は recv() の速度ではありません。私の理解が正しければ、次の 4 つの操作があります。

  • サーバーのレンダリング (1/25 秒)

  • サーバーはソケットで何かを送信し、クライアントが受信します。

  • クライアントの賃借人 (1/4 秒);

  • クライアントはソケットに何かを送り返します。

これには(0.3 + 2 * network_delay)数秒かかります。並行して行われることはありません。1 秒あたりのフレーム数を増やしたい場合は、これら 4 つの操作の一部を並列化する必要があります。たとえば、合理的に考えて、操作 3 が最も遅いと仮定します。3 つの操作を他の 3 つの操作と並行して実行する方法を次に示します。クライアントがデータを受信して​​処理し、すぐにサーバーに応答を送信するようにクライアントを変更する必要があります。そして、それをレンダリングし始めます。この場合、このレンダリングには 1/4 秒かかるため、これで十分なはずです。これは、応答がサーバーに到達し、サーバーがレンダリングし、次のパケットが再度送信されるのに十分な時間です。

于 2013-11-02T12:08:39.323 に答える