Python Language
Gniazda
Szukaj…
Wprowadzenie
Wiele języków programowania używa gniazd do komunikacji między procesami lub między urządzeniami. W tym temacie wyjaśniono prawidłowe użycie modułu gniazd w Pythonie w celu ułatwienia wysyłania i odbierania danych za pośrednictwem popularnych protokołów sieciowych.
Parametry
Parametr | Opis |
---|---|
gniazdo.AF_UNIX | Gniazdo UNIX |
gniazdo.AF_INET | IPv4 |
gniazdo.AF_INET6 | IPv6 |
gniazdo.SOCK_STREAM | TCP |
gniazdo.SOCK_DGRAM | UDP |
Wysyłanie danych przez UDP
UDP to protokół bezpołączeniowy. Wiadomości do innych procesów lub komputerów są wysyłane bez nawiązywania jakiegokolwiek połączenia. Nie ma automatycznego potwierdzenia, czy wiadomość została odebrana. UDP jest zwykle używany w aplikacjach wrażliwych na opóźnienia lub w aplikacjach wysyłających transmisje w całej sieci.
Poniższy kod wysyła komunikat do procesu nasłuchującego na porcie 66 hosta lokalnego przy użyciu protokołu UDP
Zauważ, że nie ma potrzeby „zamykania” gniazda po wysłaniu, ponieważ UDP jest bezpołączeniowy .
from socket import socket, AF_INET, SOCK_DGRAM
s = socket(AF_INET, SOCK_DGRAM)
msg = ("Hello you there!").encode('utf-8') # socket.sendto() takes bytes as input, hence we must encode the string first.
s.sendto(msg, ('localhost', 6667))
Odbieranie danych przez UDP
UDP to protokół bezpołączeniowy. Oznacza to, że uczestnicy wysyłający wiadomości nie wymagają ustanowienia połączenia przed wysłaniem wiadomości. socket.recvfrom
zwraca zatem krotkę ( msg
[wiadomość, którą otrzymało gniazdo], addr
[adres nadawcy])
Serwer UDP wykorzystujący wyłącznie moduł socket
:
from socket import socket, AF_INET, SOCK_DGRAM
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('localhost', 6667))
while True:
msg, addr = sock.recvfrom(8192) # This is the amount of bytes to read at maximum
print("Got message from %s: %s" % (addr, msg))
Poniżej znajduje się alternatywna implementacja wykorzystująca socketserver.UDPServer
:
from socketserver import BaseRequestHandler, UDPServer
class MyHandler(BaseRequestHandler):
def handle(self):
print("Got connection from: %s" % self.client_address)
msg, sock = self.request
print("It said: %s" % msg)
sock.sendto("Got your message!".encode(), self.client_address) # Send reply
serv = UDPServer(('localhost', 6667), MyHandler)
serv.serve_forever()
Domyślnie sockets
blokują. Oznacza to, że wykonanie skryptu będzie czekać, aż gniazdo odbierze dane.
Wysyłanie danych przez TCP
Przesyłanie danych przez Internet jest możliwe dzięki wielu modułom. Moduł gniazd zapewnia niski poziom dostępu do podstawowych operacji systemu operacyjnego odpowiedzialnych za wysyłanie lub odbieranie danych z innych komputerów lub procesów.
Poniższy kod wysyła ciąg bajtów b'Hello'
do serwera TCP nasłuchującego na porcie 6667 na hoście lokalnym hosta i zamyka połączenie po zakończeniu:
from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 6667)) # The address of the TCP server listening
s.send(b'Hello')
s.close()
Wyjście gniazda jest domyślnie blokowane, co oznacza, że program będzie czekał na połączenie i wysyła połączenia, aż akcja zostanie „zakończona”. W przypadku połączenia oznacza to, że serwer faktycznie przyjmuje połączenie. W przypadku wysyłania oznacza to tylko, że system operacyjny ma wystarczającą ilość miejsca w buforze, aby ustawić w kolejce dane do wysłania później.
Gniazda należy zawsze zamykać po użyciu.
Wielowątkowy serwer gniazd TCP
Po uruchomieniu bez argumentów program uruchamia serwer gniazd TCP, który nasłuchuje połączeń z 127.0.0.1
na porcie 5000
. Serwer obsługuje każde połączenie w osobnym wątku.
Po uruchomieniu z argumentem -c
program łączy się z serwerem, odczytuje listę klientów i drukuje ją. Lista klientów jest przesyłana jako ciąg JSON. Nazwę klienta można podać, przekazując argument -n
. Po podaniu różnych nazw można zaobserwować wpływ na listę klientów.
client_list.py
import argparse
import json
import socket
import threading
def handle_client(client_list, conn, address):
name = conn.recv(1024)
entry = dict(zip(['name', 'address', 'port'], [name, address[0], address[1]]))
client_list[name] = entry
conn.sendall(json.dumps(client_list))
conn.shutdown(socket.SHUT_RDWR)
conn.close()
def server(client_list):
print "Starting server..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 5000))
s.listen(5)
while True:
(conn, address) = s.accept()
t = threading.Thread(target=handle_client, args=(client_list, conn, address))
t.daemon = True
t.start()
def client(name):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 5000))
s.send(name)
data = s.recv(1024)
result = json.loads(data)
print json.dumps(result, indent=4)
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('-c', dest='client', action='store_true')
parser.add_argument('-n', dest='name', type=str, default='name')
result = parser.parse_args()
return result
def main():
client_list = dict()
args = parse_arguments()
if args.client:
client(args.name)
else:
try:
server(client_list)
except KeyboardInterrupt:
print "Keyboard interrupt"
if __name__ == '__main__':
main()
Dane wyjściowe serwera
$ python client_list.py
Starting server...
Dane wyjściowe klienta
$ python client_list.py -c -n name1
{
"name1": {
"address": "127.0.0.1",
"port": 62210,
"name": "name1"
}
}
Bufory odbiorcze są ograniczone do 1024 bajtów. Jeśli reprezentacja ciągu JSON listy klientów przekroczy ten rozmiar, zostanie obcięta. Spowoduje to zgłoszenie następującego wyjątku:
ValueError: Unterminated string starting at: line 1 column 1023 (char 1022)
Surowe gniazda w systemie Linux
Najpierw wyłącz automatyczne sumowanie kontrolne karty sieciowej:
sudo ethtool -K eth1 tx off
Następnie wyślij pakiet, używając gniazda SOCK_RAW:
#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
s.bind(("eth1", 0))
# We're putting together an ethernet frame here,
# but you could have anything you want instead
# Have a look at the 'struct' module for more
# flexible packing/unpacking of binary data
# and 'binascii' for 32 bit CRC
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"
s.send(dst_addr+src_addr+ethertype+payload+checksum)