0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UDP通信でパケットロスを判別する方法

Last updated at Posted at 2024-12-16

今回は,UDP通信でパケットロスを判別する方法について説明します.

そもそもUDPとは、User Datagram Protocolの略語であり,コネクションレス型の通信プロトコルです.

コネクションレス型であるが故に起こるメリットとデメリット両方あります.

・メリットは高速通信が可能になること
・デメリットはパケットロス(データの欠損)が起こること

しかし,クライアント(データを受け取る側)はパケットロスが起こった事すら分かりません.そのため,何が足りなかったんだろう?ってクライアントは分からずじまいになってしまいます.

そこで解決するのがシーケンス番号です.
シーケンス番号とは連続した番号でそのままの意味です.しかし,これが役に立ちます.それを下記で説明します.
シーケンス番号説明図

クライアントはシーケンス番号1番のパケットを受信した後に,シーケンス番号3番のパケットを受信しました.この時,シーケンス番号2番のパケットが欠番していることがクライアントで分かります.このようにしてパケットロスを判別します.
ですが,UDPにはシーケンス番号の実装がされていません.

実装されていない?なら実装しましょう

出来上がったものが下記になります.

今回の実験環境について

今回,クライアントはESP32,サーバはESXiに仮想マシンを建てました.
クライアントはMicroPythonで実装し,サーバはPythonで実装しています.

クライアント(ESP32)

client.py
import network
import usocket as socket
import uasyncio as asyncio
import json
import machine

# Global variables
sta_if = network.WLAN(network.STA_IF)
received_sequence_number = ""
last_seq = -1

def inet_aton(ip):
    parts = [int(part) for part in ip.split('.')]
    return bytes(parts)

def connect_wifi(ssid, password):
    global sta_if
    if not sta_if.isconnected():
        print('Connecting to WiFi...')
        sta_if.active(True)
        sta_if.connect(ssid, password)
        while not sta_if.isconnected():
            pass
        machine.Pin(2, machine.Pin.OUT).value(1)  # Assuming the blue LED is connected to GPIO pin 2

    print('Network configuration:', sta_if.ifconfig())
    return sta_if

async def receive_multicast(mcast_sock):
    global sta_if, received_sequence_number, last_seq

    while True:
        try:
            data, addr = mcast_sock.recvfrom(2048)
            if data:
                received_message = json.loads(data.decode('utf-8'))
                sequence_number = received_message["sequence_number"]
                print(f"received_sequence_number:{sequence_number}")
                if "sequence_number" in received_message:
                    received_sequence_number = received_message["sequence_number"]
                                                        
                    if last_seq != -1 and received_sequence_number != last_seq + 1:
                        for i in range(last_seq+1, received_sequence_number):
                            print(f"loss_sequence_number:{i}")

                    last_seq = received_sequence_number

        except Exception as e:
            print(f"Error receiving multicast message: {type(e).__name__}, {str(e)}")
            
async def main():
    ssid = "ssid"
    password = "password"
    MULTICAST_IP = "239.0.0.1"
    MULTI_PORT = 12345
    sta_if = connect_wifi(ssid, password)

    if sta_if.isconnected():
        mcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        mcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        mreq = inet_aton(MULTICAST_IP) + inet_aton('0.0.0.0')
        mcast_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
        mcast_sock.bind(('0.0.0.0', MULTI_PORT))

        print("Starting multicast reception...")
        await receive_multicast(mcast_sock)
    else:
        print("Failed to connect to Wi-Fi")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except Exception as e:
        print(f"Error running main: {e}")
    machine.Pin(2, machine.Pin.OUT).value(0)

サーバ(仮想マシン)

server.py
import time
import socket
import json
import logging

# Global variables
LOCALHOST = socket.gethostbyname(socket.gethostname())
MULTICAST_IP = "239.0.0.1"
MULTI_PORT = 12345
PACKET_SIZE = 115

## UDP socket creation
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.bind(('0.0.0.0', MULTI_PORT))  # Bind for multicast communication

## Logging configuration
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

## IP settings
hostname = socket.gethostname()
hostname, alias_list, ipaddr_list = socket.gethostbyname_ex(hostname)
logging.info(f"Host IP addresses: {ipaddr_list}")


def send_packets(chunks):
    for i, chunk in enumerate(chunks):
        chunk = chunk.ljust(PACKET_SIZE, '\0')
        msg = {
            "sequence_number": i,
            "datas": chunk
        }
        send_json_message = json.dumps(msg)
        try:
            udp_sock.sendto(send_json_message.encode(), (MULTICAST_IP, MULTI_PORT))
            if i % 100 == 0:
                logging.info(f"Sent packet {i}")
        except Exception as e:
            logging.error(f"Send error: {e}")
        time.sleep(0.1)


def main():
    chunks = []
    FILE_NAME = 'sendingFile_100KB.txt'
    with open(FILE_NAME, 'r') as f:
        for line in f:
            line = line.strip()
            chunks.append(line[:PACKET_SIZE])
    
    send_packets(chunks)

if __name__ == "__main__":
    main()

実行手順

client.pyをクライアントに,server.pyをサーバに記述します.
※client.pyのssidとpasswordは適切なものに設定してください
まず,クライアントを起動し,Wi-Fi接続をします.
その後,仮想マシン上でserver.pyを実行します.

実行結果

クライアントの実行結果

クライアントの実行結果

サーバの実行結果

サーバの実行結果

参考にしたサイト

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?