[Python] ネットワークプログラミング - ソケット (1)

1. ソケットの概要

ソケット、本来の意味は「ソケット」ですが、コンピューター通信の分野では、ソケットは一般に「ソケット」と訳されます。Baidu Encyclopedia の Socket の定義は次のとおりです。 , 提供する. アプリケーション層プロセスがネットワークプロトコルを使用してデータを交換するためのメカニズムを定義します. その位置の観点から, ソケットはアプリケーションプロセスに接続され、ネットワークプロトコルスタックに接続されます. アプリケーションプログラムのためのインターフェースです.ネットワーク プロトコルを介して通信します。スタックと対話するためのインターフェイス" [1] .

どのように理解するのですか?上記の段落の Socket の定義を理解するには、TCP/IP プロトコルを簡単に理解する必要があります。TCP/IPプロトコル、つまりTransmission Control Protocol/Internet Protocol(Transmission Control Protocol/Internet Protocol)とは、複数の異なるネットワーク間の情報伝達を実現できるプロトコル群のことで、一連のネットワーク通信の総称です。プロトコル[2 ]簡単に言うと、TCP/IP プロトコルは、コンピュータ間の通信で従わなければならない規則の記述です. これらの規則に従うことによってのみ、コンピュータは相互に通信することができます[3] . TCP/IP プロトコルは、下の図 1 に示すように、アプリケーション層、トランスポート層、ネットワーク層、およびリンク層の 4 層構造を採用しています [1] [ 4] :

ここに画像の説明を挿入

図 1. TCP/IP プロトコル構造
 
  • アプリケーション層: アプリケーション層は、特定のアプリケーションの詳細を処理します。アプリケーション層のプロトコルには、主に Telnet、FTP、SMTP などがあります。
  • トランスポート層: トランスポート層とも呼ばれるトランスポート層は、主にアプリケーション間の通信を提供します。トランスポート層のプロトコルには、主に TCP とユーザー データグラム プロトコル (User Datagram Protocol、UDP) があります。
  • ネットワーク層: ネットワーク層はインターネット層とも呼ばれ、主に隣接するコンピューター間の通信を担当します。ネットワーク層のプロトコルには、主に IP、ICMP、IGMP があります。
  • リンク層: リンク層は、データ リンク層またはネットワーク インターフェイス層とも呼ばれ、通常、オペレーティング システムのデバイス ドライバーと、コンピューターの対応するネットワーク インターフェイス カードが含まれます。リンク層は主に、IP モジュールの IP データグラムの送受信、ARP モジュールの ARP 要求の送信と ARP 応答の受信、および RARP の RARP 要求の送信と RARP 応答の受信を担当します。リンク層のプロトコルには、主に ARP と RARP があります。

ソケットは、下の図 2 に示すように、アプリケーション層とトランスポート層の間のソフトウェア抽象化層です。ソケットは、TCP/IP プロトコル[5]のカプセル化ですSocket は、アプリケーションがネットワーク プロトコル スタックとやり取りするための一連のインターフェイスを提供します。ソケットを介して、アプリケーションはネットワーク プロトコル スタックと簡単にやり取りできるため、さまざまなアプリケーションが通信できます。

ここに画像の説明を挿入

図 2. TCP/IP プロトコルにおけるソケットの位置
 

したがって、以下の図 3 に示すように、Socket をアプリケーション間の通信のエンドポイントとして抽象化できます。

ここに画像の説明を挿入

図 3. ソケットは、ネットワーク内の異なるホスト上のアプリケーション プロセス間の双方向通信用のエンドポイントの抽象化です
 

より一般的に理解すると、プラグをソケットに差し込むことでグリッドから電力供給を受けることができるように、アプリケーションはデータを送受信する前にインターネットに接続する必要があり、Socket はアプリケーションをネットワークに接続するために使用されるツールです。インターネット[6]

ここに画像の説明を挿入
Socket の典型的なアプリケーションは Web サーバーとブラウザーです: ブラウザーはユーザーが入力した URL を取得し、サーバーへの要求を開始します; サーバーは受信した URL を分析し、対応する Web ページのコンテンツをブラウザーに返します; テキスト、画像などの要素を提示します、およびユーザーへのビデオ[6]


2. ソケットの作成

Python では、socket() 関数を使用してソケットを作成します。その構文は次のとおりです (最初に socket モジュールをインポートする必要があります) [7] :

socket.socket([family[, type[, proto]]])  # 使用给定的地址族、套接字类型及协议号来创建套接字

パラメータの説明[7] - [9] :

  • family: アドレス ファミリ。このパラメータのデフォルトは socket.AF_INET です。

ここに画像の説明を挿入

  • type: ソケット タイプ。パラメータのデフォルトは socket.SOCK_STREAM です。

ここに画像の説明を挿入

  • protocol: プロトコル番号。通常はデフォルトで 0 に設定されているため、システムはアドレス ファミリとソケット タイプに応じて適切なプロトコルを自動的に選択します。

3.ソケットオブジェクト組み込みメソッド

このパートでは、以下で使用するいくつかの重要なメソッドのみを紹介します. その他のメソッドについては、ドキュメント[10]を参照してください.

方法 説明
サーバー方式
socket.bind(アドレス) ソケットをアドレスaddressにバインドします。アドレスの形式は、上記のアドレス ファミリーによって異なります。AF_INET の場合、アドレスはタプル (ホスト、ポート) の形式で表されます。
socket.listen([バックログ]) サーバーが接続を受け入れることを許可します。つまり、接続をリッスンします。backlog は、システムが接続を拒否する前に中断できる接続の最大数を指定します。値は少なくとも 0 にする必要があり、ほとんどのアプリケーションでは 5 に設定する必要があります。
socket.accept() 接続を受け入れます。ソケットは最初にアドレスにバインドされ、接続をリッスンする必要があります。このメソッドは (conn, address) のペアを返します。ここで、conn は、接続でデータを送受信するための新しい Socket オブジェクトです。address は、接続の反対側で Socket にバインドされたアドレスです。
クライアントメソッド
socket.connect(アドレス) アドレスのソケットに接続します。アドレスの形式は、上記のアドレス ファミリによって異なります。
socket.connect_ex(アドレス) connect メソッドの拡張バージョン。違いは、connect のようにエラーが発生したときに例外をスローするのではなく、エラーが発生したときにこのメソッドがエラー インジケーターを返すことです。メソッドが成功した場合、エラー インジケータは 0 になり、それ以外の場合、エラー インジケータは変数errnoの値になりますこのメソッドは、非同期接続などをサポートするのに役立ちます。
公開メソッド
socket.recv(bufsize[, フラグ]) データを受信します。戻り値は、受信したデータを表すバイト オブジェクトです。bufsize は、一度に受信できるデータの最大量を指定します。
socket.send(バイト[, フラグ]) データを送信するには、Socket をリモート Socket に接続する必要があります。このメソッドは、送信されたバイト数を返します。これは、バイト単位のバイト数よりも少ない場合があります。
socket.sendall(バイト[, フラグ]) データを送信するには、Socket がリモート Socket に接続されている必要があり、成功した場合は None を返します。send メソッドとは異なり、このメソッドは、すべてのデータが送信されるまで、またはエラーが発生するまで、バイト単位でデータを送信し続けます。
socket.recvfrom(bufsize[, フラグ]) データを受信します。戻り値は (bytes、address) のペアです。bytes受信したデータを表す byte オブジェクトで、addressはデータを送信した Socket のアドレスです。
socket.sendto(バイト、フラグ、アドレス) データを送信します。アドレスがターゲット Socket を指定しているため、Socket をリモート Socket に接続できませんこのメソッドは、送信されたバイト数を返します。
ソケット.close() ソケットを閉じます。

4.ソケットプログラミングの一般的な考え方

4.1 TCPに基づくソケットプログラミングの一般的な考え方

サーバー [11] :

  • サーバーソケットの作成: socket.socket(type=socket.SOCK_STREAM)
  • 作成したサーバー Socket を IP とポートにバインドします: socket.bind()
  • クライアントの接続リクエストをリッスンします: socket.listen()
  • クライアントからの接続要求を受け入れる: socket.accept()
  • クライアントからデータを受信するか、クライアントにデータを送信します: socket.recv()、または socket.send()/socket.sendall()
  • 接続を閉じる: socket.close()

クライアント [11] :

  • クライアント ソケットの作成: socket.socket(type=socket.SOCK_STREAM)
  • クライアント Socket をサーバーの IP とポートに接続します: socket.connect()、socket.connect_ex()
  • サーバーからデータを受信するか、サーバーにデータを送信します: socket.recv()、または socket.send()/socket.sendall()
  • 接続を閉じる: socket.close()

上記のプロセスは、以下の図 4 に示すように要約できます。
ここに画像の説明を挿入

図 4. TCP ベースのソケット プログラミングのフローチャート [11]
 

毎日の電話を例に取り、まず、前述の TCP ベースのソケット プログラミングの一般的な考え方を視覚的かつ直感的に理解してみましょう[6]興味がある場合は、TCP プロトコルについて詳しく知ることができます。

サーバー-受信側に相当:

  • サーバーソケットを作成する:ネットワーク規格に応じて電話/携帯電話、固定電話、5G携帯電話、または衛星電話を購入することに相当しますか?
  • 作成したサーバー ソケットを IP とポートにバインドします。これは、指定された人物に連絡できる電話/携帯電話の番号を申請することと同じです。
  • クライアントの接続要求をリッスンします。これは、電話/携帯電話をネットワークに接続し、誰かが電話をかけるのを待つことと同じです。
  • クライアントの接続要求を受け入れます。これは、電話に応答することと同じであるため、通話接続が確立されます。
  • クライアントからデータを受信するか、クライアントにデータを送信します。これは、電話で話しているのと同じであり、双方向で話すことができます。
  • 接続を閉じる: 電話を切るか、電話/受話器の電源を切るのと同じです。

クライアント-呼び出し元と同等:

  • クライアント ソケットを作成します。これは、ネットワーク規格、固定電話、5G 携帯電話、または衛星電話に応じて、電話/携帯電話を購入することと同じです。
  • クライアントの Socket をサーバーの IP とポートに接続します。これは電話をかけるのと同じで、相手の番号を入力する必要があります。
  • サーバーからデータを受信するか、サーバーにデータを送信します。これは、電話で話しているのと同じであり、双方向で通話できます。
  • 接続を閉じる: 電話を切るか、電話/受話器の電源を切るのと同じです。

私たちの毎日の呼び出しと同じように、両者は話す前に接続を確立する必要があります. サーバーは socket.listen() および socket.accept() メソッドを使用する必要があり、クライアントは socket.connect()/ メソッドを使用する必要があります. socket.connect_ex() メソッドが最初にネットワーク通信を行う前に、相互に接続を確立します。


:

  1. ネットワーク内のホストは「IPアドレス」で一意に識別でき、ホスト上のアプリケーションプログラムは「プロトコル+ポート」で一意に識別できる[12]Socket を IP とポートにバインドして、対応するアプリケーションが Socket を介してネットワーク経由で通信できるようにします。
  2. socket.listen([backlog]) メソッドのbacklogパラメーターは、システムが接続を拒否する前に中断できる接続の最大数を指定します。これは私たちの毎日の電話に似ています. 同時にまたは通話中に他の電話を受けると、これらの電話は切られ、応答されるまで待つことができます.

次に、上記の TCP ベースの Socket プログラミングの一般的な考え方をさらに理解するために、次のコードを例として取り上げます。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

""" 服务器 """

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建服务器Socket
s.bind(('127.0.0.1', 8888))  # 绑定创建的服务器Socket到一个ip和端口
s.listen(5)  # 监听客户端的连接请求
print("Waiting connection from client...")
conn, address = s.accept()  # 接受客户端的连接请求
print("Connected by {}".format(address))  # 打印客户端Socket的地址
receive_message = conn.recv(1024).decode()  # 接收客户端传来的数据并解码
print("Message received: {}".format(receive_message))  # 打印客户端传来的数据
send_message = receive_message.upper().encode()  # 将客户端传来的字符串转为大写之后,再编码发送回客户端
conn.send(send_message)  # 发送数据给客户端
conn.close()  # 关闭连接
s.close() 
#!/usr/bin/env python
# -*- coding:utf-8 -*-

""" 客户端 """

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建客户端Socket
s.connect(('127.0.0.1', 8888))  # 连接客户端Socket到服务器ip和端口
print(s.getsockname())  # 打印客户端Socket的地址
send_message = 'Hello, world!'
s.send(send_message.encode())  # 发送数据给服务器
receive_message = s.recv(1024).decode()  # 接收服务器传来的数据并解码
print("Message received: {}".format(receive_message))  # 打印服务器传来的数据
s.close()  # 关闭连接

最初にサーバー スクリプトを実行し、次にクライアント スクリプトを実行すると、結果は次のようになります。

""" 服务器 """

Waiting connection from client...
Connected by ('127.0.0.1', 52161)
Message received: Hello, world!
""" 客户端 """

('127.0.0.1', 52161)
Message received: HELLO, WORLD!

ここでは、次の点に注意する必要があります。
1. クライアントと通信するために、socket.accept() メソッドによって返された新しいソケット (サービス ソケットまたは接続ソケットと呼ばれます) を使用します。サーバー ソケット (リスニング ソケットと呼ばれます) 自体ではありません。Socket および service Socket/connection Socket の監視の詳細については、 [13]および[14]を参照してください

2. Python3 以降、ソケットはバイト型のデータを転送します. まず文字列を変換する必要があり、string.encode() で十分です. 相手側が受信したバイトデータを文字列に変換する必要があります. .decode() やるだけ[11] .


4.2 UDPに基づくソケットプログラミングの一般的な考え方

TCP ベースのソケット プログラミングと比較すると、UDP ベースのソケット プログラミングはより単純です。UDP には TCP のハンドシェークとウェーブのプロセスがないため、socket.listen()、socket.accept()、および socket.connect()/socket.connect_ex() メソッドは必要ありません。

サーバー:

  • サーバーソケットの作成: socket.socket(type=socket.SOCK_DGRAM)
  • 作成したサーバー Socket を IP とポートにバインドします: socket.bind()
  • クライアントからデータを受信するか、クライアントにデータを送信します: socket.recvfrom() または socket.sendto()
  • 接続を閉じる: socket.close()

クライアント:

  • クライアント ソケットの作成: socket.socket(type=socket.SOCK_DGRAM)
  • サーバーからデータを受信するか、サーバーにデータを送信します: socket.recvfrom() または socket.sendto()
  • 接続を閉じる: socket.close()

上記のプロセスと TCP ベースのソケット プログラミングとの違いは、以下の図 5 に示すように要約できます。
ここに画像の説明を挿入

ここに画像の説明を挿入

図 5. TCP ベースのソケット プログラミングと UDP ベースのソケット プログラミングの比較。上図は TCP に基づく Socket プログラミングのフローチャートで、下図は UDP に基づく Socket プログラミングのフローチャートです。[15]
 

例として毎日のテキスト メッセージを取り上げてみましょう。まず、UDP ベースのソケット プログラミングの一般的な考え方を視覚的かつ直感的に理解してください。興味がある場合は、UDP プロトコルについて詳しく知ることができます。

サーバー- SMS 受信側と同等:

  • サーバーソケットの作成:ネットワーク規格に合わせた携帯電話、3G携帯電話、4G携帯電話、5G携帯電話を購入するのと同じですか?
  • 作成したサーバー Socket を IP とポートにバインドします。これは、テレフォン カードを購入することと同じであり、この番号を通じて指定された人に連絡できます。
  • クライアントからデータを受信するか、クライアントにデータを送信します。テキスト メッセージの送受信に相当します。
  • 接続を閉じる:携帯電話の電源を切るのと同じです。

クライアント-送信側に相当:

  • クライアント ソケットを作成します。これは、ネットワーク規格に準拠した携帯電話、3G 携帯電話、4G 携帯電話、または 5G 携帯電話を購入することに相当しますか?
  • サーバーからデータを受信するか、サーバーにデータを送信します。テキスト メッセージの送受信に相当します。
  • 接続を閉じる:携帯電話の電源を切るのと同じです。

電話と違って、私たちは毎日メールを送受信していますが、最初に両者が接続する必要はなく、一方が相手にメールを送り、相手がメールを受信して​​から戻ってきます。メール。クライアント Socket は、 socket.sendto() メソッドを介してaddressパラメータを指定することにより、対応するアドレスでサーバー Socket にメッセージを送信します。これは、テキスト メッセージを送信するときに相手の携帯電話番号を入力することと同じです。次に、サーバー Socket は socket.recvfrom() メソッドを介してメッセージを受信し、相手のアドレスを返します. これは、他の誰かからテキスト メッセージを受信した場合と同じです. 携帯電話はテキストの内容を表示するだけではありません.相手から送信されたメッセージだけでなく、相手の携帯電話番号も表示されます。このように、サーバー Socket はsocket.sendto() メソッドを介してaddressパラメータを指定することでクライアントにメッセージを送信できます。これは、相手の携帯電話番号にメッセージを返すことと同じです。

次に、上記の UDP ベースのソケット プログラミングの一般的な考え方をさらに理解するために、次のコードを例として取り上げます。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

""" 服务器 """

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 创建服务器Socket
s.bind(('127.0.0.1', 8888))  # 绑定创建的服务器Socket到一个ip和端口
receive_message, address = s.recvfrom(1024)  # 接收客户端传来的数据
print("Receive message from {}".format(address))  # 打印客户端Socket的地址
receive_message = receive_message.decode()  # 解码客户端传来的数据
print("Message received: {}".format(receive_message))  # 打印客户端传来的数据
send_message = receive_message.upper().encode()  # 将客户端传来的字符串转为大写之后,再编码发送回客户端
s.sendto(send_message, address)  # 发送数据给客户端
s.close()  # 关闭连接
#!/usr/bin/env python
# -*- coding:utf-8 -*-

""" 客户端 """

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 创建客户端Socket
send_message = 'Hello, world!'
s.sendto(send_message.encode(), ('127.0.0.1', 8888))  # 发送数据给服务器
print(s.getsockname())  # 打印客户端Socket的地址
receive_message, address = s.recvfrom(1024)  # 接收服务器传来的数据
print("Receive message from {}".format(address))  # 打印服务器Socket的地址
receive_message = receive_message.decode()  # 解码服务器传来的数据
print("Message received: {}".format(receive_message))  # 打印服务器传来的数据
s.close()  # 关闭连接

最初にサーバー スクリプトを実行し、次にクライアント スクリプトを実行すると、結果は次のようになります。

""" 服务器 """

Receive message from ('127.0.0.1', 64410)
Message received: Hello, world!
""" 客户端 """

('0.0.0.0', 64410)
Receive message from ('127.0.0.1', 8888)
Message received: HELLO, WORLD!

参照

[1]: https://baike.baidu.com/item/%E5%A5%97%E6%8E%A5%E5%AD%97/9637606?fr=アラジン
[2]: https://baike.baidu.com/item/TCP/IP%E5%8D%8F%E8%AE%AE/212915
[3]: https://www.runoob.com/tcpip/tcpip-tutorial.html
[4]: https://www.cnblogs.com/jukaiit/p/6775404.html
[5]: https://www.jianshu.com/p/8c1f37361a89
[6]: https://zhuanlan.zhihu.com/p/137212690
[7]: https://www.runoob.com/python/python-socket.html
[8]: https://blog.51cto.com/xpleaf/1700032
[9]: https://researchlab.github.io/2018/09/20/socket-concept/
[10]: https://docs.python.org/3.10/library/socket.html#socket.socket
[11]: https://www.liujiangblog.com/course/python/76
[12]: https://blog.csdn.net/pashanhu6402/article/details/96428887
[13]: https://blog.csdn.net/phunxm/article/details/5085825
[14]: https://www.cnblogs.com/freebrid/p/4703096.html
[15]: https://zhuanlan.zhihu.com/p/73514975

おすすめ

転載: blog.csdn.net/Graduate2015/article/details/122209859