サーバに、クライアントが接続してクライアントで、キー入力文字列を送信して、
サーバで受信文字列を送信するコードです。
サーバー側とクライアント側をそれぞれを示します。
#!/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ファイルを送受信する目的で、バイト列で送信、バイト列の受信のプログラムになっている。
この送受信の実験で分かるように、このような手法ではクライアント側入力した文字列を、サーバ側で復元して表示する繰り返しができません。
上記を利用して、ファイルや文字列を受信するサーバープログラムと、クライアントプログラムを紹介します。
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
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()
実験で動作したコード(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()