ソケット プログラミングの手順
サーバーはソケットを作成し、アドレスとポートをバインドし、接続が確立されると、accept 関数を通じて受信接続を受信します。
クライアントはソケットも作成します。リモート アドレスとポートをバインドし、接続を確立してデータを送信します。
サーバーソケット
以下は、サンプルコードを通して詳細に説明します。サーバー socker_server.py
import socket import sys HOST = "127.0.0.1" PORT = 10000 s = None for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): af, socktype, proto, canonname, sa = res try: s = socket.socket(af, socktype, proto) except socket.error as msg: s = None continue try: s.bind(sa) s.listen(5) except socket.error as msg: s.close() s = None continue break if s is None: print 'could not open socket' sys.exit(1) conn, addr = s.accept() print 'Connected by', addr while 1: data = conn.recv(1024) if not data: break conn.send(data) conn.close()
まず、socket.getaddrinnfo 関数を通じて、ホスト/ポートを 5 つのタプルを含むシーケンスに変換します。この 5 タプルには、ソケット接続を作成するために必要なパラメータがすべて含まれています。返される 5 タプルは (family、sockettype、proto、canonname、sockaddr)
ファミリー アドレス クラスターであり、socket() 関数の最初のパラメーターとして使用されます。主に次のようなものがあります:
socket.AF_UNIX は単一マシン上のプロセスとの通信に使用されます。
socket.AF_INET はサーバーとの通信に使用されます。
socket.AF_INET6 は IPv6 をサポートします
sockettype ソケットタイプ、socket() 関数の 2 番目のパラメータとして使用されます。一般的に使用されるものは、
TCP プロトコルに使用されるデフォルトの socket.SOCK_STREAM
UDP プロトコルに使用される
socket.SOCK_DGRAM です。使用されるsocket()関数の3番目のパラメータ。 getaddrinnfo 関数は、アドレス形式とソケット タイプに基づいて、適切なプロトコル
canonname と標準化されたホスト名を返します。
sockaddr はソケットアドレスを記述します。これは主にbind()関数とconnect()関数に使用されるタプルです
次に、ソケットオブジェクトを作成し、getaddrinnfo関数によって返されるaf、sockettype、protoを渡します。
s = socket.socket(af, socktype, proto)
次にソケットアドレスをバインドします
s.bind(sa)
リスニングモードを有効化します
s.listen(5)
listen 関数は、ソケットに接続されている接続をリッスンします。このパラメーターは、システムが接続を拒否する前に一時停止できる接続キューの最大数が 5 であることを示します。 。これらの接続はまだ受け入れられていません。数量を無限にすることはできません。通常は 5 が指定されます。
接続をリッスンしたら、accept 関数を呼び出して接続を受信します
conn, addr = s.accept()
accept 関数はタプルを返し、conn はデータの送受信に使用される新しいソケット オブジェクトです。 addr は相手側のソケットアドレスを表します。
次に、conn オブジェクトを使用してデータを送受信できます
data = conn.recv(1024) # 接收数据, 这里指定一次最多接收的字符数量为1024 conn.send(data) # 发送数据
ここでは、接続ソケットを受信すると実行を停止するため、接続をループしたい場合は、accept 関数を無限ループに入れます。
クライアント ソケット
クライアント ソケットのプログラミングは、connect を通じてサーバーとの接続を確立した後、相互に通信できるようになります。 socket_client.pyは以下の通りです
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: s = socket.socket(af, socktype, proto) except socket.error as msg: s = None continue try: s.connect(sa) except socket.error as msg: s.close() s = None continue break if s is None: print 'could not open socket' sys.exit(1) s.sendall('Hello, world') data = s.recv(1024) s.close() print 'Received', repr(data)
上記は主にTCPストリームデータのソケットプログラミングです。 UDP プロトコル データの場合、処理は少し異なります。たとえば、UDP パケットを送受信するための処理関数は次のとおりです。
socket.sendto(string, flags, address) socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的数据,address是发送方的socket地址
SocketServer モジュール
Python のネットワーク プログラミングでは、ソケット モジュールに加えて、SocketServer モジュールも提供されます。このモジュールは主にソケット モジュールをカプセル化し、ソケット モジュールの作成をバインドします。ソケット オブジェクトの設定、接続、受信、送信、および終了はすべて内部にカプセル化されているため、ネットワーク サービスのプログラミングが大幅に簡素化されます。
このモジュールは、対応するソケット ストリームを作成するための次の 2 つの主要なネットワーク サービス クラスを提供します
TCPServer は TCP プロトコルのソケット ストリームを作成します
UDPServer は UDP プロトコルのソケット ストリームを作成します
ソケット ストリーム オブジェクトに加えて、 、リクエスト処理クラスも必要です。 SocketServer モジュールは、BaseRequestHandler とその派生クラス StreamRequestHandler および DatagramRequestHandler を含むリクエスト処理クラスを提供します。したがって、これら 3 つのクラスのいずれかを継承して、受信したリクエストを処理するためにこの関数を使用してハンドル関数をオーバーライドするだけです。サーバー側のコード例を見てみましょう
import SocketServer class MyTCPHandler(SocketServer.StreamRequestHandler): """创建请求处理类,重写handle方法。此外也可以重写setup()和finish()来做一些请求处理前和处理后的一些工作""" def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() print "{} wrote:".format(self.client_address[0]) print self.data # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 10000 server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C # server.shutdown() server.serve_forever() # 一直循环接收请求 # server.handle_request() # 只处理一次请求就退出
看着是不是代码简单了很多,而且SocketServer模块内部使用了多路复用IO技术,可以实现更好的连接性能。看serve_forever函数的源代码用到了select模块。通过传入socket对象调用select.select()来监听socket对象的文件描述符,一旦发现socket对象就绪,就通知应用程序进行相应的读写操作。源代码如下:
def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: while not self.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set()
即使使用了select技术,TCPServer,UDPServer处理请求仍然是同步的,意味着一个请求处理完,才能处理下一个请求。但SocketServer模块提供了另外2个类用来支持异步的模式。
ForkingMixIn 利用多进程实现异步
ThreadingMixIn 利用多线程实现异步
看名字就知道使用了mixin模式。而mixin模式可以通过多继承来实现,所以通过对网络服务类进行多继承的方式就可以实现异步模式
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass
针对ThreadindMixIn,实现异步的原理也就是在内部对每个请求创建一个线程来处理。看源码
def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start()
下面提供一个异步模式的示例
import socket import threading import SocketServer class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): def handle(self): data = self.request.recv(1024) cur_thread = threading.current_thread() response = "{}: {}".format(cur_thread.name, data) self.request.sendall(response) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass def client(ip, port, message): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) try: sock.sendall(message) response = sock.recv(1024) print "Received: {}".format(response) finally: sock.close() if __name__ == "__main__": # Port 0 means to select an arbitrary unused port HOST, PORT = "localhost", 0 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) ip, port = server.server_address # Start a thread with the server -- that thread will then start one # more thread for each request server_thread = threading.Thread(target=server.serve_forever) # Exit the server thread when the main thread terminates server_thread.daemon = True server_thread.start() print "Server loop running in thread:", server_thread.name client(ip, port, "Hello World 1") client(ip, port, "Hello World 2") client(ip, port, "Hello World 3") server.shutdown() server.server_close()
以上是本人对socket相关的理解,有什么不当或错误之处,还请指出。