0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python TCP通信 for文が適切に動作しないことがある?

Last updated at Posted at 2024-11-16

起こったこと

Python初心者が、PythonでTCP通信を試してみようとしたときのことです。
↓このサイトを参考にやってみていました。
"PythonソケットによるTCP通信入門"
https://nayutari.com/python-socket#

そしてできたのが以下のコード。
(上記のサイトの"2.5.ブロードキャスト対応"と、一部を除いて、内容は同じです。)

python server.py
import socket
import threading

IPADDR = "127.0.0.1"
PORT = 49152

sock_sv = socket.socket(socket.AF_INET)
sock_sv.bind((IPADDR, PORT))
sock_sv.listen()

# クライアントのリスト
sock_list = []

def recv_client(sock, addr):
    while True:
        try:
            data = sock.recv(1024)
            if data == b"":
                break

            print("$ say client:{}".format(addr))

            # 受信データを全クライアントに送信
            for sock in sock_list:
                sock[0].send(data)

        except ConnectionResetError:
            break

    # クライアントリストから削除
    client_list.remove((sock, addr))
    print("- close client:{}".format(addr))

    sock.shutdown(socket.SHUT_RDWR)
    sock.close()

# クライアント接続待ちループ
while True:
    sock_cl, addr = sock_sv.accept()
    # クライアントをリストに追加
    sock_list.append((sock_cl, addr))
    print("+ join client:{}".format(addr))

    thread = threading.Thread(target=recv_client, args=(sock_cl, addr))
    thread.start()

このコードで動かしてみたところ、問題なく動いていたように思えたのですが、
以下の動きを試したとき、クライアント側からの送信を受け付けなくなってしまいました。

  1. クライアントを2つ以上接続する。
  2. 1つ目に接続したクライアントから送信し、その後2つ目に接続したクライアントからも送信。
  3. もう一度、1つ目に接続したクライアントから送信しようとすると、以下のエラーが出て、受信がされない。
Exception in thread Thread-1 (recv_client):
Traceback (most recent call last):
  File "C:\Users\(ユーザ名)\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "C:\Users\(ユーザ名)\AppData\Local\Programs\Python\Python310\lib\threading.py", line 946, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\(ユーザ名)\OneDrive\ドキュメント\server.py", line 17, in recv_client
    data = sock.recv(1024)
AttributeError: 'tuple' object has no attribute 'recv'

このエラー文は、後からこの記事を書くために、クライアントもpythonにして試したときに出たもので、最初に試したときは出ていませんでした。
(クライアント側を別言語で作っていたからだと思われます)

なので、最初はエラー文も出ず、なぜこうなっているのか原因が分かりませんでした。
ですが、ここで参考サイトの元コードとの違いに気づきました。

 # 受信データを全クライアントに送信(元のコード)
 for client in client _list:
    client[0].send(data)
 # 受信データを全クライアントに送信(今のコード)
 for sock in sock_list:
    sock[0].send(data)

別言語で書いていたクライアント側の変数名と合わせるために、変数名をsockに変えていました。
試しに、これを元コードと同じく、clientに変えてみると、先ほどの動作をしても、動くようになりました。

なぜ?

しかしなぜ、変数名の違いで動作が適切に行われなかったのでしょうか?
色々試してみたところ、以下のようにrecv_client()の引数の名前と、for文内でループで使われる名前が一致した際に、このようなエラーが出ました。

def recv_client(sock, addr):# ←ここの変数名がsock
    while True:
        try:
            data = sock.recv(1024)
            if data == b"":
                break

            print("$ say client:{}".format(addr))

            # 受信データを全クライアントに送信
            for sock in sock_list:# ←ここの変数名もsock
                sock[0].send(data)

        except ConnectionResetError:
            break

後から試したときに出たエラーコードは、「タプルオブジェクトだから、recvは使えない」といった内容なので、同じsockという変数が使われて、分からなくなったのでしょうか?

ちなみに、最初にsockからclientに戻して動いたとき、
pythonでは、for文内でも同じ変数を使うと動かなくなるのか、と考え、以下のコードを試しましたが、普通に動きました。

users = ['A','B','C']

def configure_user(user):
    for user in users:
        print(user)
    return

user = 'D'
configure_user(user)
出力.txt
A
B
C

おわりに

このことについて「python for」などと、雑に調べてみたのですが、はっきりとした原因は、分からずじまいでした。
とりあえず、ここから以下の学びを得ました、

  1. 最初からいきなりコードをいじらず、まずはそのまま動かして、試した方が良い。
  2. for文内とはいえ、同じ変数名はあまり使うべきではない。(それにより動く、動かないに関わらず)

以上です。ありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?