作り方が悪いだけなんだろうか
気づいたらもう冬休み第2弾も終わりか。
Pythonの練習にUDPでパケット投げまくってネットワークのスループットはかるプログラム作ってたんだが、作り方が悪いのかそういうものなのか、同一ホストで測定しててもパケットロスが30% overとかいってる。一応パケット送る方はちゃんと送ってるみたいなので、受信側で落としてるみたいなんだけど、どうにかできんものかなぁ。
- 追記
- もったいないからソース貼る。思いつきで作ってる上にほとんどPython使ったことないのできわめて汚い。
#!/usr/bin/python -O # -*- coding: utf-8 -*- import socket from optparse import OptionParser from struct import * from datetime import timedelta, datetime # パケットのstructモジュールでのフォーマット文字列 HELLO = '!5sII' OK = '!2s' DATAHEADER = '!I' # メイン def main(): # コマンドラインオプションの解析 parser = OptionParser() parser.add_option("-s", "--server", dest="servermode", action="store_true", default=False, help="server mode") parser.add_option("-p", "--port", type="int", dest="port", help="server port", default=8021) parser.add_option("-d", "--destination", dest="destination", help="destination host") parser.add_option("-c", "--count", type="int", dest="count", help="send number of COUNT datagrams", default=10000) parser.add_option("-l", "--length", type="int", dest="datalength", help="send LENGTH byte datagram", default=1500) (options, args) = parser.parse_args() # コマンドラインオプションによってモードを切り替えて起動する。 if options.servermode: servermode(options.port) else: if not options.destination: parser.error("client mode is required option -d.") clientmode(options.destination, options.port, options.count, options.datalength) # サーバモード def servermode(port): print "server mode", "port:", port # ソケット生成 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # NATを超えるために、ソース・デスティネーションポートを固定する。 # ソース・ディスティネーションポートを同じにするため、 # 同一ホストでサーバ・クライアントは同居できない。 # ※ポートをソース・デスティネーションの2種類使えば可能 s.bind(("", port)) # Helloパケットを受け取る (counter, length) = recievehello(s, port) # パケットを受け取る。 (bps, losscount) = recievepacket(s, port, counter, length) # 結果表示 print "Recieve rate : %d bps" % bps print "Loss packets : %d packets (%f %%)" % (losscount, ((losscount * 100.0) / (counter * 1.0))) # Helloパケットを受け取って、初期データを作る def recievehello(s, port): # タイムアウトは設定しないで待ち続ける。 # socket#recvfrom()が返すaddressは、 # アドレスとポートのタプルみたい。 (recieve_packet, address) = s.recvfrom(calcsize(HELLO)) (hello, counter, length) = unpack(HELLO, recieve_packet) # 一応内容の確認もする。 if hello != 'hello': raise # OKパケットを作る。 ok_packet = pack(OK, 'OK') # OKパケットを送る。 (destination, port) = address sending(s, port, destination, ok_packet) print "From", address, "count:", counter, "length:", length # 送られてくる数とパケットの長さをタプルで返す。 return (counter, length) # クライアントからパケットをひたすら受け取る。 def recievepacket(s, port, counter, length): print "recieving packets..." # 送られてくるデータのフォーマット文字列を作る。 format_string = dataformat(length) # 受信タイムアウトの設定をする。 s.settimeout(1.0) # 対象ネットワークにより調整の必要あり # 受信できたらTrueにする配列を作る。 recieved = [] for index in range(counter): recieved.append(False) packetsize = calcsize(format_string) # 開始時刻を取得する。 start_time = datetime.now() # ひたすら受信する。 try: while True: (recieve_packet, address) = s.recvfrom(packetsize) (index) = unpack(format_string, recieve_packet) # 受信したことを記録する。 recieved[index[0]] = True except socket.timeout: # タイムアウトの例外は無視。その時点で受信終了。 pass # 送るのにかかった時間を計算する。 end_time = datetime.now() - timedelta(seconds=-1) delta_time = end_time - start_time print "start time : %s" % start_time print "end time : %s" % end_time print "delta time : %s" % delta_time # 受信レートを計算する。 seconds = delta_time.days * 24.0 * 60.0 * 60.0 + delta_time.seconds * 1.0 + delta_time.microseconds / 1000.0 bits = counter * length * 8.0 # 受信レート(bps)と消失パケット数をタプルで返す。 # recieved[0]は必ずFalseなのでカウントから1減らす。 return (bits / seconds, recieved.count(False) - 1) # クライアントモード def clientmode(destination, port, counter, length): print "client mode", "destination:", destination, "port:", port, "count:", counter, "length:", length # ソケット生成 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # NATを超えるために、ソース・デスティネーションポートを固定する。 # サーバのポートと同じにするため、同一ホストで # サーバ・クライアントは同居できない。 # ※ポートをソース・デスティネーションの2種類使えば可能 # s.bind(("", port)) s.bind(("", port + 1)) # とりあえずテスト用。コマンドラインオプション増やすか # サーバにHelloパケットを送る sayhello(s, port, destination, counter, length) # 指定された量のパケットをサーバに送りつける bps = sendpacket(s, port, destination, counter, length) print "load : %f bps" % bps # 最初にどんなパケットを送りつけるかをHelloでサーバに知らせる def sayhello(s, port, destination, counter, length): # Helloパケットを組み立てる packet = pack(HELLO, 'hello', counter, length) # 送りつける sending(s, port, destination, packet) # OKがくるまでのタイムアウトを設定 s.settimeout(1.0) # 戻りパケットは2バイト(OK) (recieve_packet, address) = s.recvfrom(calcsize(OK)) # 一応中身を検査 (recieve) = unpack(OK, recieve_packet) if recieve[0] != 'OK': raise # 指定された量のパケットをサーバに送りつける。 def sendpacket(s, port, destination, counter, length): print "sending packets..." # 送りつけるパケットを作るためのフォーマット文字列を作る。 format_string = dataformat(length) # 開始時刻を取得する。 start_time = datetime.now() # 送りつける packet_number = 1 while packet_number != counter: data = pack(format_string, packet_number) sending(s, port, destination, data) packet_number += 1 # 完了時刻を取得する。 end_time = datetime.now() # 送るのにかかった時間を計算する。 delta_time = end_time - start_time print "start time : %s" % start_time print "end time : %s" % end_time print "delta time : %s" % delta_time # 実際にかけた負荷をbps単位で返す。 seconds = delta_time.days * 24.0 * 60.0 * 60.0 + delta_time.seconds * 1.0 + delta_time.microseconds / 1000.0 bits = counter * length * 8.0 return bits / seconds # どうもWindowsはこういうことしないとちゃんと送ってくれないみたい # from Pythonクックブック p.496 def sending(s, port, destination, data): while data: bytes_sent = s.sendto(data, (destination, port)) data = data[bytes_sent:] # 送りつけるパケットを作るためのフォーマット文字列を作る。 def dataformat(length): datalength = length - calcsize(DATAHEADER) return "%s%d%s" % (DATAHEADER, datalength, 'x') # いつもの if __name__ == "__main__": main()