QA@IT

PythonでSocketプログラミングをしたい

6032 PV

以下の内容をPython2.7.5で組みたいと考えています.
チャットを用いたゲームに使いたいです.

サーバーはポート3個準備
ポート1とポート2からの接続を確認してからチャットが開始します.
ここで,チャットはポート1(先攻のクライアント),ポート2(後攻のクライアント)につないだクライアントが交互に行い,相手が入力終わるまで入力できません.
ポート3は接続していなくても開始します.ポート3につないだクライアントはポート1とポート2につないだクライアントのやりとりをみることができます.ポート3は多数の人が接続できる想定です.
先攻のクライアントが動かしたプログラムは必ずポート1につながり,後攻のクライアントが動かしたプログラムは必ずポート2につながります.そして閲覧者が動かしたプログラムは必ずポート3につながります.
先攻と後攻がどちらもつながったときにチャットが開始します.

(1) ポート1からのデータを受信して,ポート2とポート3にそのまま送信します.そしてデータをサーバーに保存します.
(2) ポート2からのデータを受信し,ポート1とポート3にそのまま送信します.そしてデータをサーバーに保存.
(3) それ以降は(1)と(2)の繰り返しを行います.

このプログラムを今週中に作らないといけません.
大変お手数なのですが,やりかたを教えていただけると幸いです.

現状についてです.
できていること
・つないだ順に先攻と後攻と決まり,そこでチャットができる.
・そのチャットは交互ではなく,打ちたいときに打てる

できていないこと
・サーバがポートを3つあけて,クライアントが接続しようとするポートで先攻後攻閲覧者を判別する
・ポート1orポート2のいずれかにクライアントが接続したとき,空いている方のポートに接続されるまで待つ処理
・交互にやりとりを行う

  • コードがないとなんとも言えないですが、
    「つないだ順に先攻と後攻と決まり,そこでチャットができる.」ができているなら「サーバがポートを3つあけて,クライアントが接続しようとするポートで先攻後攻閲覧者を判別する」はできそうな気がしますけどどこで詰まっているんでしょう。
    -
  • できていないことの「ポート1orポート2のいずれかにクライアントが接続したとき,空いている方のポートに接続されるまで待つ処理」ですが、「先攻のクライアントが動かしたプログラムは必ずポート1につながり」とあるので「空いている方のポートに」ではなくて「ポートに接続できるまで待つ」ではないでしょうか。
    それなら、クライアントがつながらなかった場合にリトライし続けると良いと思います。
    -
  • flied_onion様

    ありがとうございます.
    Socket通信は初めてで,とまどっています.
    >「先攻のクライアントが動かしたプログラムは必ずポート1につながり」とあるので「空いている方のポートに」ではなくて「ポートに接続できるまで待つ」ではないでしょうか。
    おっしゃる通りです.大変失礼しました.
    -
  • flied_onion様
    サーバがポート3つあけた状態で動くということはどうしたらよいかという基本的なところがわかっていません.
    このサイトの言っていることで方向性は正しいのでしょうか?(TCPThreadedServerを用いる)
    http://stackoverflow.com/questions/3655053/python-listen-on-two-ports
    -

回答

flied_onion様

おっしゃっていることは,server側の以下のコードでserverがsocketを追加していると思うのですが,
ここを増やせばよいということでしょうか.

    port, socklist, server = 9876, [], localhost
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((server, port))
    server_socket.listen(1)
    socklist.append(server_socket)

ここで,

    port, socklist, server = 9876, [], localhost
    server_socketA = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socketA.bind((server, port))
    server_socketA.listen(1)
    socklist.append(server_socketA)
    port = 9999
    server_socketB = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socketB.bind((server, port))
    server_socketB.listen(1)
    socklist.append(server_socketB)

としてもうまくいきません.

この問題は解決しました.

以下現在のコードです.
やりたいことは,
・clientAとclientBの両方が接続されるまでサーバーが待ち受けをする処理
・clientA側に,clientBが接続されるまで"waiting..."などと表示すること
・clientAとBが交互にプレイする
です.

 read_sockets, write_sockets, error_sockets = select.select(socklist, [], [])
    for sock in read_sockets:
        if sock == server_socket1:
            print("reach1")
            sockfd, addr = server_socket1.accept()
            socklist.append(sockfd)
            broadcast(socklist, server_socket1, sockfd, '[%s:%s] Enter' % addr)
            server1start = 1
        elif sock == server_socket2:
            print("reach2")
            sockfd, addr = server_socket2.accept()
            socklist.append(sockfd)
            broadcast(socklist, server_socket2, sockfd, '[%s:%s] Enter1' % addr)
            server2start = 1
        elif sock == server_socket3:
            print("reach3")
            sockfd, addr = server_socket3.accept()
            socklist.append(sockfd)
            broadcast(socklist, server_socket3, sockfd, '[%s:%s] Enter2' % addr)    
        else:
            try:
                data = sock.recv(4096).decode()
                if data == '': raise Exception('Done')
                if data:
                    broadcast(socklist, server_socket1, sock, data)
            except Exception as e:
                print(e)
                broadcast(socklist, server_socket1, sock, '[%s, %s] Exit' % addr)
                sock.close()
                socklist.remove(sock)


6/1追加
この部分でどうしたらよいのか困っています.
以下のやり方だとうまくいかないです.


 try:
                if server1start == 1 and server2start == 1:
                    #p send
                    if p == 0:
                        p = p + 1
                        broadcast(socklist, server_socket1, sockfd, '%s' % p)
                        broadcast(socklist, server_socket2, sockfd, '%s' % p)
                        #sock.send(str(p).encode('utf-8'))
                    data = sock.recv(4096).decode()
                    if data == '': raise Exception('Done')
                    if data:
                        broadcast(socklist, server_socket1, sock, data)

編集 履歴 (4)
  • そうです。そういうつもりで書きました。
    どううまくいかないのか伝えてもらえないと回答できません。

    今回は前のコードを見てみましたが、broadcast関数は
    if socket != server_socketA and socket != server_socketB and socket != sock :
    と直していますか?
    直った場合は理由を考えてみると良いと思います。
    -
  • あと、それで直った場合、server_socketAなどがグローバル変数になっているので、broadcasetの引数を直した方が良いでしょう。 -
  • いただいたアドバイスを元に組んだところ,全てのクライアントが接続し,そしてAとBが入力した内容がbroadcastされ,サーバとCにも表示されるようになりました.
    次は,AとBが交互にプレイすることと,AとB両方が接続するまでプレイ開始しないということです.
    今これについてはわかっていません.
    -
  • serverがAとBの接続を待つのは,おそらくwhileループでなんとかなるかとおもいまますが,clientの表示(例えばWaiting Other Player..みたいな)とpromptを使い分けるためにserverとclientの間で何をやりとりしたらよいのかがわかっていません. -
  • コードを追記しました -
  • server1startなどで接続状態を管理しているのでしょうからそれを見て情報をクライアントに送ればよいでしょう。whileの前で初期値を設定するのを忘れないで下さい。 -
  • broadcastで何が行われているのかを考えてみて下さい。ヒントとしては第3引数に指定している理由は「送信者のソケットだから」でしょう。
    あとは自分で考えてみて下さい。
    -
  • flied_onion様
    あれから別件のプログラムを書いていて,今こちらに戻ってきました.
    こちらのプログラムは正直よくわかっていません.
    流れとしては,serverから整数pを投げて, それをclientが受け取ります.
    そしてそのpの値でclient側のpromptを制御するという策です.
    ですが,そのpの値とチャットで打ち込んだdataが干渉(?)しているのかうまくいきません.
    -
  • sockfdには何が入っている想定でしょうか。
    そしてそれは(想定通りのものが入っているとして)broadcastでどう使われると思いますか?
    -

flied_onion様

ありがとうございます.

基本的なことがわかっていなくて申し訳ないのですが,同じIPアドレス(今はlocalhost)でポート番号が
違っているとき,threadはどのように記述したらよいのでしょうか.
何かメソッドのようなものを作成するのでしょうか.
大変お手数ですが,ご教授いただれば幸いです.
期限が迫っていて焦っています.

編集 履歴 (3)
  • Threadといっていたのはサーバー側の話だったのですが、selectを使っているのならselectの対象となるソケットを増やせばいいと思います。

    これは自分のコードですか?そうでない場合は著作権などは気にしてくださいね。
    -
  • flied_onion様
    ありがとうございます.
    コードはいろんなところから見てこれというものを引っ張ってきてしまいました.
    失礼しました.
    -
  • そのまま過ぎる場合は参考URLを示して一部引用などの方がよいと思います。転載自体禁止している場合もありますので。

    また、そのうえでどこがわからないか聞かれた方が答えやすいです。
    -

現状どこまで書けるのかわからないですが、ひとまず

このサイトの言っていることで方向性は正しいのでしょうか?(TCPThreadedServerを用いる)

に関して。

そもそもそのStackoverflowの質問は他の回答にありますが、acceptしてないからデータを受信できていないのであってTCPのソケットを複数作ってacceptやrecvを呼び出すことは可能です。

(c_sock_1, addr1) = sk_1.accept()

ただ、acceptやrecvのタイミングで待ちが発生してしまう(処理が接続やデータ受信まで止まってしまう)のでスレッドを使って並列に処理する方が現実的なプログラミングとなります。
スレッドはTCPThreadedServerを使ってももちろんできますし、
単純にスレッドを作成(threading.Thread)するだけでもできるでしょう。
スレッド以外だとnon blockingで行う方法もあります。

期限付きということで課題かなにかではないかと思っていますが、スレッドの作成まで求められているのかはちょっとわかりません。
処理がブロックされても順序が決まっているならなんとかならないこともない気はします。スレッド書けるならそっちを使った方が良いと思いますが。

ところで、そのStackoverflowのコードと同じようにソケットを作った場合サーバーに必要なポートは接続を待つポートと実際に通信するポートの2つ必要になります(コードを書く上では意識しませんが)。「サーバーはポート3個準備」というのがどこまで求められているのかわからないですが、socket.SOCK_DGRAMでUDPで通信した方が簡単かもしれません。

※ 関係ないですが、pythonの2.7は現在2.7.11ですので、可能なら更新した方が良いですよ。2.7.5だとちょっと古すぎるかなという気がします。この問題が解決してからでいいと思いますが。

編集 履歴 (2)
ウォッチ

この質問への回答やコメントをメールでお知らせします。