pythonメニュー

TCP通信の確認

python の socketクラスのTCP通信を利用して、任意ファイルを通信相手に送るプログラムを紹介します。
サーバ側プログラムを先に実行させて、それにクライアントプログラムで接続(TCPの接続)し、そのコネクションを利用して、 クラアインからサーバにファイルを送信するまでの内容です。

STEP1

サーバに、クライアントが接続してクライアントで、キー入力文字列を送信して、 サーバで受信文字列を送信するコードです。
サーバー側とクライアント側をそれぞれを示します。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#   サーバ側「sudo python3 umehosi.py」で動かす

import socket
portnumber=59154
hostname=socket.gethostname()
ip = socket.gethostbyname(hostname)
server_addr =(ip, portnumber)
print("Serverの情報:",hostname, server_addr)
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversock.bind(server_addr)  # IPとポート番号を指定します
print("接続要求を待つ")
serversock.listen(5)
print("接続に対して許可")

sock, address = serversock.accept()#サーバの接続受け入れ
print("接続相手:",address)

while True:
    bin = sock.recv(1)#1byte受信
    if len(bin) != 1:
        break
    print( hex(bin[0]))

sock.close()
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#  クライアント側 「sudo python3 umeclient.py」で動かす

import socket
portnumber=59154
ip="192.168.0.110"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, portnumber))
print("接続成功")

while True:
    s = input("送信文字列>")
    if s == "" : break
    bin=s.encode("utf-8")#binaryへ変換
    sock.sendall(bin)#一括送信

sock.close()
最終的に、binaryファイルを送受信する目的で、バイト列で送信、バイト列の受信のプログラムになっている。 この送受信の実験で分かるように、このような手法ではクライアント側入力した文字列を、サーバ側で復元して表示する繰り返しができません。


STEP2

上記を変更して、入力した文字列ごとを正しく復元するために、 クライアント送信側では改行コード"\r\n"を送り、サーバ側では"\r\n"までデータを取り込んだら復号するよう変更します。
以下は、接続処理後の繰り返し部を示します。
buf=b""
while True:
    bin = sock.recv(1)#1byte受信
    if len(bin) != 1:
        break
    buf += bin
    if buf[-2:] == b"\r\n":
        s = buf[0:-2].decode('utf-8')
        print(s)
        buf=b""
while True:
    s = input("送信文字列>")
    if s == "" : break
    bin=(s+"\r\n").encode("utf-8")#binaryへ変換
    sock.sendall(bin)#一括送信

sock.close()


STEP3

上記を利用して、ファイルや文字列を受信するサーバープログラムと、クライアントプログラムを紹介します。
FTPアプリ存在しない環境で、以下が動作すれば、簡単に任意のファイルを送り込めるでしょう。

まず、サーバープログラム(tcp_server.py)は次のようになります。
# tcp_server.py
import socket

def recieveFile(sock:socket):
    buf=b""
    while True:
        bin = sock.recv(1)        # receieve 1byte
        if len(bin) != 1: break   # close or error ?
        buf += bin
        if buf[-2:] == b"\r\n": # end line ?
            s = buf[0:-2].decode('utf-8')# binarry to str
            print(s)
            a=s.split(' ')
            filename, filesize = a[0], int(a[1])
            print("filename:", filename, ",  filesize:", filesize) 
            with open( filename, "wb") as f:
                for n in range(filesize):
                    bin = sock.recv(1)
                    f.write(bin)
            print("received:", filesize, "byte.")
            break

def receiveData(sock:socket) -> bool:
    bin = sock.recv(1) # receive 1byte
    if len(bin) != 1: return False # close or error ?
    if bin == b"M" :
        buf=b""
        while True:
            bin = sock.recv(1) # receive 1byte
            buf += bin
            if bin == b"\n": break # end ? 1 line str?
        #
        print(buf.decode('utf-8'), end="") 
    #
    if bin == b"f" : recieveFile(sock)
    return True

portnumber=3000
hostname=socket.gethostname()
#ip = socket.gethostbyname(hostname) # get local IP Address
ip = "192.168.0.110"
server_addr =(ip, portnumber)
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversock.bind(server_addr) 
print(ip, " bind !");
serversock.listen(2) 
print("LISTENING")
while True:
    sock, address = serversock.accept()
    print("ESTABLISHED---------!")
    print("client:",address)
    while receiveData(sock) : pass 
    break


上記サーバへファイルや文字列を送信するクライアントプログラム(tcp_client.py)です。
import socket # tcp_client.py
import os
import sys

def sendFile(sock: socket, filename:str):
    filesize = os.path.getsize(filename)
    s = "f{} {}".format(filename, filesize)
    bin=(s+"\r\n").encode("utf-8") # to binary
    print(bin, end="  ")
    sock.sendall(bin) # send filename filesize
    with open(filename, "rb") as f:
        bin = f.read(filesize)
    # print("send data:" , bin )
    sock.sendall(bin) # send all file
    print("send byte:" , len(bin) )

def sendString(sock: socket, msg:str):
    bin=msg.encode("utf-8")# to binary
    sock.sendall(bin)# send all file

portnumber=3000
ip=input("server IP address:>>>")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(ip+":3000 connect")
sock.connect((ip, portnumber))
print("connected")
loopFlag = True
while loopFlag:
    s = input("send info input 'M'/'f'/'Q':>>>")
    if len(s) > 0 and s[0] == 'M' :
        msg = input("send str input: >>>");
        sendString(sock, 'M' + msg+"\r\n");
        continue;
    if len(s) > 0 and s[0] == 'f' :
        print( os.listdir('.') )
        filename = input("send file name: >>>")
        sendFile(sock, filename) # send file
        continue;
    if len(s) > 0 and s[0] == 'Q': loopFlag = False;

sock.close

Pico W をSTA(ステーション)モードで使い、TCPサーバーとして使う実験

Raspberry Pi Pico W(Raspberry Pi Zero 2 Wではない)のTCP通信の通信実験です。
このプログラムは、Pico W をSTA(ステーション)モードで起動します。
TCPサーバーを起動して、そのIPアドレスとポート番号を、ブロードキャストで配信します。
TCPサーバーは、ノンブロッキングモードにして、ブロードキャスト配信と共に、クライアントからの接続で ブロードキャスト配信の繰り返しを止めています。
ブロードキャストの配信周期は2秒です。
これにより、ブロードキャスト配信ループが終わった時、クライアントからの接続を許可したことになる。
最後に『TCPクライアントから1byte受信後に、クライアントへ7byteのテスト用バイナリー送信』を繰り返す。
import socket
import network
import struct
from machine import Pin
import utime
import _thread

SSID='〇〇'# Wifi用 環境に合わせて設定
PW = '〇〇〇'# 上記に接続するためのパスフレーズ 環境に合わせて設定

def wifi__connect(): # STA(ステーション)モードで既存のWi-Fiに接続して、確定したらリターン
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PW)
    while wlan.isconnected() == False:
        print('Connecting to Wi-Fi AP')
        utime.sleep(1)
    #
    wlan_status = wlan.ifconfig()
    return wlan_status

target_pin = Pin(14,Pin.OUT) # 使用LED(GPIO14接続)利用の準備
onboard_led = Pin( "LED", Pin.OUT )

target_pin.high()# 外付けLED点灯
wlan_status = wifi__connect()# Wifi接続
print( f'''Connected!
    Pico IP Address:{wlan_status[0]} Netmask:{wlan_status[1]}
    Default Gateway:{wlan_status[2]} Name Server: {wlan_status[3]}''' ) # Wifi接続情報表示
utime.sleep(1)
target_pin.low()# 外付けLED消灯

# TCP サーバー起動
server_port=59154
server_addr = socket.getaddrinfo('0.0.0.0', server_port)[0][-1]
serversock = socket.socket()
print(f"start server:{server_addr}")# TCPサーバーのIPアドレスとポート番号
serversock.bind(server_addr)
serversock.listen(1) # TCPサーバ受け入れ待機
#serversock.setblocking(False)  # ← ノンブロッキングモードにする
client=None # TCP 通信用ソケット
address=None # TCP remort address

BROADCAST_PORT = 59001 
MESSAGE = f"{wlan_status[0]},{server_port}"
MESSAGE = MESSAGE.encode('utf-8')
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ブロードキャストを有効にする 
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

# 自身TCPサーバーに、クライアントが接続するまで、サーバー情報をブロードキャストで配信する繰り返し
count=0
while True:
    sock.sendto(MESSAGE, ('255.255.255.255', BROADCAST_PORT))
    print(f"[{count}] Sent: {MESSAGE}")
    try:
        client, address = serversock.accept() # クライアントの接続許可
        serversock.close()
        break  # 接続したらループ抜ける
    except OSError:# 接続なしの時はここに来る
        print("まだ接続なし。処理続行。")
    utime.sleep(2)
    count+=1

# client, address = serversock.accept() # クライアントの接続許可
print(f"{address}からの接続を許可しました。")

client.setblocking(True)# ← ロッキングモードにする
count=0

while True: # 『TCPクライアントから1byte受信後に、クライアントへ7byteのテスト用バイナリー送信』を繰り返す。
    client.recv(1) # 【A】 100回程度後に、ここで次のエラーが生じる→OSError: [Errno 103] ECONNABORTED
    client.send(b'\x01\x02\x03\x04\x05\x06\x07')  # 固定7バイト送信
    # 【A】のコメントがある1byte受信処理を無効化すると数千回の送信を行った後、OSError: [Errno 103] ECONNABORTEDのエラーが上記で生じる
    utime.sleep(0.01)
    count+=1
    print(f"{count}回目の送信")
初期は、TCPの1byte受信はしていませんでした。7バイト送信の繰り返しで、数千回の送信を行った後にOSError: [Errno 103] ECONNABORTEDのエラーが発生して止まります。
原因調査で、「一部のOSやルーター/ネットワーク機器が、アイドル状態(双方向通信なし)を不正接続と判断して TCP RST を返すことがある。 また一定時間応答がないと切断するケースがある(特に家庭用ルーター環境で起きやすい)」という情報から 送信だけのための失敗か?と考えて、1byteの受信処理を追加してみました。
すると、 100回程度受信送信の繰り返し後、client.recv(1) の受信部で、同じようエラーが起きるようになりました。

その後、ブロードキャスト配信を止めて、TCPノンブロッキングモードの使用を止めて、通常のシンプルな送受信に変更しても、エラーが起きるようです。

以前に、PicoWの開発環境として遠隔制御用のTCPサーバーを構築していますが、 その時のTCPサーバー側からの送受信でこのようなエラーが起きていません。
このTCP送受信エラーが起きない環境は、PicoWをAP(アクセスポイント)モードで使っています。
そして、今回のエラーが起きる方はPicoWをSTA(ステーション)モードで使っています。
STAモードでは、既存のWi-Fiに接続しており、そのアクセスポイントを介して通信しています。 対してAPモードの場合、中継を介さずに直接通信するため、問題が起きにくいのではないかと考えています。

現状(2025年6月)において、PicoWをSTA(ステーション)モードでTCPサーバーを利用した場合に、安定した通信が得られないという結果になったので この手法を避けてことにする。そこで、TCPの代わりにUDPのユニキャストを使う試みを行う。
なお、上記の実験で使ったクライアント側の実験コードを下記に示します。
import socket
import struct

def receiveBroadcast(portNo): #ブロードキャスト受信関数
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', portNo))
    print("Waiting for broadcast...")
    data, addr = s.recvfrom(1024)
    print(f"Received: {data} from {addr}")
    s.close()
    return data

ip_port_bin = receiveBroadcast(59001) # 接続サーバIPアドレスとポート番号の受信
print(f"受信データ:{ip_port_bin}")
rec_data=ip_port_bin.decode('utf-8')
ip_port_a = rec_data.split(',')

ip=ip_port_a[0]			# 接続サーバーのIPアドレス取得
portnumber=int(ip_port_a[1])	# 接続サーバーのポート番号取得
print(f"Multicast Recive IP:PORT:{ip}:{portnumber}")

#ip='192.168.0.38'
#portnumber=59154

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# サーバーへの接続
sock.connect((ip, portnumber))
print("接続成功")

while True:
    sock.send(b"G") 	# 【A】1byteの送信
    bin = sock.recv(7)	# 7byte受信
    if bin == b"" : break
    print(f"{bin}")

sock.close()

Pico W をSTA(ステーション)モードで使い、TCPクライアントとして使う実験

実験で動作したコード(picowtcp.py)
動作ができるが、OSError: [Errno 103] ECONNABORTEDのエラーが時々発生して止まります。
検討中
import socket
import network
import utime
import struct

def print_pass(*args, **kwargs):
    pass

# print=print_pass

SSID='〇〇'# Wifi用
PW = '〇〇〇'# 上記に接続するためのパスフレーズ

ip, portnumber="192.168.0.110",59000#接続先サーバのアドレスデフォルト(マルチキャスト受信で変更)

def wifi__connect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PW)
    while wlan.isconnected() == False:
        print('Connecting to Wi-Fi AP')
        utime.sleep(1)
    #
    wlan_status = wlan.ifconfig()
    return f'''Connected!
    Pico IP Address:{wlan_status[0]} Netmask:{wlan_status[1]}
    Default Gateway:{wlan_status[2]} Name Server: {wlan_status[3]}'''

# https://micropython-docs-ja.readthedocs.io/ja/latest/library/socket.html

def ipv4_inet_pton(ip: str)->bytes:# iPv4のアドレス文字列に対応する4byteバイナリを返す
    a=ip.split('.')
    return bytes(map(int,a))

def receiveIpMulticast_new(multicast_group, portNo): #マルチキャスト
    wlan = network.WLAN(network.STA_IF)
    local_ip = ipv4_inet_pton(wlan.ifconfig()[0])
    print(f"local_ip:{local_ip}")
    group = ipv4_inet_pton(multicast_group)

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', portNo))

    mreq = struct.pack('4s4s', group, local_ip)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    print(f"Multicast-----{multicast_group}:{portNo}-----Wait !!!")

    #s.settimeout(5)  # optional
    try:
        data, addr = s.recvfrom(1024)
        print(f"Multicast-----receive data: {data.decode()} from {addr}")
        s.close()
        return data
    except OSError as e:
        print("Multicast receive failed:", e)
        s.close()
        return None

def receiveIpMulticast(multicast_group, portNo): #マルチキャスト
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ソケットの作成
    # server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # ★
    server_socket.bind(('', portNo)) # ソケットにアドレスをバインド
    
    # マルチキャストグループに参加
    group = ipv4_inet_pton(multicast_group) # 引数のIPアドレス文字列に対応する4byteバイナリ取得
    mreq = struct.pack('4sL', group, 0)
    server_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
    
    # 受信
    print(f"Multicast-----{multicast_group}:{portNo}-----Wait !!!")
    data, addr = server_socket.recvfrom(1024)  # データとアドレスを受信
    print(f"Multicast-----receive data: {data.decode('utf-8')} from {addr}")
    server_socket.close()
    return data

def receiveBroadcast(portNo): #ブロードキャスト
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', portNo))
    print("Waiting for broadcast...")
    data, addr = s.recvfrom(1024)
    print(f"Received: {data} from {addr}")
    s.close()
    return data

def receiveUnicast(portNo): #ユニキャスト
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('0.0.0.0', portNo))
    print(f"UDP 受信待機中 (ポート {portNo})...")
    data, addr = sock.recvfrom(1024)
    print(f"受信: {data.decode('utf-8')} from {addr}")
    sock.close()
    return data    

    
def tcp_connect():
   client=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   print(f'Connecting to {ip}:{portnumber}')
   client.connect((ip, portnumber))
   client.setblocking(True)# client.settimeout(None)  # タイムアウトを秒数で指定
   #client.setblocking(False)
   return client

def send_data(sock:socket, prefix:str, d16s:list):
    bin=prefix.encode('utf-8') + int(d16s[0]).to_bytes(2,'big')
    bin+=int(d16s[1]).to_bytes(2,'big')+int(d16s[2]).to_bytes(2,'big')
    n=0
    while n < len(bin):#上記の(1+2*3)=7byteを送る。(非ブロッキングにも対応)
        n+=sock.write(bin[n:])
    #sock.write(bin)、 sock.send(bin)、sock.sendall(bin)# などで全てOSError: [Errno 103] ECONNABORTED
    return bin
    
print( wifi__connect() )
client = tcp_connect()
bin=send_data(client, 'M', [50, 127, -1]) # 7byteのバイト例で送信
print( f"送信データ:{bin}")
while True:
     client.write(bin)
     #utime.sleep(0.001)
    
client.close()