Heim > Artikel > Backend-Entwicklung > Eine vorläufige Untersuchung der Python-Socket-Programmierung
Socket-Programmierschritte
Der Server erstellt einen Socket, bindet die Adresse und den Port und wartet dann auf eingehende Verbindungen am Port. Sobald eine Verbindung eingeht, empfängt er die eingehende Verbindung über die Akzeptanzfunktion.
Der Client erstellt auch einen Socket. Binden Sie die Remote-Adresse und den Port, stellen Sie dann eine Verbindung her und senden Sie Daten.
Serverseitiger Socket
Das Folgende ist eine detaillierte Beschreibung des serverseitigen socker_server.py anhand eines Beispielcodes
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()
Zuerst verwenden wir den Socket .getaddrinnfo-Funktion zum Konvertieren von Host/Port in eine Sequenz mit 5 Tupeln. Dieses 5-Tupel enthält alle notwendigen Parameter, die wir zum Erstellen einer Socket-Verbindung benötigen. Die zurückgegebenen 5 Tupel sind (family, sockettype, proto, canonname, sockaddr)
Familienadresscluster, der als erster Parameter der socket()-Funktion verwendet wird. Es gibt hauptsächlich die folgenden:
socket.AF_UNIX wird für die Kommunikation mit Prozessen auf einer einzelnen Maschine verwendet.
socket.AF_INET wird für die Kommunikation mit Servern verwendet.
socket.AF_INET6 unterstützt IPv6
sockettype-Socket-Typ, der als zweiter Parameter der socket()-Funktion verwendet wird. Häufig werden
socket.SOCK_STREAM-Standard verwendet, der für das TCP-Protokoll
socket verwendet wird . SOCK_DGRAM wird für das UDP-Protokoll
Protoprotokoll verwendet und als dritter Parameter der socket()-Funktion verwendet. Die getaddrinnfo-Funktion gibt den entsprechenden Protokoll-
Kanonnamen und einen standardisierten Hostnamen basierend auf dem Adressformat und dem Socket-Typ zurück.
sockaddr beschreibt eine Socket-Adresse. Es handelt sich um ein Tupel, das hauptsächlich für die Funktionen bind() und connect() verwendet wird.
Als nächstes erstellen Sie ein Socket-Objekt und übergeben das von der getaddrinnfo-Funktion zurückgegebene af ,Sockettyp,Proto.
s = socket.socket(af, socktype, proto)
Dann binde meine Socket-Adresse
s.bind(sa)
Abhörmodus aktivieren
s.listen(5)
Die Abhörfunktion überwacht die mit dem Socket verbundene Verbindung und die Parameter Geben Sie an, dass das System maximal 5 Verbindungswarteschlangen warten kann, bevor es die Verbindung ablehnt. Diese Verbindungen wurden noch nicht akzeptiert. Die Menge kann nicht unendlich sein, normalerweise wird 5 angegeben.
Sobald wir die Verbindung abhören, rufen wir die Accept-Funktion auf, um die Verbindung zu empfangen.
conn, addr = s.accept()
Die Accept-Funktion gibt ein Tupel zurück, und conn ist ein neues Socket-Objekt, das zum Empfangen und Empfangen verwendet wird Daten senden. addr stellt die Socket-Adresse des anderen Endes dar.
Als nächstes können wir das Conn-Objekt zum Senden und Empfangen von Daten verwenden.
data = conn.recv(1024) # 接收数据, 这里指定一次最多接收的字符数量为1024 conn.send(data) # 发送数据
Hier erhalten wir einen Verbindungs-Socket, der nicht mehr ausgeführt wird. Wenn wir also die Verbindung schleifen möchten, verwenden Sie den Funktion akzeptieren Setzen Sie es in eine Endlosschleife.
Client-Socket
Die Client-Socket-Programmierung ist relativ einfach. Nachdem Sie über Connect eine Verbindung mit dem Server hergestellt haben, können Sie miteinander kommunizieren. socket_client.py lautet wie folgt
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)
Das Obige dient hauptsächlich der Socket-Programmierung von TCP-Stream-Daten. Bei UDP-Protokolldaten ist die Verarbeitung etwas anders. Die Verarbeitungsfunktion zum Senden und Empfangen von UDP-Datenpaketen lautet beispielsweise:
socket.sendto(string, flags, address) socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的数据,address是发送方的socket地址
SocketServer-Modul
Zusätzlich zum Socket-Modul stellt die Netzwerkprogrammierung in Python auch das SocketServer-Modul bereit Das Modul führt hauptsächlich das Socket-Modul Encapsulation aus, das das Erstellen, Binden, Verbinden, Empfangen, Senden und Schließen von Socket-Objekten kapselt, was die Programmierung von Netzwerkdiensten erheblich vereinfacht.
Dieses Modul stellt die folgenden 2 Hauptnetzwerkdienstklassen zum Erstellen entsprechender Socket-Streams bereit
TCPServer erstellt einen Socket-Stream des TCP-Protokolls
UDPServer erstellt einen Socket-Stream des UDP-Protokolls
Wir haben das Socket-Stream-Objekt und benötigen außerdem eine Anforderungsverarbeitungsklasse. Das SocketServer-Modul stellt Anforderungsverarbeitungsklassen bereit, einschließlich BaseRequestHandler und seiner abgeleiteten Klassen StreamRequestHandler und DatagramRequestHandler. Erben Sie also einfach eine dieser drei Klassen und überschreiben Sie dann die Handle-Funktion. Diese Funktion wird zur Verarbeitung der empfangenen Anfrage verwendet. Schauen wir uns ein serverseitiges Codebeispiel an
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相关的理解,有什么不当或错误之处,还请指出。