Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

Ổ cắm và API ổ cắm được sử dụng để gửi tin nhắn trên mạng. Họ cung cấp một hình thức giao tiếp giữa các quá trình (IPC). Mạng có thể là một mạng cục bộ, logic vào máy tính hoặc một mạng được kết nối về mặt vật lý với mạng bên ngoài, với các kết nối riêng với các mạng khác. Ví dụ rõ ràng là Internet, mà bạn kết nối thông qua ISP của bạn.

Show

Trong hướng dẫn này, bạn sẽ tạo ra:

  • Một máy chủ và máy khách ổ cắm đơn giảnsocket server and client
  • Một phiên bản cải tiến xử lý đồng thời nhiều kết nốimultiple connections simultaneously
  • Một ứng dụng máy chủ-khách hàng có chức năng giống như ứng dụng ổ cắm chính thức, hoàn chỉnh với tiêu đề và nội dung tùy chỉnh riêng của nósocket application, complete with its own custom header and content

Đến cuối hướng dẫn này, bạn sẽ hiểu cách sử dụng các chức năng và phương thức chính trong mô-đun ổ cắm Python, để viết các ứng dụng máy khách của riêng bạn. Bạn sẽ biết cách sử dụng một lớp tùy chỉnh để gửi tin nhắn và dữ liệu giữa các điểm cuối mà bạn có thể xây dựng và sử dụng cho các ứng dụng của riêng bạn.

Các ví dụ trong hướng dẫn này yêu cầu Python 3.6 trở lên, và đã được thử nghiệm bằng Python 3.10. Để tận dụng tối đa hướng dẫn này, tốt nhất là bạn nên tải xuống mã nguồn và có nó để tham khảo trong khi đọc:

Mạng và ổ cắm là những chủ đề lớn. Tập theo nghĩa đen đã được viết về chúng. Nếu bạn mới sử dụng ổ cắm hoặc kết nối mạng, thì điều đó hoàn toàn bình thường nếu bạn cảm thấy choáng ngợp với tất cả các thuật ngữ và mảnh.

Mặc dù vậy, don không được khuyến khích. Hướng dẫn này là dành cho bạn! Như với bất cứ điều gì liên quan đến Python, bạn có thể học một chút tại một thời điểm. Đánh dấu bài viết này và quay lại khi bạn đã sẵn sàng cho phần tiếp theo.

Tiểu sử

Ổ cắm có một lịch sử lâu dài. Việc sử dụng chúng bắt nguồn từ ARPANET vào năm 1971 và sau đó trở thành API trong Hệ điều hành Phân phối Phần mềm Berkeley (BSD) được phát hành năm 1983 có tên Berkeley Sockets.

Khi Internet cất cánh vào những năm 1990 với World Wide Web, lập trình mạng cũng vậy. Máy chủ web và trình duyệt weren là các ứng dụng duy nhất tận dụng các mạng mới được kết nối và sử dụng ổ cắm. Các ứng dụng máy khách-máy chủ của tất cả các loại và kích thước được sử dụng rộng rãi.

Ngày nay, mặc dù các giao thức cơ bản được sử dụng bởi API ổ cắm đã phát triển qua nhiều năm và các giao thức mới đã phát triển, API cấp thấp vẫn giữ nguyên.

Loại ứng dụng ổ cắm phổ biến nhất là các ứng dụng máy khách-máy khách, trong đó một bên hoạt động như máy chủ và chờ kết nối từ máy khách. Đây là loại ứng dụng mà bạn sẽ tạo trong hướng dẫn này. Cụ thể hơn, bạn sẽ tập trung vào API ổ cắm cho ổ cắm Internet, đôi khi được gọi là ổ cắm Berkeley hoặc BSD. Ngoài ra còn có các ổ cắm miền UNIX, chỉ có thể được sử dụng để giao tiếp giữa các quy trình trên cùng một máy chủ.

Tổng quan API ổ cắm

Mô -đun ổ cắm Python cung cấp giao diện cho API Berkeley Sockets. Đây là mô -đun mà bạn sẽ sử dụng trong hướng dẫn này.

Các hàm và phương thức API ổ cắm chính trong mô -đun này là:

  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    7
  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    8
  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    9
  • $ python echo-client.py 
    Received b'Hello, world'
    
    0
  • $ python echo-client.py 
    Received b'Hello, world'
    
    1
  • $ python echo-client.py 
    Received b'Hello, world'
    
    2
  • $ python echo-client.py 
    Received b'Hello, world'
    
    3
  • $ python echo-client.py 
    Received b'Hello, world'
    
    4
  • $ python echo-client.py 
    Received b'Hello, world'
    
    5

Python cung cấp một API thuận tiện và nhất quán ánh xạ trực tiếp vào các cuộc gọi hệ thống, các đối tác C. Trong phần tiếp theo, bạn sẽ học cách chúng được sử dụng cùng nhau.

Là một phần của thư viện tiêu chuẩn, Python cũng có các lớp giúp sử dụng các chức năng ổ cắm cấp thấp này dễ dàng hơn. Mặc dù nó không được đề cập trong hướng dẫn này, bạn có thể kiểm tra mô -đun Socketserver, một khung cho các máy chủ mạng. Ngoài ra còn có nhiều mô-đun có sẵn thực hiện các giao thức Internet cấp cao hơn như HTTP và SMTP. Để biết tổng quan, hãy xem các giao thức Internet và hỗ trợ.

Ổ cắm TCP

Bạn sẽ tạo một đối tượng ổ cắm bằng cách sử dụng

$ python echo-client.py 
Received b'Hello, world'
6, chỉ định loại ổ cắm là
$ python echo-client.py 
Received b'Hello, world'
7. Khi bạn làm điều đó, giao thức mặc định mà sử dụng là Giao thức điều khiển truyền (TCP). Đây là một mặc định tốt và có lẽ những gì bạn muốn.

Tại sao bạn nên sử dụng TCP? Giao thức điều khiển truyền (TCP):

  • Là đáng tin cậy: các gói bị rơi trong mạng được phát hiện và truyền lại bởi người gửi. Packets dropped in the network are detected and retransmitted by the sender.
  • Có phân phối dữ liệu theo đơn đặt hàng: Dữ liệu được đọc bởi ứng dụng của bạn theo thứ tự được viết bởi người gửi. Data is read by your application in the order it was written by the sender.

Ngược lại, ổ cắm giao thức datagram của người dùng (UDP) được tạo bằng

$ python echo-client.py 
Received b'Hello, world'
8 aren đáng tin cậy và dữ liệu được đọc bởi người nhận có thể nằm ngoài thứ tự từ người gửi.

Tại sao nó quan trọng? Mạng là một hệ thống phân phối hiệu quả tốt nhất. Không có gì đảm bảo rằng dữ liệu của bạn sẽ đến đích hoặc bạn sẽ nhận được những gì đã được gửi cho bạn.

Các thiết bị mạng, chẳng hạn như bộ định tuyến và công tắc, có sẵn băng thông hữu hạn và đi kèm với các giới hạn hệ thống vốn có của riêng họ. Họ có CPU, bộ nhớ, xe buýt và bộ đệm gói giao diện, giống như máy khách và máy chủ của bạn. TCP giúp bạn không phải lo lắng về việc mất gói, việc đến ngoài thứ tự và những cạm bẫy khác luôn xảy ra khi bạn giao tiếp qua một mạng.

Để hiểu rõ hơn về điều này, hãy xem chuỗi các cuộc gọi API ổ cắm và luồng dữ liệu cho TCP:

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm
Lưu lượng ổ cắm TCP (Nguồn hình ảnh)

Cột bên trái đại diện cho máy chủ. Ở phía bên tay phải là khách hàng.

Bắt đầu từ cột trên cùng bên trái, lưu ý các cuộc gọi API mà máy chủ thực hiện để thiết lập một ổ cắm nghe nghe của người dùng:

  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    7
  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    8
  • # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    9
  • $ python echo-client.py 
    Received b'Hello, world'
    
    0

Một ổ cắm nghe làm những gì tên của nó gợi ý. Nó lắng nghe các kết nối từ khách hàng. Khi máy khách kết nối, máy chủ gọi

$ python echo-client.py 
Received b'Hello, world'
0 để chấp nhận hoặc hoàn thành kết nối.

Máy khách gọi

$ python echo-client.py 
Received b'Hello, world'
1 để thiết lập kết nối với máy chủ và bắt đầu bắt tay ba chiều. Bước bắt tay rất quan trọng vì nó đảm bảo rằng mỗi bên của kết nối có thể truy cập được trong mạng, nói cách khác là máy khách có thể tiếp cận máy chủ và ngược lại. Nó có thể chỉ là một máy chủ, máy khách hoặc máy chủ có thể tiếp cận bên kia.

Ở giữa là phần Trip Trip, trong đó dữ liệu được trao đổi giữa máy khách và máy chủ bằng cách sử dụng các cuộc gọi đến

$ python echo-client.py 
Received b'Hello, world'
3 và
$ python echo-client.py 
Received b'Hello, world'
4.

Ở phía dưới, máy khách và máy chủ đóng ổ cắm tương ứng của họ.

Echo khách hàng và máy chủ

Bây giờ, bạn đã nhận được một cái nhìn tổng quan về API ổ cắm và cách khách hàng và máy chủ giao tiếp, bạn đã sẵn sàng để tạo máy khách và máy chủ đầu tiên của mình. Bạn sẽ bắt đầu với một triển khai đơn giản. Máy chủ chỉ đơn giản là lặp lại bất cứ điều gì nó nhận được trở lại với máy khách.

Máy chủ Echo

Đây là máy chủ:

# echo-server.py

import socket

HOST = "127.0.0.1"  # Standard loopback interface address (localhost)
PORT = 65432  # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

Được rồi, vậy chính xác những gì đang xảy ra trong cuộc gọi API?

$ python echo-client.py 
Received b'Hello, world'
6 Tạo một đối tượng ổ cắm hỗ trợ loại Trình quản lý ngữ cảnh, do đó bạn có thể sử dụng nó trong câu lệnh
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8. Không cần phải gọi
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
9:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().

Các đối số được chuyển cho

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
7 là các hằng số được sử dụng để chỉ định loại họ địa chỉ và loại ổ cắm.
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
1 là gia đình địa chỉ internet cho IPv4.
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
2 là loại ổ cắm cho TCP, giao thức sẽ được sử dụng để vận chuyển tin nhắn trong mạng.

Phương thức

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
8 được sử dụng để liên kết ổ cắm với giao diện mạng và số cổng cụ thể:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...

Các giá trị được truyền đến

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
8 phụ thuộc vào họ địa chỉ của ổ cắm. Trong ví dụ này, bạn đã sử dụng
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
5 (IPv4). Vì vậy, nó mong đợi một bộ hai:
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
6.

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 có thể là tên máy chủ, địa chỉ IP hoặc chuỗi trống. Nếu một địa chỉ IP được sử dụng,
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 phải là chuỗi địa chỉ có định dạng IPv4. Địa chỉ IP
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 là địa chỉ IPv4 tiêu chuẩn cho giao diện Loopback, do đó, chỉ các xử lý trên máy chủ mới có thể kết nối với máy chủ. Nếu bạn vượt qua một chuỗi trống, máy chủ sẽ chấp nhận kết nối trên tất cả các giao diện IPv4 có sẵn.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 đại diện cho số cổng TCP để chấp nhận kết nối từ máy khách. Nó phải là một số nguyên từ
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01 đến
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
02, vì
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
03 được bảo lưu. Một số hệ thống có thể yêu cầu các đặc quyền siêu nhân nếu số cổng nhỏ hơn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
04.

Tại đây, một lưu ý về việc sử dụng tên máy chủ với

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
8:

Nếu bạn sử dụng tên máy chủ trong phần máy chủ của địa chỉ ổ cắm IPv4/V6, chương trình có thể hiển thị hành vi không xác định, vì Python sử dụng địa chỉ đầu tiên được trả về từ độ phân giải DNS. Địa chỉ ổ cắm sẽ được giải quyết khác nhau thành địa chỉ IPv4/V6 thực tế, tùy thuộc vào kết quả từ độ phân giải DNS và/hoặc cấu hình máy chủ. Đối với hành vi xác định, hãy sử dụng một địa chỉ số trong phần máy chủ. (Nguồn)

Bạn sẽ tìm hiểu thêm về điều này sau, trong việc sử dụng tên máy chủ. Hiện tại, chỉ cần hiểu rằng khi sử dụng tên máy chủ, bạn có thể thấy các kết quả khác nhau tùy thuộc vào những gì mà Lùi trả về từ quá trình giải quyết tên. Những kết quả này có thể là bất cứ điều gì. Lần đầu tiên bạn chạy ứng dụng của mình, bạn có thể nhận được địa chỉ

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
06. Lần tới, bạn nhận được một địa chỉ khác,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
07. Lần thứ ba, bạn có thể nhận được
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
08, v.v.

Trong ví dụ máy chủ,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
9 cho phép máy chủ chấp nhận kết nối. Nó làm cho máy chủ trở thành một ổ cắm của người khác:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...

Phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
9 có tham số
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
11. Nó chỉ định số lượng các kết nối không được chấp nhận mà hệ thống sẽ cho phép trước khi từ chối các kết nối mới. Bắt đầu từ Python 3.5, nó tùy chọn. Nếu không được chỉ định, giá trị
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
11 mặc định được chọn.

Nếu máy chủ của bạn nhận được đồng thời nhiều yêu cầu kết nối, việc tăng giá trị

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
11 có thể giúp bằng cách đặt độ dài tối đa của hàng đợi cho các kết nối đang chờ xử lý. Giá trị tối đa phụ thuộc vào hệ thống. Ví dụ: trên Linux, xem
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
14.

Phương thức

$ python echo-client.py 
Received b'Hello, world'
0 chặn thực thi và chờ kết nối đến. Khi một máy khách kết nối, nó sẽ trả về một đối tượng ổ cắm mới đại diện cho kết nối và một tuple giữ địa chỉ của máy khách. Bộ tuple sẽ chứa
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
6 cho các kết nối IPv4 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
17 cho IPv6. Xem các gia đình địa chỉ ổ cắm trong phần tham chiếu để biết chi tiết về các giá trị tuple.

Một điều mà bắt buộc phải hiểu là bây giờ bạn có một đối tượng ổ cắm mới từ

$ python echo-client.py 
Received b'Hello, world'
0. Điều này rất quan trọng bởi vì nó có một ổ cắm mà bạn sẽ sử dụng để giao tiếp với khách hàng. Nó khác biệt với ổ cắm nghe mà máy chủ đang sử dụng để chấp nhận các kết nối mới:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

Sau khi

$ python echo-client.py 
Received b'Hello, world'
0 cung cấp đối tượng ổ cắm máy khách
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
20, vòng lặp
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
21 vô hạn được sử dụng để lặp qua các cuộc gọi chặn đến
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
22. Điều này đọc bất kỳ dữ liệu nào mà khách hàng gửi và lặp lại nó bằng cách sử dụng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
23.

Nếu

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
22 trả về một đối tượng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
25 trống,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
26, báo hiệu rằng máy khách đã đóng kết nối và vòng lặp bị chấm dứt. Câu lệnh
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 được sử dụng với
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
20 để tự động đóng ổ cắm ở cuối khối.

Echo khách hàng

Bây giờ hãy để Lừa nhìn vào khách hàng:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")

So với máy chủ, máy khách khá đơn giản. Nó tạo ra một đối tượng ổ cắm, sử dụng

$ python echo-client.py 
Received b'Hello, world'
1 để kết nối với máy chủ và gọi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
30 để gửi tin nhắn của nó. Cuối cùng, nó gọi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
31 để đọc trả lời máy chủ và sau đó in nó.

Chạy máy khách và máy chủ Echo

Trong phần này, bạn sẽ chạy máy khách và máy chủ để xem cách họ cư xử và kiểm tra những gì xảy ra.

Mở dấu nhắc thiết bị đầu cuối hoặc lệnh, điều hướng đến thư mục chứa các tập lệnh của bạn, đảm bảo rằng bạn đã cài đặt Python 3.6 trở lên và trên đường dẫn của bạn, sau đó chạy máy chủ:

Thiết bị đầu cuối của bạn sẽ xuất hiện để treo. Điều đó bởi vì máy chủ bị chặn, hoặc bị treo, trên

$ python echo-client.py 
Received b'Hello, world'
0:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

Nó chờ đợi một kết nối khách hàng. Bây giờ, hãy mở một cửa sổ hoặc dấu nhắc lệnh đầu cuối khác và chạy máy khách:

$ python echo-client.py 
Received b'Hello, world'

Trong cửa sổ máy chủ, bạn nên nhận thấy một cái gì đó như thế này:

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)

Trong đầu ra ở trên, máy chủ đã in tuple

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
33 được trả về từ
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
34. Đây là địa chỉ IP của máy khách và số cổng TCP. Số cổng,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
35, rất có thể sẽ khác nhau khi bạn chạy nó trên máy của bạn.

Xem trạng thái ổ cắm

Để xem trạng thái hiện tại của ổ cắm trên máy chủ của bạn, hãy sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
36. Nó có sẵn theo mặc định trên MacOS, Linux và Windows.

Tại đây, đầu ra NetStat từ MacOS sau khi khởi động máy chủ:

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN

Lưu ý rằng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
38. Nếu
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
39 đã sử dụng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
40 thay vì
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
41, NetStat sẽ hiển thị điều này:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
0

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
43, có nghĩa là tất cả các giao diện máy chủ có sẵn hỗ trợ họ địa chỉ sẽ được sử dụng để chấp nhận các kết nối đến. Trong ví dụ này,
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
5 đã được sử dụng (IPv4) trong cuộc gọi đến
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
7. Bạn có thể thấy điều này trong cột
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
46:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
47.

Đầu ra ở trên được cắt để chỉ hiển thị máy chủ Echo. Bạn có thể thấy nhiều đầu ra hơn, tùy thuộc vào hệ thống mà bạn đang chạy nó. Những điều cần chú ý là các cột

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
46,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
50. Trong ví dụ cuối cùng ở trên, NetStat cho thấy máy chủ Echo đang sử dụng ổ cắm IPv4 TCP (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
47), trên cổng 65432 trên tất cả các giao diện (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
43) và nó ở trạng thái nghe (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
53).

Một cách khác để truy cập này, cùng với thông tin hữu ích bổ sung, là sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
54 (liệt kê các tệp mở). Nó có sẵn theo mặc định trên macOS và có thể được cài đặt trên Linux bằng cách sử dụng trình quản lý gói của bạn, nếu nó chưa có:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
1

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
54 cung cấp cho bạn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
56,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
57 (ID quy trình) và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
58 (ID người dùng) của ổ cắm Internet mở khi được sử dụng với tùy chọn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
59. Trên đây là quá trình máy chủ Echo.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
36 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
54 có sẵn nhiều tùy chọn và khác nhau tùy thuộc vào hệ điều hành mà bạn đang chạy chúng. Kiểm tra trang
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
62 hoặc tài liệu cho cả hai. Họ chắc chắn đáng để dành một ít thời gian với và làm quen. Bạn sẽ được khen thưởng. Trên macOS và Linux, sử dụng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
63 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
64. Đối với Windows, sử dụng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
65.

Ở đây, một lỗi phổ biến mà bạn sẽ gặp phải khi một nỗ lực kết nối được thực hiện vào một cổng không có ổ cắm nghe:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
2

Số cổng được chỉ định là sai hoặc máy chủ đang chạy. Hoặc có thể có một tường lửa trên đường dẫn mà chặn kết nối, có thể dễ dàng quên đi. Bạn cũng có thể thấy lỗi

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
66. Nhận một quy tắc tường lửa được thêm vào cho phép khách hàng kết nối với cổng TCP!

Có một danh sách các lỗi phổ biến trong phần tham chiếu.

Sự cố giao tiếp

Bây giờ bạn sẽ xem xét kỹ hơn về cách máy khách và máy chủ giao tiếp với nhau:

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

Khi sử dụng giao diện loopback (địa chỉ IPv4

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc địa chỉ IPv6
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68), dữ liệu không bao giờ rời khỏi máy chủ hoặc chạm vào mạng bên ngoài. Trong sơ đồ trên, giao diện loopback được chứa bên trong máy chủ. Điều này thể hiện bản chất bên trong của giao diện loopback và cho thấy rằng các kết nối và dữ liệu quá cảnh là cục bộ đến máy chủ. Đây là lý do tại sao bạn cũng sẽ nghe thấy giao diện loopback và địa chỉ IP
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68 được gọi là Local localhost.

Các ứng dụng sử dụng giao diện Loopback để liên lạc với các quy trình khác chạy trên máy chủ và để bảo mật và cách ly khỏi mạng bên ngoài. Bởi vì nó nội bộ và chỉ có thể truy cập từ bên trong máy chủ, nên nó không bị lộ.

Bạn có thể thấy điều này trong hành động nếu bạn có một máy chủ ứng dụng sử dụng cơ sở dữ liệu riêng của nó. Nếu nó không phải là cơ sở dữ liệu được sử dụng bởi các máy chủ khác, thì nó có thể được cấu hình để nghe các kết nối trên giao diện Loopback. Nếu đây là trường hợp, các máy chủ khác trên mạng có thể kết nối với nó.

Khi bạn sử dụng địa chỉ IP khác ngoài

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68 trong các ứng dụng của bạn, nó có thể bị ràng buộc với giao diện Ethernet mà kết nối với mạng bên ngoài. Đây là cửa ngõ của bạn đến các máy chủ khác bên ngoài vương quốc Local Localhost của bạn:

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

Hãy cẩn thận ở ngoài đó. Nó là một thế giới khó chịu, độc ác. Hãy chắc chắn đọc phần bằng cách sử dụng tên máy chủ trước khi mạo hiểm từ giới hạn an toàn của địa phương. Có một ghi chú bảo mật áp dụng ngay cả khi bạn không sử dụng tên máy chủ nhưng chỉ sử dụng địa chỉ IP.

Xử lý nhiều kết nối

Máy chủ Echo chắc chắn có những hạn chế của nó. Điều lớn nhất là nó chỉ phục vụ một khách hàng và sau đó thoát ra. Khách hàng Echo cũng có giới hạn này, nhưng có một vấn đề bổ sung. Khi khách hàng sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
31, có thể nó sẽ chỉ trả về một byte,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
74 từ
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
75:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
3

Đối số

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
76 của
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
04 được sử dụng ở trên là lượng dữ liệu tối đa được nhận cùng một lúc. Nó không có nghĩa là
$ python echo-client.py 
Received b'Hello, world'
4 sẽ trả lại
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
04 byte.

Phương pháp

$ python echo-client.py 
Received b'Hello, world'
3 cũng hành xử theo cách này. Nó trả về số byte được gửi, có thể nhỏ hơn kích thước của dữ liệu được truyền vào. Bạn có trách nhiệm kiểm tra điều này và gọi
$ python echo-client.py 
Received b'Hello, world'
3 nhiều lần khi cần gửi tất cả dữ liệu:

Các ứng dụng có trách nhiệm kiểm tra xem tất cả dữ liệu đã được gửi; Nếu chỉ có một số dữ liệu được truyền đi, ứng dụng cần thử phân phối dữ liệu còn lại. (Nguồn)

Trong ví dụ trên, bạn đã tránh phải làm điều này bằng cách sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
82:

Không giống như Send (), phương thức này tiếp tục gửi dữ liệu từ byte cho đến khi tất cả dữ liệu đã được gửi hoặc xảy ra lỗi.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
83 được trả lại trên thành công. (Nguồn)

Bạn có hai vấn đề vào thời điểm này:

  • Làm thế nào để bạn xử lý nhiều kết nối đồng thời?
  • Bạn cần gọi
    $ python echo-client.py 
    Received b'Hello, world'
    
    3 và
    $ python echo-client.py 
    Received b'Hello, world'
    
    4 cho đến khi tất cả dữ liệu được gửi hoặc nhận.

Bạn có thể làm gì? Có nhiều cách tiếp cận để đồng thời. Một cách tiếp cận phổ biến là sử dụng I/O không đồng bộ.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
86 đã được đưa vào thư viện tiêu chuẩn trong Python 3.4. Sự lựa chọn truyền thống là sử dụng chủ đề.

Vấn đề với sự đồng thời là nó khó có thể đi đúng. Có nhiều sự tinh tế để xem xét và bảo vệ chống lại. Tất cả chỉ là một trong những điều này để thể hiện chính nó và ứng dụng của bạn có thể đột nhiên thất bại theo những cách không tinh tế.

Điều này có nghĩa là khiến bạn sợ hãi khi học và sử dụng lập trình đồng thời. Nếu ứng dụng của bạn cần mở rộng quy mô, thì đó là một điều cần thiết nếu bạn muốn sử dụng nhiều hơn một bộ xử lý hoặc một lõi. Tuy nhiên, đối với hướng dẫn này, bạn sẽ sử dụng một cái gì đó mà thậm chí còn truyền thống hơn các chủ đề và dễ lý luận hơn. Bạn sẽ sử dụng ông nội của các cuộc gọi hệ thống:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87.

Phương pháp

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 cho phép bạn kiểm tra hoàn thành I/O trên nhiều ổ cắm. Vì vậy, bạn có thể gọi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 để xem ổ cắm nào có I/O sẵn sàng để đọc và/hoặc viết. Nhưng đây là Python, vì vậy có nhiều hơn nữa. Bạn sẽ sử dụng mô -đun bộ chọn trong thư viện tiêu chuẩn để việc triển khai hiệu quả nhất được sử dụng, bất kể hệ điều hành mà bạn tình cờ đang chạy:

Mô-đun này cho phép ghép kênh I/O cấp cao và hiệu quả, được xây dựng trên các nguyên thủy mô-đun chọn. Thay vào đó, người dùng được khuyến khích sử dụng mô-đun này, trừ khi họ muốn kiểm soát chính xác các nguyên thủy cấp hệ điều hành được sử dụng. (Nguồn)

Tuy nhiên, bằng cách sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87, bạn không thể chạy đồng thời. Điều đó nói rằng, tùy thuộc vào khối lượng công việc của bạn, phương pháp này có thể vẫn rất nhanh. Nó phụ thuộc vào những gì ứng dụng của bạn cần làm khi nó phục vụ một yêu cầu và số lượng khách hàng cần hỗ trợ.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
86 sử dụng đa nhiệm hợp tác đơn luồng và vòng lặp sự kiện để quản lý các tác vụ. Với
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87, bạn sẽ viết phiên bản của một vòng lặp sự kiện, mặc dù đơn giản và đồng bộ hơn. Khi sử dụng nhiều luồng, mặc dù bạn có đồng thời, hiện tại bạn phải sử dụng GIL (khóa phiên dịch toàn cầu) với CPython và Pypy. Điều này có hiệu quả giới hạn số lượng công việc bạn có thể làm song song.

Đây là tất cả để nói rằng sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 có thể là một lựa chọn hoàn toàn tốt. Don cảm thấy như bạn phải sử dụng
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
86, chủ đề hoặc thư viện không đồng bộ mới nhất. Thông thường, trong một ứng dụng mạng, ứng dụng của bạn dù sao cũng bị ràng buộc I/O: nó có thể đang chờ trên mạng cục bộ, cho các điểm cuối ở phía bên kia của mạng, cho đĩa ghi, v.v.

Nếu bạn nhận được yêu cầu từ các khách hàng bắt đầu công việc ràng buộc CPU, hãy xem mô -đun đồng thời. Nó chứa lớp ProcessPoolExecutor, sử dụng một nhóm quy trình để thực hiện các cuộc gọi không đồng bộ.

Nếu bạn sử dụng nhiều quy trình, hệ điều hành có thể lên lịch mã Python của bạn để chạy song song trên nhiều bộ xử lý hoặc lõi, mà không cần Gil. Để biết ý tưởng và cảm hứng, hãy xem Pycon Talk John Reese - Suy nghĩ bên ngoài Gil với Asyncio và Multiprocessing - Pycon 2018.

Trong phần tiếp theo, bạn sẽ xem xét các ví dụ về một máy chủ và máy khách giải quyết các vấn đề này. Họ sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 để xử lý đồng thời nhiều kết nối và gọi
$ python echo-client.py 
Received b'Hello, world'
3 và
$ python echo-client.py 
Received b'Hello, world'
4 bao nhiêu lần nếu cần.

Máy khách và máy chủ đa kết nối

Trong hai phần tiếp theo, bạn sẽ tạo một máy chủ và máy khách xử lý nhiều kết nối bằng cách sử dụng đối tượng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
98 được tạo từ mô -đun chọn lọc.

Máy chủ đa kết nối

Đầu tiên, chuyển sự chú ý của bạn sang máy chủ đa kết nối. Phần đầu tiên thiết lập ổ cắm nghe:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
4

Sự khác biệt lớn nhất giữa máy chủ này và máy chủ Echo là cuộc gọi đến

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
99 để định cấu hình ổ cắm ở chế độ không chặn. Các cuộc gọi được thực hiện cho ổ cắm này sẽ không còn chặn. Khi nó được sử dụng với
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
00, như bạn sẽ thấy bên dưới, bạn có thể đợi các sự kiện trên một hoặc nhiều ổ cắm và sau đó đọc và ghi dữ liệu khi nó sẵn sàng.

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01 đăng ký ổ cắm được theo dõi với
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
00 cho các sự kiện mà bạn quan tâm. Đối với ổ cắm nghe, bạn muốn đọc các sự kiện:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
03.

Để lưu trữ bất kỳ dữ liệu tùy ý nào mà bạn thích cùng với ổ cắm, bạn sẽ sử dụng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04. Nó đã trở lại khi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 trở lại. Bạn sẽ sử dụng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04 để theo dõi những gì đã được gửi và nhận trên ổ cắm.

Tiếp theo là vòng lặp sự kiện:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
5

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
07 Khối cho đến khi có ổ cắm sẵn sàng cho I/O. Nó trả về một danh sách các bộ dữ liệu, một cho mỗi ổ cắm. Mỗi tuple chứa một
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
08 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
09.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
08 là một selectorKey
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
11 chứa thuộc tính
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
12.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
13 là đối tượng ổ cắm và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
09 là mặt nạ sự kiện của các hoạt động đã sẵn sàng.

Nếu

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
15 là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
83, thì bạn sẽ biết nó từ ổ cắm nghe và bạn cần chấp nhận kết nối. Bạn sẽ gọi chức năng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
17 của riêng bạn để lấy đối tượng ổ cắm mới và đăng ký nó bằng bộ chọn. Bạn sẽ nhìn vào điều đó trong một khoảnh khắc.

Nếu

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
15 không phải là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
83, thì bạn sẽ biết đó là một ổ cắm khách hàng mà Lừa đã được chấp nhận và bạn cần phải phục vụ nó.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
20 sau đó được gọi với
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
08 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
09 làm đối số, và đó là mọi thứ bạn cần để vận hành trên ổ cắm.

Đây là những gì chức năng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
17 của bạn làm:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
6

Bởi vì ổ cắm nghe đã được đăng ký cho sự kiện

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
03, nên nó sẽ sẵn sàng để đọc. Bạn gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
25 và sau đó gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
26 để đặt ổ cắm ở chế độ không chặn.

Hãy nhớ rằng, đây là mục tiêu chính trong phiên bản này của máy chủ vì bạn không muốn nó chặn. Nếu nó chặn, thì toàn bộ máy chủ bị đình trệ cho đến khi nó trở lại. Điều đó có nghĩa là các ổ cắm khác được chờ đợi mặc dù máy chủ không tích cực hoạt động. Đây là trạng thái Hang Hang đáng sợ mà bạn không muốn máy chủ của bạn được sử dụng.

Tiếp theo, bạn tạo một đối tượng để giữ dữ liệu mà bạn muốn bao gồm cùng với ổ cắm bằng cách sử dụng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
27. Bởi vì bạn muốn biết khi nào kết nối máy khách đã sẵn sàng để đọc và viết, cả hai sự kiện đó đều được đặt với bitwise hoặc toán tử:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
7

Mặt nạ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
28, ổ cắm và đối tượng dữ liệu sau đó được truyền đến
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01.

Bây giờ hãy xem

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
20 để xem kết nối máy khách được xử lý như thế nào khi nó sẵn sàng:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
8

Đây là trung tâm của máy chủ đa kết nối đơn giản.

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
08 là
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
11 được trả về từ
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 có chứa đối tượng ổ cắm (
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
12) và đối tượng dữ liệu.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
09 chứa các sự kiện đã sẵn sàng.

Nếu ổ cắm đã sẵn sàng để đọc, thì

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
36 sẽ đánh giá thành
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
37, do đó
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
38 được gọi. Bất kỳ dữ liệu nào mà đọc được được thêm vào
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
39 để nó có thể được gửi sau.

Lưu ý khối

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
40 để kiểm tra xem không có dữ liệu nào được nhận:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
9

Nếu không nhận được dữ liệu, điều này có nghĩa là máy khách đã đóng ổ cắm của họ, vì vậy máy chủ cũng vậy. Nhưng don không quên gọi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
41 trước khi đóng, vì vậy, nó không còn được theo dõi bởi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87.

Khi ổ cắm đã sẵn sàng để viết, điều này phải luôn luôn là trường hợp của ổ cắm khỏe mạnh, bất kỳ dữ liệu nào nhận được được lưu trữ trong

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
39 đều được lặp lại với máy khách bằng cách sử dụng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
44. Các byte được gửi sau đó được xóa khỏi bộ đệm gửi:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
0

Phương thức

$ python echo-client.py 
Received b'Hello, world'
3 trả về số byte được gửi. Số này sau đó có thể được sử dụng với ký hiệu lát cắt trên bộ đệm
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
46 để loại bỏ các byte được gửi.

Máy khách đa kết nối

Bây giờ hãy xem máy khách đa kết nối,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
47. Nó rất giống với máy chủ, nhưng thay vì nghe các kết nối, nó bắt đầu bằng cách bắt đầu kết nối thông qua
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
48:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
1

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
49 được đọc từ dòng lệnh và là số lượng kết nối để tạo cho máy chủ. Giống như máy chủ, mỗi ổ cắm được đặt thành chế độ không chặn.

Bạn sử dụng

$ python echo-client.py 
Received b'Hello, world'
2 thay vì
$ python echo-client.py 
Received b'Hello, world'
1 vì
$ python echo-client.py 
Received b'Hello, world'
1 sẽ ngay lập tức tăng ngoại lệ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
53. Phương thức
$ python echo-client.py 
Received b'Hello, world'
2 ban đầu trả về một chỉ báo lỗi,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
55, thay vì nêu ra một ngoại lệ sẽ can thiệp vào kết nối đang diễn ra. Sau khi kết nối hoàn thành, ổ cắm đã sẵn sàng để đọc và viết và được trả lại bởi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87.

Sau khi ổ cắm được thiết lập, dữ liệu bạn muốn lưu trữ với ổ cắm được tạo bằng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
27. Các tin nhắn mà máy khách sẽ gửi đến máy chủ được sao chép bằng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
58 vì mỗi kết nối sẽ gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 và sửa đổi danh sách. Mọi thứ cần thiết để theo dõi những gì khách hàng cần gửi, đã gửi và nhận được, bao gồm tổng số byte trong tin nhắn, được lưu trữ trong đối tượng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04.

Kiểm tra các thay đổi được thực hiện từ máy chủ ____ ____220 cho phiên bản máy khách:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
2

Nó về cơ bản giống nhau nhưng đối với một sự khác biệt quan trọng. Máy khách theo dõi số lượng byte mà nó nhận được từ máy chủ để có thể đóng bên cạnh kết nối. Khi máy chủ phát hiện ra điều này, nó cũng đóng mặt của kết nối.

Lưu ý rằng bằng cách thực hiện điều này, máy chủ phụ thuộc vào máy khách được hành động tốt: máy chủ hy vọng máy khách sẽ đóng phía kết nối của nó khi nó thực hiện gửi tin nhắn. Nếu máy khách không đóng, máy chủ sẽ để kết nối mở. Trong một ứng dụng thực sự, bạn có thể muốn bảo vệ chống lại điều này trong máy chủ của mình bằng cách triển khai thời gian chờ để ngăn chặn các kết nối máy khách tích lũy nếu họ không gửi yêu cầu sau một khoảng thời gian nhất định.

Chạy máy khách và máy chủ đa kết nối

Bây giờ, thời gian để chạy

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
62 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
47. Cả hai đều sử dụng các đối số dòng lệnh. Bạn có thể chạy chúng mà không cần đối số để xem các tùy chọn.

Đối với máy chủ, hãy vượt qua số

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
3

Đối với máy khách, cũng chuyển số lượng kết nối để tạo cho máy chủ,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
66:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
4

Dưới đây là đầu ra của máy chủ khi nghe trên giao diện loopback trên cổng 65432:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
5

Dưới đây là đầu ra của máy khách khi nó tạo hai kết nối với máy chủ ở trên:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
6

Tuyệt quá! Bây giờ bạn đã chạy máy khách và máy chủ đa kết nối. Trong phần tiếp theo, bạn sẽ lấy ví dụ này hơn nữa.

Ứng dụng khách hàng và máy chủ

Ví dụ về máy khách và máy chủ đa kết nối chắc chắn là một cải tiến so với nơi bạn bắt đầu. Tuy nhiên, bây giờ bạn có thể thực hiện thêm một bước và giải quyết các thiếu sót của ví dụ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
67 trước đó trong một triển khai cuối cùng: máy khách và máy chủ ứng dụng.

Bạn muốn một máy khách và máy chủ xử lý các lỗi một cách thích hợp để các kết nối khác bị ảnh hưởng. Rõ ràng, khách hàng hoặc máy chủ của bạn không nên rơi xuống trong một quả bóng giận dữ nếu một ngoại lệ không bị bắt. Đây là điều mà bạn đã phải lo lắng cho đến bây giờ, bởi vì các ví dụ đã cố tình bỏ qua việc xử lý lỗi cho sự ngắn gọn và rõ ràng.

Bây giờ, bạn đã quen thuộc với API cơ bản, ổ cắm không chặn và

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87, bạn có thể thêm một số xử lý lỗi và giải quyết con voi trong phòng, mà các ví dụ đã giấu bạn đằng sau bức màn lớn ở đó. Hãy nhớ rằng lớp tùy chỉnh đã được đề cập trở lại trong phần giới thiệu? Đó là những gì bạn sẽ khám phá tiếp theo.

Đầu tiên, bạn sẽ giải quyết các lỗi:

Tất cả các lỗi đều tăng ngoại lệ. Các ngoại lệ bình thường cho các loại đối số không hợp lệ và các điều kiện ngoài bộ nhớ có thể được nêu ra; Bắt đầu từ Python 3.3, các lỗi liên quan đến ổ cắm hoặc ngữ nghĩa địa chỉ tăng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
69 hoặc một trong các lớp con của nó. (Nguồn)

Vì vậy, một điều bạn cần làm là bắt

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
69. Một cân nhắc quan trọng khác liên quan đến lỗi là thời gian chờ. Bạn sẽ thấy họ được thảo luận ở nhiều nơi trong tài liệu. Thời gian chờ xảy ra và là một lỗi được gọi là lỗi bình thường. Máy chủ và bộ định tuyến được khởi động lại, các cổng chuyển đổi xấu, dây cáp trở nên xấu, dây cáp bị rút phích cắm, bạn đặt tên cho nó. Bạn nên chuẩn bị cho những lỗi này và các lỗi khác, xử lý chúng trong mã của bạn.timeouts. You’ll see them discussed in many places in the documentation. Timeouts happen and are a so-called normal error. Hosts and routers are rebooted, switch ports go bad, cables go bad, cables get unplugged, you name it. You should be prepared for these and other errors, handling them in your code.

Còn con voi trong phòng thì sao? Như được gợi ý bởi loại ổ cắm

$ python echo-client.py 
Received b'Hello, world'
7, khi sử dụng TCP, bạn đã đọc từ một dòng byte liên tục. Nó giống như đọc từ một tệp trên đĩa, nhưng thay vào đó, bạn đã đọc các byte từ mạng. Tuy nhiên, không giống như đọc một tệp, ở đó không có
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
72.

Nói cách khác, bạn có thể định vị lại vị trí con trỏ ổ cắm, nếu có một, và di chuyển xung quanh dữ liệu.

Khi byte đến ổ cắm của bạn, có bộ đệm mạng liên quan. Một khi bạn đã đọc chúng, chúng cần được lưu ở đâu đó, nếu không bạn sẽ bỏ chúng. Gọi

$ python echo-client.py 
Received b'Hello, world'
4 một lần nữa đọc luồng byte tiếp theo có sẵn từ ổ cắm.

Bạn sẽ đọc từ ổ cắm trong các khối. Vì vậy, bạn cần gọi

$ python echo-client.py 
Received b'Hello, world'
4 và lưu dữ liệu trong bộ đệm cho đến khi bạn đọc đủ byte để có một thông báo hoàn chỉnh có ý nghĩa với ứng dụng của bạn.

Nó tùy thuộc vào bạn để xác định và theo dõi vị trí của các ranh giới thông điệp. Theo như ổ cắm TCP, nó chỉ gửi và nhận các byte thô đến và từ mạng. Nó không biết gì về những byte thô đó có nghĩa là gì.

Đây là lý do tại sao bạn cần xác định giao thức lớp ứng dụng. Những gì một giao thức lớp ứng dụng? Nói một cách đơn giản, ứng dụng của bạn sẽ gửi và nhận tin nhắn. Định dạng của các tin nhắn này là giao thức ứng dụng của bạn.

Nói cách khác, độ dài và định dạng mà bạn chọn cho các thông báo này xác định ngữ nghĩa và hành vi của ứng dụng của bạn. Điều này liên quan trực tiếp đến những gì bạn đã học trong đoạn trước về việc đọc byte từ ổ cắm. Khi bạn đọc các byte với

$ python echo-client.py 
Received b'Hello, world'
4, bạn cần theo kịp số lượng byte đã được đọc và tìm ra ranh giới thông điệp ở đâu.

Làm thế nào bạn có thể làm điều này? Một cách là luôn luôn gửi tin nhắn có độ dài cố định. Nếu họ luôn luôn có cùng kích thước, thì nó dễ dàng. Khi bạn đọc số byte đó vào bộ đệm, thì bạn biết bạn có một tin nhắn đầy đủ.

Tuy nhiên, việc sử dụng các tin nhắn có độ dài cố định là không hiệu quả cho các tin nhắn nhỏ nơi bạn cần sử dụng đệm để điền vào chúng. Ngoài ra, bạn vẫn còn để lại vấn đề phải làm gì về dữ liệu không phù hợp với một tin nhắn.

Trong hướng dẫn này, bạn sẽ học được một cách tiếp cận chung, một cách mà sử dụng bởi nhiều giao thức, bao gồm cả HTTP. Bạn có thể các tin nhắn tiền tố có tiêu đề bao gồm độ dài nội dung cũng như bất kỳ trường nào khác bạn cần. Bằng cách này, bạn sẽ chỉ cần theo kịp tiêu đề. Khi bạn đã đọc tiêu đề, bạn có thể xử lý nó để xác định độ dài của nội dung tin nhắn. Với độ dài nội dung, sau đó bạn có thể đọc số byte đó để tiêu thụ nó.header that includes the content length as well as any other fields you need. By doing this, you’ll only need to keep up with the header. Once you’ve read the header, you can process it to determine the length of the message’s content. With the content length, you can then read that number of bytes to consume it.

Bạn sẽ thực hiện điều này bằng cách tạo một lớp tùy chỉnh có thể gửi và nhận tin nhắn có chứa dữ liệu văn bản hoặc nhị phân. Bạn có thể cải thiện và mở rộng lớp này cho các ứng dụng của riêng bạn. Điều quan trọng nhất là bạn sẽ có thể thấy một ví dụ về cách thực hiện điều này.

Trước khi bạn bắt đầu, có một cái gì đó bạn cần biết về ổ cắm và byte. Như bạn đã học trước đó, khi gửi và nhận dữ liệu qua ổ cắm, bạn đã gửi và nhận các byte thô.

Nếu bạn nhận được dữ liệu và muốn sử dụng nó trong một bối cảnh mà nó được giải thích là nhiều byte, ví dụ như một số nguyên 4 byte, bạn sẽ cần phải tính đến rằng nó có thể ở định dạng mà không có nguồn gốc từ máy CPU của bạn. Máy khách hoặc máy chủ ở đầu bên kia có thể có CPU sử dụng đơn đặt hàng byte khác với của bạn. Nếu đây là trường hợp, thì bạn sẽ cần phải chuyển đổi nó sang thứ tự byte gốc của máy chủ trước khi sử dụng nó.

Thứ tự byte này được gọi là một endianness CPU. Xem byte endianness trong phần tham chiếu để biết chi tiết. Bạn sẽ tránh được vấn đề này bằng cách tận dụng Unicode cho tiêu đề tin nhắn của bạn và sử dụng UTF-8 mã hóa. Vì UTF-8 sử dụng mã hóa 8 bit, không có vấn đề đặt hàng byte nào.

Bạn có thể tìm thấy một lời giải thích trong mã hóa Python và tài liệu Unicode. Lưu ý rằng điều này chỉ áp dụng cho tiêu đề văn bản. Bạn sẽ sử dụng một loại rõ ràng và mã hóa được xác định trong tiêu đề cho nội dung mà Lừa được gửi, tải trọng tin nhắn. Điều này sẽ cho phép bạn chuyển bất kỳ dữ liệu nào mà bạn thích (văn bản hoặc nhị phân), ở bất kỳ định dạng nào.

Bạn có thể dễ dàng xác định thứ tự byte của máy bằng cách sử dụng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
76. Ví dụ, bạn có thể thấy một cái gì đó như thế này:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
7

Nếu bạn chạy điều này trong một máy ảo mô phỏng CPU lớn (PowerPC), thì một cái gì đó như thế này sẽ xảy ra:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
8

Trong ứng dụng ví dụ này, giao thức lớp ứng dụng của bạn định nghĩa tiêu đề là văn bản Unicode với mã hóa UTF-8. Đối với nội dung thực tế trong tin nhắn, tải trọng tin nhắn, bạn sẽ vẫn phải trao đổi thứ tự byte theo cách thủ công nếu cần.

Điều này sẽ phụ thuộc vào ứng dụng của bạn và liệu nó có cần xử lý dữ liệu nhị phân đa byte từ một máy có độ chính xác khác hay không. Bạn có thể giúp khách hàng hoặc máy chủ của mình triển khai hỗ trợ nhị phân bằng cách thêm các tiêu đề bổ sung và sử dụng chúng để truyền các tham số, tương tự như HTTP.

Don Tiết lo lắng nếu điều này không có ý nghĩa gì. Trong phần tiếp theo, bạn sẽ thấy tất cả những điều này hoạt động và phù hợp với nhau như thế nào.

Gửi tin nhắn ứng dụng

Vẫn còn một chút vấn đề. Bạn có một tiêu đề có độ dài thay đổi, rất đẹp và linh hoạt, nhưng làm thế nào để bạn biết độ dài của tiêu đề khi đọc nó với

$ python echo-client.py 
Received b'Hello, world'
4?

Khi bạn đã biết trước đây về việc sử dụng

$ python echo-client.py 
Received b'Hello, world'
4 và ranh giới tin nhắn, bạn cũng đã học được rằng các tiêu đề có độ dài cố định có thể không hiệu quả. Điều đó đúng, nhưng bạn sẽ sử dụng một tiêu đề nhỏ, 2 byte, có độ dài cố định để có tiền tố tiêu đề JSON chứa độ dài của nó.

Bạn có thể nghĩ về điều này như một cách tiếp cận lai để gửi tin nhắn. Trên thực tế, bạn đã khởi động quá trình nhận thông báo bằng cách gửi độ dài của tiêu đề trước. Điều này giúp người nhận của bạn dễ dàng giải mã thông điệp.

Để cung cấp cho bạn ý tưởng tốt hơn về định dạng tin nhắn, hãy xem toàn bộ tin nhắn:

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

Một thông báo bắt đầu với một tiêu đề có độ dài cố định của hai byte, là một số nguyên theo thứ tự byte mạng. Đây là độ dài của tiêu đề tiếp theo, tiêu đề JSON có độ dài thay đổi. Khi bạn đã đọc hai byte với

$ python echo-client.py 
Received b'Hello, world'
4, thì bạn biết bạn có thể xử lý hai byte dưới dạng số nguyên và sau đó đọc số byte đó trước khi giải mã tiêu đề UTF-8 JSON.

Tiêu đề JSON chứa một từ điển của các tiêu đề bổ sung. Một trong số đó là

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80, đó là số byte của nội dung tin nhắn (không bao gồm tiêu đề JSON). Khi bạn đã gọi
$ python echo-client.py 
Received b'Hello, world'
4 và đọc byte
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80, sau đó bạn đã đạt đến một ranh giới tin nhắn, có nghĩa là bạn đã đọc toàn bộ tin nhắn.

Lớp thông báo ứng dụng

Cuối cùng, tiền thưởng! Trong phần này, bạn sẽ nghiên cứu lớp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 và xem cách mà nó được sử dụng với
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 khi đọc và viết các sự kiện xảy ra trên ổ cắm.

Ứng dụng ví dụ này phản ánh những loại tin nhắn mà máy khách và máy chủ có thể sử dụng hợp lý. Bạn vượt xa khách hàng và máy chủ đồ chơi Echo tại thời điểm này!

Để giữ cho mọi thứ đơn giản và vẫn chứng minh mọi thứ sẽ hoạt động như thế nào trong một ứng dụng thực, ví dụ này sử dụng giao thức ứng dụng thực hiện một tính năng tìm kiếm cơ bản. Máy khách gửi yêu cầu tìm kiếm và máy chủ thực hiện tìm kiếm một trận đấu. Nếu yêu cầu được gửi bởi khách hàng được công nhận là tìm kiếm, máy chủ giả định rằng đó là một yêu cầu nhị phân và trả về phản hồi nhị phân.

Sau khi đọc các phần sau, chạy các ví dụ và thử nghiệm mã, bạn sẽ thấy mọi thứ hoạt động như thế nào. Sau đó, bạn có thể sử dụng lớp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 làm điểm bắt đầu và sửa đổi nó cho mục đích sử dụng của riêng bạn.

Ứng dụng này không xa so với ví dụ máy khách và máy chủ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
67. Mã vòng lặp sự kiện vẫn giữ nguyên trong
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
87 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
88. Những gì bạn sẽ làm là chuyển mã tin nhắn vào một lớp có tên
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 và thêm các phương thức để hỗ trợ đọc, viết và xử lý các tiêu đề và nội dung. Đây là một ví dụ tuyệt vời để sử dụng một lớp.

Như bạn đã biết trước đây và bạn sẽ thấy bên dưới, làm việc với các ổ cắm liên quan đến việc giữ trạng thái. Bằng cách sử dụng một lớp, bạn giữ tất cả trạng thái, dữ liệu và mã được gói cùng nhau trong một đơn vị có tổ chức. Một thể hiện của lớp được tạo cho mỗi ổ cắm trong máy khách và máy chủ khi kết nối được bắt đầu hoặc chấp nhận.

Lớp chủ yếu giống nhau cho cả máy khách và máy chủ cho các phương thức trình bao bọc và tiện ích. Họ bắt đầu với một dấu gạch dưới, như

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
90. Các phương pháp này đơn giản hóa làm việc với lớp. Họ giúp đỡ các phương pháp khác bằng cách cho phép chúng ngắn hơn và hỗ trợ nguyên tắc khô.

Lớp máy chủ ____ ____283 hoạt động theo cách tương tự như máy khách và ngược lại. Sự khác biệt là máy khách bắt đầu kết nối và gửi thông báo yêu cầu, theo sau là xử lý thông báo phản hồi máy chủ. Ngược lại, máy chủ chờ kết nối, xử lý thông báo yêu cầu của máy khách và sau đó gửi tin nhắn phản hồi.

Có vẻ như thế này:

BươcĐiểm cuốiNội dung hành động / tin nhắn
1 Khách hàngGửi nội dung yêu cầu chứa
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
2 Người phục vụNhận và xử lý yêu cầu khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
3 Người phục vụNhận và xử lý yêu cầu khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
4 Khách hàngGửi nội dung yêu cầu chứa
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83

Người phục vụ

Nhận và xử lý yêu cầu khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Gửi nội dung phản hồi có chứa
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Nhận và xử lý phản hồi máy chủ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Người phục vụNhận và xử lý yêu cầu khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Gửi nội dung phản hồi có chứa
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Người phục vụNhận và xử lý yêu cầu khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Gửi nội dung phản hồi có chứa
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83
Khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
87
Kịch bản chính của khách hàng
Khách hàng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
00
Lớp khách hàng ____ ____283

Điểm vào tin nhắn

Hiểu cách thức hoạt động của lớp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 có thể là một thách thức bởi vì có một khía cạnh của thiết kế của nó có thể không rõ ràng ngay lập tức. Tại sao? Quản lý trạng thái.

Sau khi một đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được tạo, nó liên kết với một ổ cắm mà theo dõi các sự kiện bằng cách sử dụng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
04:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
9

Khi các sự kiện đã sẵn sàng trên ổ cắm, họ đã được trả lại bởi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
05. Sau đó, bạn có thể lấy lại tham chiếu trở lại đối tượng tin nhắn bằng thuộc tính
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04 trên đối tượng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
08 và gọi một phương thức trong
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
0

Nhìn vào vòng lặp sự kiện ở trên, bạn sẽ thấy rằng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
00 nằm ở ghế lái. Nó chặn, chờ đợi ở đầu vòng lặp cho các sự kiện. Nó có trách nhiệm thức dậy khi đọc và viết các sự kiện đã sẵn sàng để được xử lý trên ổ cắm. Điều đó có nghĩa là, một cách gián tiếp, nó cũng chịu trách nhiệm gọi phương thức
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10. Đó là lý do tại sao
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10 là điểm nhập cảnh.

Ở đây, những gì phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10 làm:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
1

Điều đó tốt:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10 rất đơn giản. Nó chỉ có thể làm hai điều: Gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
14 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15.

Đây là nơi quản lý trạng thái xuất hiện. Nếu một phương pháp khác phụ thuộc vào các biến trạng thái có một giá trị nhất định, thì chúng sẽ chỉ được gọi từ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
14 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15. Điều này giữ cho logic đơn giản nhất có thể như các sự kiện xuất hiện trên ổ cắm để xử lý.

Bạn có thể bị cám dỗ sử dụng hỗn hợp một số phương thức kiểm tra các biến trạng thái hiện tại và, tùy thuộc vào giá trị của chúng, hãy gọi các phương thức khác để xử lý dữ liệu bên ngoài

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
14 hoặc
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15. Cuối cùng, điều này có thể sẽ chứng minh quá phức tạp để quản lý và theo kịp.

Bạn chắc chắn nên sửa đổi lớp cho phù hợp với nhu cầu của riêng bạn để nó phù hợp nhất với bạn, nhưng có lẽ bạn sẽ có kết quả tốt nhất nếu bạn kiểm tra trạng thái và các cuộc gọi đến các phương thức phụ thuộc vào trạng thái đó với các phương thức

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
14 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15 nếu có thể.

Bây giờ hãy nhìn vào

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
14. Đây là phiên bản máy chủ, nhưng máy khách là như nhau. Nó chỉ sử dụng một tên phương thức khác,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
23 thay vì
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
24:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
2

Phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
25 được gọi là đầu tiên. Nó gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26 để đọc dữ liệu từ ổ cắm và lưu trữ trong bộ đệm nhận.

Hãy nhớ rằng khi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26 được gọi, tất cả các dữ liệu tạo nên một tin nhắn hoàn chỉnh có thể chưa đến.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26 có thể cần được gọi lại. Đây là lý do tại sao có kiểm tra trạng thái cho từng phần của tin nhắn trước khi phương thức thích hợp để xử lý nó được gọi.

Trước khi một phương thức xử lý một phần của tin nhắn, trước tiên nó kiểm tra để đảm bảo đủ byte đã được đọc vào bộ đệm nhận. Nếu họ có, nó xử lý các byte tương ứng của nó, sẽ loại bỏ chúng khỏi bộ đệm và ghi đầu ra của nó vào một biến mà Lọ sử dụng bởi giai đoạn xử lý tiếp theo. Vì có ba thành phần cho một tin nhắn, có ba kiểm tra trạng thái và các cuộc gọi phương thức

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
29:

Thành phần tin nhắnPhương phápĐầu ra
Tiêu đề có độ dài cố định
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
30
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
31
Tiêu đề JSON
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
32
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
33
Nội dung
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
34
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
35

Tiếp theo, kiểm tra

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15. Đây là phiên bản máy chủ của máy chủ:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
3

Phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15 kiểm tra trước cho
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
38. Nếu một người tồn tại và một phản hồi đã được tạo ra,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
39 sẽ được gọi. Phương thức
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
39 đặt biến trạng thái
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
41 và ghi phản hồi cho bộ đệm gửi.

Phương thức

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42 gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 nếu có dữ liệu trong bộ đệm gửi.

Hãy nhớ rằng khi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 được gọi, tất cả dữ liệu trong bộ đệm gửi có thể không được xếp hàng để truyền. Các bộ đệm mạng cho ổ cắm có thể đầy và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 có thể cần được gọi lại. Đây là lý do tại sao có kiểm tra nhà nước. Phương pháp
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
39 chỉ nên được gọi là một lần, nhưng nó dự kiến ​​rằng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42 sẽ cần được gọi là nhiều lần.

Phiên bản máy khách của

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15 là tương tự:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
4

Vì máy khách bắt đầu kết nối với máy chủ và gửi yêu cầu trước, biến trạng thái

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
49 được kiểm tra. Nếu một yêu cầu đã được xếp hàng, nó sẽ gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
50. Phương thức
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
51 tạo yêu cầu và ghi nó vào bộ đệm gửi. Nó cũng đặt biến trạng thái
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
49 để nó chỉ được gọi một lần.

Giống như đối với máy chủ,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42 gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 nếu có dữ liệu trong bộ đệm gửi.

Sự khác biệt đáng chú ý trong phiên bản

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15 của máy khách là lần kiểm tra cuối cùng để xem liệu yêu cầu đã được xếp hàng. Điều này sẽ được giải thích nhiều hơn trong tập lệnh chính của khách hàng, nhưng lý do cho điều này là để nói với
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
05 để ngừng giám sát ổ cắm cho các sự kiện ghi. Nếu yêu cầu đã được xếp hàng và bộ đệm gửi trống, thì bạn đã viết xong và bạn chỉ quan tâm đến các sự kiện đọc. Không có lý do gì để được thông báo rằng ổ cắm có thể ghi được.

Để kết thúc phần này, hãy xem xét suy nghĩ này: Mục đích chính của phần này là giải thích rằng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
05 đang gọi vào lớp
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 thông qua phương pháp
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10 và để mô tả cách quản lý trạng thái.

Điều này rất quan trọng vì

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10 sẽ được gọi là nhiều lần trong suốt cuộc đời của kết nối. Do đó, hãy đảm bảo rằng bất kỳ phương thức nào chỉ nên được gọi một lần đều được kiểm tra một biến trạng thái hoặc biến trạng thái được đặt theo phương thức đều được người gọi kiểm tra.

Kịch bản chính của máy chủ

Trong tập lệnh chính của máy chủ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
88, các đối số được đọc từ dòng lệnh chỉ định giao diện và cổng để nghe:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
5

Ví dụ: để nghe trên giao diện loopback trên cổng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
62, nhập:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
6

Sử dụng một chuỗi trống cho

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
63 để nghe trên tất cả các giao diện.

Sau khi tạo ổ cắm, một cuộc gọi được thực hiện đến

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
64 với tùy chọn
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
65:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
7

Đặt tùy chọn ổ cắm này tránh lỗi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
66. Bạn sẽ thấy điều này khi khởi động máy chủ trên một cổng có các kết nối ở trạng thái Time_Wait.

Ví dụ: nếu máy chủ chủ động đóng kết nối, nó sẽ vẫn ở trạng thái

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
67 trong hai phút trở lên, tùy thuộc vào hệ điều hành. Nếu bạn cố gắng khởi động lại máy chủ trước khi trạng thái
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
67 hết hạn, thì bạn sẽ nhận được ngoại lệ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
69 là
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
66. Đây là một biện pháp bảo vệ để đảm bảo rằng bất kỳ gói nào bị trì hoãn trong mạng không được cung cấp cho ứng dụng sai.

Vòng lặp sự kiện bắt gặp bất kỳ lỗi nào để máy chủ có thể ở lại và tiếp tục chạy:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
8

Khi kết nối máy khách được chấp nhận, đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được tạo:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
9

Đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được liên kết với ổ cắm trong cuộc gọi đến
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01 và ban đầu được đặt để chỉ theo dõi các sự kiện đọc. Khi yêu cầu đã được đọc, bạn sẽ sửa đổi nó để chỉ nghe các sự kiện ghi.

Một lợi thế của việc thực hiện phương pháp này trong máy chủ là trong hầu hết các trường hợp, khi một ổ cắm khỏe mạnh và không có vấn đề về mạng, nó sẽ luôn có thể ghi được.

Nếu bạn đã nói

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01 cũng theo dõi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
75, thì vòng lặp sự kiện sẽ ngay lập tức thức dậy và thông báo cho bạn rằng đây là trường hợp. Tuy nhiên, tại thời điểm này, không có lý do gì để thức dậy và gọi
$ python echo-client.py 
Received b'Hello, world'
3 trên ổ cắm. Không có phản hồi nào để gửi, bởi vì một yêu cầu chưa được xử lý. Điều này sẽ tiêu thụ và chất thải chu kỳ CPU có giá trị.

Lớp tin nhắn máy chủ

Trong phần Nhập thông báo, bạn đã tìm hiểu cách đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được gọi là hành động khi các sự kiện ổ cắm đã sẵn sàng thông qua
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10. Bây giờ bạn sẽ tìm hiểu những gì xảy ra khi dữ liệu được đọc trên ổ cắm và một thành phần hoặc mảnh của thông báo đã sẵn sàng để được máy chủ xử lý.

Lớp thông báo máy chủ có trong

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
97, là một phần của mã nguồn bạn đã tải xuống trước đó. Bạn cũng có thể tải xuống mã bằng cách nhấp vào liên kết bên dưới:

Các phương thức xuất hiện trong lớp theo thứ tự mà việc xử lý diễn ra cho một tin nhắn.

Khi máy chủ đã đọc ít nhất hai byte, tiêu đề có độ dài cố định có thể được xử lý:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
0

Tiêu đề có độ dài cố định là một số nguyên 2 byte trong mạng, hoặc đơn đặt hàng Big-endian, byte. Nó chứa chiều dài của tiêu đề JSON. Bạn sẽ sử dụng struct.unpack () để đọc giá trị, giải mã nó và lưu trữ nó trong

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
31. Sau khi xử lý phần của thông báo mà nó chịu trách nhiệm,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
81 sẽ loại bỏ nó khỏi bộ đệm nhận.

Giống như với tiêu đề có độ dài cố định, khi có đủ dữ liệu trong bộ đệm nhận để chứa tiêu đề JSON, nó cũng có thể được xử lý:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
1

Phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
82 được gọi để giải mã và giảm giá tiêu đề JSON vào từ điển. Bởi vì tiêu đề JSON được định nghĩa là unicode với mã hóa UTF-8,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
83 được mã hóa cứng trong cuộc gọi. Kết quả được lưu vào
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
33. Sau khi xử lý phần của thông báo mà nó chịu trách nhiệm,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
32 sẽ loại bỏ nó khỏi bộ đệm nhận.

Tiếp theo là nội dung thực tế, hoặc tải trọng của tin nhắn. Nó được mô tả bởi tiêu đề JSON trong

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
33. Khi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80 byte có sẵn trong bộ đệm nhận, yêu cầu có thể được xử lý:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
2

Sau khi lưu nội dung tin nhắn vào biến

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
24 sẽ xóa nó khỏi bộ đệm nhận. Sau đó, nếu loại nội dung là JSON,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
24 giải mã và giải mã nó. Nếu nó không, ứng dụng ví dụ này giả định rằng nó là một yêu cầu nhị phân và chỉ cần in loại nội dung.

Điều cuối cùng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
24 không phải là sửa đổi bộ chọn để chỉ giám sát các sự kiện ghi. Trong tập lệnh chính của máy chủ,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
88, ổ cắm ban đầu được đặt để giám sát các sự kiện đọc. Bây giờ yêu cầu đã được xử lý đầy đủ, bạn không còn quan tâm đến việc đọc.

Một phản hồi bây giờ có thể được tạo và ghi vào ổ cắm. Khi ổ cắm có thể ghi,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
39 được gọi từ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
3

Một phản hồi được tạo bằng cách gọi các phương thức khác, tùy thuộc vào loại nội dung. Trong ứng dụng ví dụ này, việc tra cứu từ điển đơn giản được thực hiện cho các yêu cầu JSON khi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
95. Đối với các ứng dụng của riêng bạn, bạn có thể xác định các phương pháp khác được gọi ở đây.

Sau khi tạo thông báo phản hồi, biến trạng thái

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
96 được đặt sao cho
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
15 không gọi lại
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
39. Cuối cùng, phản hồi được thêm vào bộ đệm gửi. Điều này được nhìn thấy và gửi qua
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42.

Một chút khó để tìm ra là làm thế nào để đóng kết nối sau khi phản hồi được viết. Bạn có thể thực hiện cuộc gọi đến

$ python echo-client.py 
Received b'Hello, world'
5 trong phương thức
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
4

Mặc dù nó hơi ẩn, nhưng đây là một sự đánh đổi có thể chấp nhận được khi lớp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 chỉ xử lý một tin nhắn trên mỗi kết nối. Sau khi phản hồi được viết, không còn gì để máy chủ làm. Nó đã hoàn thành công việc của mình.

Tập lệnh chính của khách hàng

Trong tập lệnh chính của máy khách,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
87, các đối số được đọc từ dòng lệnh và được sử dụng để tạo yêu cầu và khởi động kết nối với máy chủ:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
5

Đây là một ví dụ:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
6

Sau khi tạo một từ điển đại diện cho yêu cầu từ các đối số dòng lệnh, máy chủ, cổng và từ điển yêu cầu được chuyển đến

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
04:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
7

Một ổ cắm được tạo cho kết nối máy chủ, cũng như đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 bằng từ điển
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
38.

Giống như đối với máy chủ, đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được liên kết với ổ cắm trong cuộc gọi đến
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01. Tuy nhiên, đối với máy khách, ổ cắm ban đầu được thiết lập để được theo dõi cho cả các sự kiện đọc và viết. Khi yêu cầu đã được viết, bạn sẽ sửa đổi nó để chỉ nghe các sự kiện đọc.

Cách tiếp cận này mang lại cho bạn lợi thế tương tự như máy chủ: không lãng phí chu kỳ CPU. Sau khi yêu cầu đã được gửi, bạn không còn quan tâm đến các sự kiện viết, vì vậy, không có lý do gì để thức dậy và xử lý chúng.

Lớp tin nhắn khách hàng

Trong phần Nhập thông báo, bạn đã tìm hiểu làm thế nào đối tượng tin nhắn được gọi là hành động khi các sự kiện ổ cắm đã sẵn sàng thông qua

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
10. Bây giờ bạn sẽ tìm hiểu những gì xảy ra sau khi dữ liệu được đọc và ghi trên ổ cắm và một thông báo đã sẵn sàng để được xử lý bởi máy khách.

Lớp tin nhắn của máy khách nằm trong

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
00, là một phần của mã nguồn bạn đã tải xuống trước đó. Bạn cũng có thể tải xuống mã bằng cách nhấp vào liên kết bên dưới:

Các phương thức xuất hiện trong lớp theo thứ tự mà việc xử lý diễn ra cho một tin nhắn.

Nhiệm vụ đầu tiên cho khách hàng là xếp hàng yêu cầu:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
8

Các từ điển được sử dụng để tạo yêu cầu, tùy thuộc vào những gì được truyền trên dòng lệnh, nằm trong tập lệnh chính của máy khách,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
87. Từ điển yêu cầu được truyền như một đối số cho lớp khi một đối tượng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83 được tạo.

Thông báo yêu cầu được tạo và thêm vào bộ đệm gửi, sau đó được nhìn thấy và gửi qua

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42. Biến trạng thái
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
14 được đặt sao cho
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
50 được gọi lại.

Sau khi yêu cầu đã được gửi, máy khách chờ phản hồi từ máy chủ.

Các phương thức đọc và xử lý một thông báo trong máy khách giống như đối với máy chủ. Vì dữ liệu phản hồi được đọc từ ổ cắm, các phương thức tiêu đề

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
29 được gọi là:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
81 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
18.

Sự khác biệt là trong việc đặt tên của các phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
29 cuối cùng và thực tế là họ xử lý một phản hồi, không tạo ra một:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
23,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
21 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
22.

Cuối cùng, nhưng chắc chắn không kém phần quan trọng, là cuộc gọi cuối cùng cho

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
23:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
9

Lớp thông báo kết thúc

Để kết thúc việc học của bạn về lớp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
83, điều đáng nói là một vài điều quan trọng cần chú ý với một vài phương pháp hỗ trợ.

Bất kỳ trường hợp ngoại lệ nào được đưa ra bởi lớp đều bị bắt bởi tập lệnh chính trong Điều khoản

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
25 bên trong vòng lặp sự kiện:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
0

Lưu ý dòng:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
26.

Đây là một dòng thực sự quan trọng, vì nhiều lý do! Nó không chỉ đảm bảo rằng ổ cắm được đóng, mà

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
26 cũng loại bỏ ổ cắm khỏi được theo dõi bởi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87. Điều này đơn giản hóa rất nhiều mã trong lớp và giảm độ phức tạp. Nếu có một ngoại lệ hoặc bạn tự nâng cao một cách rõ ràng, bạn sẽ biết
$ python echo-client.py 
Received b'Hello, world'
5 sẽ chăm sóc việc dọn dẹp.

Các phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
30 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
31 cũng chứa một cái gì đó thú vị:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
1

Lưu ý dòng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
32.

Phương pháp

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42 cũng có một. Những dòng này rất quan trọng vì chúng bắt được một lỗi tạm thời và bỏ qua nó bằng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
34. Lỗi tạm thời là khi ổ cắm sẽ chặn, ví dụ nếu nó chờ đợi trên mạng hoặc đầu kia của kết nối, còn được gọi là ngang hàng của nó.

Bằng cách bắt và bỏ qua ngoại lệ với

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
34,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 cuối cùng sẽ kích hoạt một cuộc gọi mới và bạn sẽ có một cơ hội khác để đọc hoặc viết dữ liệu.

Chạy máy khách và máy chủ ứng dụng

Sau tất cả những công việc khó khăn này, đó là thời gian để có một số niềm vui và chạy một số tìm kiếm!

Trong các ví dụ này, bạn sẽ chạy máy chủ để nó lắng nghe trên tất cả các giao diện bằng cách chuyển một chuỗi trống cho đối số

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7. Điều này sẽ cho phép bạn chạy máy khách và kết nối từ một máy ảo mà trên mạng khác. Nó mô phỏng một máy PowerPC lớn.

Đầu tiên, khởi động máy chủ:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
2

Bây giờ chạy máy khách và nhập tìm kiếm. Xem nếu bạn có thể tìm thấy anh ấy:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
3

Bạn có thể nhận thấy rằng thiết bị đầu cuối đang chạy một shell mà sử dụng mã hóa văn bản của Unicode (UTF-8), do đó, đầu ra trên in độc đáo với biểu tượng cảm xúc.

Bây giờ xem bạn có thể tìm thấy những chú chó con không:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
4

Lưu ý chuỗi byte được gửi qua mạng cho yêu cầu trong dòng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
38. Nó dễ dàng hơn để xem nếu bạn tìm kiếm các byte được in bằng hex đại diện cho biểu tượng cảm xúc chó con:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
39. Nếu thiết bị đầu cuối của bạn đang sử dụng Unicode với UTF-8 mã hóa, bạn sẽ có thể nhập biểu tượng cảm xúc để tìm kiếm.

Điều này chứng tỏ rằng bạn đã gửi các byte thô qua mạng và chúng cần được giải mã bởi người nhận để được giải thích chính xác. Đây là lý do tại sao bạn đã đi đến tất cả những rắc rối để tạo một tiêu đề chứa loại nội dung và mã hóa.

Ở đây, đầu ra máy chủ từ cả hai kết nối máy khách ở trên:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
5

Nhìn vào dòng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
38 để xem các byte được ghi vào ổ cắm của máy khách. Đây là thông báo phản hồi của máy chủ.

Bạn cũng có thể kiểm tra gửi các yêu cầu nhị phân đến máy chủ nếu đối số

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
41 là bất cứ điều gì khác ngoài
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
42:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
6

Bởi vì yêu cầu

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
43 không phải là
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
44, máy chủ coi nó là một loại nhị phân tùy chỉnh và không thực hiện giải mã JSON. Nó chỉ đơn giản là in
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
43 và trả về mười byte đầu tiên cho máy khách:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
7

Xử lý sự cố

Chắc chắn, một cái gì đó đã giành được công việc, và bạn sẽ tự hỏi phải làm gì. Đừng lo lắng, nó xảy ra với tất cả mọi người. Hy vọng rằng, với sự giúp đỡ của hướng dẫn này, trình gỡ lỗi của bạn và công cụ tìm kiếm yêu thích của bạn, bạn sẽ có thể đi lại với phần mã nguồn.

Nếu không, điểm dừng đầu tiên của bạn phải là tài liệu mô -đun ổ cắm Python. Hãy chắc chắn rằng bạn đọc tất cả các tài liệu cho từng hàm hoặc phương thức mà bạn gọi. Ngoài ra, đọc qua phần tham chiếu dưới đây cho các ý tưởng. Đặc biệt, kiểm tra phần lỗi.

Đôi khi, nó không phải là tất cả về mã nguồn. Mã nguồn có thể chính xác và nó chỉ là máy chủ khác, máy khách hoặc máy chủ. Hoặc nó có thể là mạng. Có thể một bộ định tuyến, tường lửa hoặc một số thiết bị mạng khác đang chơi người đàn ông.

Đối với các loại vấn đề này, các công cụ bổ sung là rất cần thiết. Dưới đây là một vài công cụ và tiện ích có thể giúp hoặc ít nhất là cung cấp một số manh mối.

Ping

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
46 sẽ kiểm tra xem máy chủ có còn sống và kết nối với mạng hay không bằng cách gửi yêu cầu Echo ICMP. Nó giao tiếp trực tiếp với ngăn xếp giao thức TCP/IP của hệ điều hành, do đó, nó hoạt động độc lập với bất kỳ ứng dụng nào chạy trên máy chủ.

Dưới đây là một ví dụ về việc chạy ping trên macOS:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
8

Lưu ý các số liệu thống kê ở cuối đầu ra. Điều này có thể hữu ích khi bạn đang cố gắng khám phá các vấn đề kết nối không liên tục. Ví dụ, có mất gói nào không? Có bao nhiêu độ trễ? Bạn có thể kiểm tra thời gian khứ hồi.

Nếu có một tường lửa giữa bạn và máy chủ khác, một yêu cầu Echo Ping có thể không được phép. Một số quản trị viên tường lửa thực hiện các chính sách thực thi điều này. Ý tưởng là họ không muốn chủ nhà của họ có thể phát hiện được. Nếu đây là trường hợp và bạn có các quy tắc tường lửa được thêm vào để cho phép các máy chủ giao tiếp, thì hãy đảm bảo rằng các quy tắc cũng cho phép ICMP vượt qua giữa chúng.

ICMP là giao thức được sử dụng bởi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
46, nhưng nó cũng là giao thức TCP và các giao thức cấp thấp khác sử dụng để truyền đạt các thông báo lỗi. Nếu bạn trải nghiệm hành vi kỳ lạ hoặc kết nối chậm, đây có thể là lý do.

Tin nhắn ICMP được xác định theo loại và mã. Để cung cấp cho bạn một ý tưởng về thông tin quan trọng mà họ mang theo, đây là một số ít:

Loại ICMPMã ICMPSự mô tả
8 0 Yêu cầu echo
0 0 Echo trả lời
3 0 Mạng đích không thể truy cập được
3 1 Máy chủ đích không thể truy cập được
3 2 Giao thức đích không thể truy cập được
3 3 Cổng đích không thể truy cập được
3 4 Yêu cầu phân mảnh và bộ cờ DF
11 0 TTL đã hết hạn trong quá trình vận chuyển

Xem bài viết MTU Discovery để biết thông tin liên quan đến phân mảnh và tin nhắn ICMP. Đây là một ví dụ về một cái gì đó có thể gây ra hành vi kỳ lạ.

netstat

Trong phần xem trạng thái ổ cắm, bạn đã tìm hiểu cách

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
36 có thể được sử dụng để hiển thị thông tin về ổ cắm và trạng thái hiện tại của chúng. Tiện ích này có sẵn trên MacOS, Linux và Windows.

Phần đó đã không đề cập đến các cột

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
49 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
50 trong đầu ra ví dụ. Các cột này sẽ cho bạn thấy số lượng byte được giữ trong bộ đệm mạng được xếp hàng để truyền hoặc nhận, nhưng vì một số lý do, thiên đường đã được đọc hoặc viết bởi ứng dụng từ xa hoặc cục bộ.

Nói cách khác, các byte đang chờ đợi trong bộ đệm mạng trong hàng đợi hệ điều hành. Một lý do có thể là ứng dụng bị ràng buộc CPU hoặc không thể gọi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26 hoặc
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59 và xử lý byte. Hoặc có thể có các vấn đề mạng ảnh hưởng đến truyền thông, như tắc nghẽn hoặc phần cứng mạng bị hỏng hoặc cáp.

Để chứng minh điều này và xem bạn có thể gửi bao nhiêu dữ liệu trước khi thấy lỗi, bạn có thể thử máy khách thử nghiệm kết nối với máy chủ thử nghiệm và liên tục gọi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
59. Máy chủ thử nghiệm không bao giờ gọi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26. Nó chỉ chấp nhận kết nối. Điều này khiến các bộ đệm mạng trên máy chủ điền, cuối cùng làm tăng lỗi trên máy khách.

Đầu tiên, khởi động máy chủ:

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
9

Sau đó, chạy máy khách để xem lỗi là gì:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
0

Tại đây, ____ ____136 đầu ra từ trong khi máy khách và máy chủ vẫn đang chạy, với máy khách in thông báo lỗi trên nhiều lần:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
1

Mục đầu tiên là máy chủ (

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 có cổng 65432):

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
2

Lưu ý

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
49:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
58.

Mục thứ hai là máy khách (

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
59 có cổng 65432):

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
3

Lưu ý

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
50:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
61.

Khách hàng chắc chắn đang cố gắng viết byte, nhưng máy chủ không phải là đọc chúng. Điều này đã khiến hàng đợi bộ đệm mạng máy chủ điền vào phía nhận và hàng đợi bộ đệm mạng của máy khách để điền vào phía gửi.

các cửa sổ

Nếu bạn làm việc với Windows, có một bộ tiện ích mà bạn chắc chắn nên kiểm tra xem bạn có chưa sử dụng được không: Windows SysiNternals.

Một trong số đó là

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
62. TCPVIEW là một đồ họa
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
36 cho Windows. Ngoài địa chỉ, số cổng và trạng thái ổ cắm, nó sẽ cho bạn thấy bạn chạy tổng số cho số lượng gói và byte được gửi và nhận. Giống như với tiện ích UNIX
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
54, bạn cũng nhận được tên quy trình và ID. Kiểm tra các menu cho các tùy chọn hiển thị khác.

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

WIRESHARK

Đôi khi bạn cần phải xem những gì xảy ra trên dây. Quên về những gì nhật ký ứng dụng nói hoặc giá trị là gì mà được trả lại từ một cuộc gọi thư viện. Bạn muốn xem những gì thực sự được gửi hoặc nhận trên mạng. Cũng giống như với những người gỡ lỗi, khi bạn cần nhìn thấy nó, không có sự thay thế nào.

Wireshark là một máy phân tích giao thức mạng và ứng dụng thu thập lưu lượng chạy trên macOS, Linux và Windows, trong số các phân tích khác. Có một phiên bản GUI có tên

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
65 và cũng là một phiên bản dựa trên văn bản, có tên là
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
66.

Chạy thu thập lưu lượng truy cập là một cách tuyệt vời để xem cách ứng dụng hoạt động trên mạng và thu thập bằng chứng về những gì nó gửi và nhận, và tần suất và bao nhiêu. Bạn cũng có thể thấy khi khách hoặc máy chủ đóng hoặc hủy bỏ kết nối hoặc ngừng phản hồi. Thông tin này có thể cực kỳ hữu ích khi bạn xử lý sự cố.

Có nhiều hướng dẫn tốt và các tài nguyên khác trên web sẽ hướng dẫn bạn những điều cơ bản của việc sử dụng Wireshark và Tshark.

Dưới đây, một ví dụ về việc bắt giữ lưu lượng truy cập bằng cách sử dụng Wireshark trên giao diện Loopback:

Hướng dẫn python open multiple sockets - python mở nhiều ổ cắm

Ở đây, ví dụ tương tự được hiển thị ở trên bằng cách sử dụng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
66:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
4

Tiếp theo, bạn sẽ nhận được nhiều tài liệu tham khảo hơn để hỗ trợ hành trình lập trình ổ cắm của bạn!

Tài liệu tham khảo

Bạn có thể sử dụng phần này làm tài liệu tham khảo chung với thông tin bổ sung và liên kết đến các tài nguyên bên ngoài.

Tài liệu Python

  • Mô -đun ổ cắm Python
  • Lập trình ổ cắm Python từ

Lỗi

Sau đây là từ tài liệu mô -đun Python từ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
68:

Tất cả các lỗi đều tăng ngoại lệ. Các ngoại lệ bình thường cho các loại đối số không hợp lệ và các điều kiện ngoài bộ nhớ có thể được nêu ra; Bắt đầu từ Python 3.3, các lỗi liên quan đến ổ cắm hoặc ngữ nghĩa địa chỉ tăng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
69 hoặc một trong các lớp con của nó. (Nguồn)

Dưới đây là một số lỗi phổ biến mà bạn có thể gặp phải khi làm việc với ổ cắm:

Ngoại lệ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
70 Hằng số
Sự mô tả
ChặnEwouldblockTài nguyên tạm thời không có. Ví dụ: ở chế độ không chặn, khi gọi
$ python echo-client.py 
Received b'Hello, world'
3 và đồng đẳng đang bận và không đọc, hàng đợi (bộ đệm mạng) đã đầy đủ. Hoặc có vấn đề với mạng. Hy vọng rằng đây là một điều kiện tạm thời.
OserrorEaddrinuseĐịa chỉ đã được sử dụng. Đảm bảo rằng có một quy trình khác không chạy mà sử dụng cùng một số cổng và máy chủ của bạn đang đặt tùy chọn ổ cắm
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
72:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
73.
ConnectionResetErrorEconnresetĐặt lại kết nối theo ngang hàng. Quá trình từ xa bị sập hoặc không đóng ổ cắm đúng cách, còn được gọi là tắt máy ô uế. Hoặc có một tường lửa hoặc thiết bị khác trong đường dẫn mạng mà thiếu các quy tắc hoặc hành vi sai.
TimeouterrorEtimedoutChiến dịch kết thúc. Không có phản hồi từ ngang hàng.
ConnectionRefuseRorrorEconnref sử dụngKết nối từ chối. Không nghe ứng dụng trên cổng được chỉ định.

Ổ cắm địa chỉ gia đình

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
5 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
75 đại diện cho các họ địa chỉ và giao thức được sử dụng cho đối số đầu tiên đến
$ python echo-client.py 
Received b'Hello, world'
6. API sử dụng một địa chỉ mong đợi nó ở một định dạng nhất định, tùy thuộc vào việc ổ cắm được tạo bằng
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
5 hay
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
75.

Địa chỉ gia đìnhGiao thứcĐịa chỉ tupleSự mô tả
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
5
IPv4
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
6
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 là một chuỗi có tên máy chủ như
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
82 hoặc địa chỉ IPv4 như
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
83.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 là một số nguyên.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
75
IPv6
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
17
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 là một chuỗi có tên máy chủ như
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
82 hoặc địa chỉ IPv6 như
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
89.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 là một số nguyên.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
91 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
92 đại diện cho các thành viên
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
93 và
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
94 trong C Struct
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
95.

Lưu ý đoạn trích bên dưới từ tài liệu mô -đun ổ cắm Python sườn liên quan đến giá trị

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 của bộ địa chỉ:

Đối với địa chỉ IPv4, hai biểu mẫu đặc biệt được chấp nhận thay vì địa chỉ máy chủ: chuỗi trống đại diện cho

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
97 và chuỗi
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
98 đại diện cho
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
99. Hành vi này không tương thích với IPv6, do đó, bạn có thể muốn tránh những điều này nếu bạn có ý định hỗ trợ IPv6 với các chương trình Python của bạn. (Nguồn)

Xem tài liệu gia đình ổ cắm Python sườn để biết thêm thông tin.

Hướng dẫn này sử dụng ổ cắm IPv4, nhưng nếu mạng của bạn hỗ trợ nó, hãy thử kiểm tra và sử dụng IPv6 nếu có thể. Một cách để hỗ trợ điều này một cách dễ dàng là bằng cách sử dụng ổ cắm chức năng.getaddrinfo (). Nó dịch các đối số

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
7 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 thành một chuỗi năm bộ phận chứa tất cả các đối số cần thiết để tạo ổ cắm được kết nối với dịch vụ đó.
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
02 sẽ hiểu và giải thích các địa chỉ IPv6 và tên máy chủ được truyền vào địa chỉ IPv6, ngoài IPv4.

Ví dụ sau trả về thông tin địa chỉ cho kết nối TCP với

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
03 trên cổng
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
04:

>>>

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
5

Kết quả có thể khác nhau trên hệ thống của bạn nếu IPv6 được bật. Các giá trị được trả về ở trên có thể được sử dụng bằng cách chuyển chúng cho

$ python echo-client.py 
Received b'Hello, world'
6 và
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
06. Có một ví dụ về máy khách và máy chủ trong phần ví dụ của tài liệu mô -đun ổ cắm Python.

Sử dụng tên máy chủ

Đối với ngữ cảnh, phần này áp dụng chủ yếu cho việc sử dụng tên máy chủ với

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
8 và
$ python echo-client.py 
Received b'Hello, world'
1 hoặc
$ python echo-client.py 
Received b'Hello, world'
2, khi bạn có ý định sử dụng giao diện Loopback, thì Local Localhost. Tuy nhiên, nó cũng áp dụng bất cứ lúc nào bạn sử dụng tên máy chủ và có một kỳ vọng về việc nó giải quyết đến một địa chỉ nhất định và có ý nghĩa đặc biệt đối với ứng dụng của bạn ảnh hưởng đến hành vi hoặc giả định của nó. Điều này trái ngược với kịch bản điển hình của máy khách bằng cách sử dụng tên máy chủ để kết nối với máy chủ mà DNS giải quyết bởi DNS, như www.example.com.

Sau đây là từ tài liệu mô -đun Python từ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
68:

Nếu bạn sử dụng tên máy chủ trong phần máy chủ của địa chỉ ổ cắm IPv4/V6, chương trình có thể hiển thị hành vi không xác định, vì Python sử dụng địa chỉ đầu tiên được trả về từ độ phân giải DNS. Địa chỉ ổ cắm sẽ được giải quyết khác nhau thành địa chỉ IPv4/V6 thực tế, tùy thuộc vào kết quả từ độ phân giải DNS và/hoặc cấu hình máy chủ. Đối với hành vi xác định, hãy sử dụng một địa chỉ số trong phần máy chủ. (Nguồn)

Quy ước tiêu chuẩn cho tên là Local LocalHost, để nó giải quyết thành

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68, giao diện Loopback. Điều này sẽ nhiều khả năng là trường hợp của bạn trên hệ thống của bạn, nhưng có thể không. Nó phụ thuộc vào cách hệ thống của bạn được cấu hình để giải quyết tên. Như với tất cả những điều đó, luôn có những ngoại lệ và không có gì đảm bảo rằng việc sử dụng tên là Local Localhost, sẽ kết nối với giao diện Loopback.

Ví dụ: trên Linux, xem

# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
13, tệp cấu hình chuyển đổi dịch vụ tên. Một nơi khác để kiểm tra MacOS và Linux là tệp
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
14. Trên Windows, xem
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
15. Tệp
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
16 chứa một bảng tĩnh của ánh xạ tên-địa chỉ ở định dạng văn bản đơn giản. DNS là một phần khác của câu đố hoàn toàn.

Điều thú vị là, tính đến tháng 6 năm 2018, có một bản nháp của RFC cho phép ‘Localhost, được địa phương thảo luận về các công ước, giả định và bảo mật xung quanh việc sử dụng tên là Local Localhost.

Điều quan trọng để hiểu là khi bạn sử dụng tên máy chủ trong ứng dụng của mình, các địa chỉ được trả lại theo nghĩa đen có thể là bất cứ điều gì. Don Tiết đưa ra các giả định liên quan đến một tên nếu bạn có một ứng dụng nhạy cảm với bảo mật. Tùy thuộc vào ứng dụng và môi trường của bạn, điều này có thể hoặc không phải là mối quan tâm đối với bạn.

Bất kể bạn có sử dụng tên máy chủ hay không, nếu ứng dụng của bạn cần hỗ trợ các kết nối an toàn thông qua mã hóa và xác thực, thì bạn có thể muốn xem xét sử dụng TLS. Đây là chủ đề riêng của nó và ngoài phạm vi của hướng dẫn này. Xem tài liệu mô -đun SSL Python sườn để bắt đầu. Đây là cùng một giao thức mà trình duyệt web của bạn sử dụng để kết nối an toàn với các trang web.

Với giao diện, địa chỉ IP và độ phân giải tên để xem xét, có nhiều biến. Những gì bạn nên làm? Dưới đây là một số khuyến nghị mà bạn có thể sử dụng nếu bạn không có quy trình xem xét ứng dụng mạng:

Đăng kíCách sử dụngsự giới thiệu
Người phục vụGiao diện LoopbackSử dụng địa chỉ IP, chẳng hạn như
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68.
Người phục vụGiao diện LoopbackSử dụng địa chỉ IP, chẳng hạn như
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68.
Giao diện EthernetGiao diện LoopbackSử dụng địa chỉ IP, chẳng hạn như
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68.
Giao diện EthernetGiao diện LoopbackSử dụng địa chỉ IP, chẳng hạn như
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN
9 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
68.

Giao diện Ethernet

Sử dụng một địa chỉ IP, chẳng hạn như with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: pass # Use the socket object without calling s.close(). 06. Để hỗ trợ nhiều giao diện, hãy sử dụng một chuỗi trống cho tất cả các giao diện/địa chỉ. Xem ghi chú bảo mật ở trên.

Khách hàng

Sử dụng địa chỉ IP để thống nhất và không phụ thuộc vào độ phân giải tên. Đối với trường hợp điển hình, sử dụng tên máy chủ. Xem ghi chú bảo mật ở trên.

Đối với máy khách hoặc máy chủ, nếu bạn cần xác thực máy chủ mà bạn đang kết nối, hãy xem xét sử dụng TLS.

Chặn các cuộc gọi

Một hàm hoặc phương thức tạm thời tạm dừng ứng dụng của bạn là một cuộc gọi chặn. Ví dụ, $ python echo-client.py Received b'Hello, world' 0, $ python echo-client.py Received b'Hello, world' 1, $ python echo-client.py Received b'Hello, world' 3 và $ python echo-client.py Received b'Hello, world' 4, có nghĩa là họ không trả lại ngay lập tức. Chặn các cuộc gọi phải chờ trên các cuộc gọi hệ thống (I/O) để hoàn thành trước khi chúng có thể trả về một giá trị. Vì vậy, bạn, người gọi, bị chặn cho đến khi họ đã hoàn thành hoặc thời gian chờ hoặc lỗi khác xảy ra.

Chặn các cuộc gọi ổ cắm có thể được đặt thành chế độ không chặn để chúng quay lại ngay lập tức. Nếu bạn làm điều này, thì bạn sẽ cần ít nhất phải tái cấu trúc hoặc thiết kế lại ứng dụng của mình để xử lý thao tác ổ cắm khi nó sẵn sàng.

Bởi vì cuộc gọi trả về ngay lập tức, dữ liệu có thể chưa sẵn sàng. Callee đang chờ đợi trên mạng và đã có thời gian để hoàn thành công việc của mình. Nếu đây là trường hợp, thì trạng thái hiện tại là giá trị

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
70
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
27. Chế độ không chặn được hỗ trợ với .setblocking ().

Theo mặc định, ổ cắm luôn được tạo ở chế độ chặn. Xem ghi chú về thời gian chờ ổ cắm để biết mô tả của ba chế độ.

Đóng kết nối

Một điều thú vị cần lưu ý với TCP là nó hoàn toàn hợp pháp cho máy khách hoặc máy chủ để đóng phía kết nối của họ trong khi phía bên kia vẫn mở. Điều này được gọi là kết nối nửa mở của người Viking. Nó đưa ra quyết định của ứng dụng cho dù điều này có mong muốn hay không. Nói chung, nó không. Ở trạng thái này, phía đã đóng phần cuối của kết nối không còn có thể gửi dữ liệu. Họ chỉ có thể nhận được nó.

Cách tiếp cận này không nhất thiết phải được khuyến nghị, nhưng ví dụ, HTTP sử dụng tiêu đề có tên là Kết nối, kết nối mà sử dụng để chuẩn hóa cách các ứng dụng nên đóng hoặc tồn tại các kết nối mở. Để biết chi tiết, xem Phần 6.3 trong RFC 7230, Giao thức chuyển siêu văn bản (http/1.1): cú pháp và định tuyến tin nhắn.

Khi thiết kế và viết ứng dụng của bạn và giao thức lớp ứng dụng của nó, đó là một ý tưởng tốt để tiếp tục và tìm ra cách bạn mong đợi các kết nối sẽ được đóng lại. Đôi khi điều này là hiển nhiên và đơn giản, hoặc nó là một thứ gì đó có thể lấy một số mẫu và thử nghiệm ban đầu. Nó phụ thuộc vào ứng dụng và cách vòng lặp tin nhắn được xử lý với dữ liệu dự kiến. Chỉ cần đảm bảo rằng các ổ cắm luôn được đóng một cách kịp thời sau khi họ hoàn thành công việc của họ.

Điều này trở nên có vấn đề khi có dữ liệu liên quan đến việc lưu trữ trong các tệp hoặc cơ sở dữ liệu và ở đó, không có siêu dữ liệu nào có sẵn chỉ định mã hóa của nó. Khi dữ liệu được chuyển sang điểm cuối khác, nó sẽ phải cố gắng phát hiện mã hóa. Để thảo luận, xem bài viết của Wikipedia, Unicode, trong đó tham chiếu RFC 3629: UTF-8, định dạng chuyển đổi của ISO 10646:

Tuy nhiên, RFC 3629, tiêu chuẩn UTF-8, khuyến nghị rằng các dấu lệnh byte bị cấm trong các giao thức sử dụng UTF-8, nhưng thảo luận về các trường hợp có thể không thể thực hiện được. Ngoài ra, hạn chế lớn đối với các mẫu có thể có trong UTF-8 (ví dụ: không thể có bất kỳ byte đơn độc nào có bộ bit cao) có nghĩa là có thể phân biệt UTF-8 với các mã hóa ký tự khác mà không cần dựa vào BOM. (Nguồn)

Việc thực hiện từ đây là luôn luôn lưu trữ mã hóa được sử dụng cho dữ liệu mà xử lý bởi ứng dụng của bạn nếu nó có thể thay đổi. Nói cách khác, hãy cố gắng bằng cách nào đó lưu trữ mã hóa dưới dạng siêu dữ liệu nếu nó không phải lúc nào cũng UTF-8 hoặc một số mã hóa khác với BOM. Sau đó, bạn có thể gửi mã hóa đó trong một tiêu đề cùng với dữ liệu để cho người nhận biết nó là gì.

Đặt hàng byte được sử dụng trong TCP/IP là Big-Endian và được gọi là thứ tự mạng. Thứ tự mạng được sử dụng để biểu diễn số nguyên trong các lớp thấp hơn của ngăn xếp giao thức, như địa chỉ IP và số cổng. Mô -đun ổ cắm Python Python bao gồm các chức năng chuyển đổi số nguyên thành và từ mạng và đơn đặt hàng byte:

Hàm sốSự mô tả
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
28
Chuyển đổi số nguyên dương 32 bit từ mạng sang thứ tự byte. Trên các máy nơi đơn đặt hàng byte máy chủ giống như thứ tự byte mạng, đây là không có op; Nếu không, nó thực hiện hoạt động hoán đổi 4 byte.
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
29
Chuyển đổi số nguyên dương 16 bit từ mạng sang đơn đặt hàng byte. Trên các máy nơi đơn đặt hàng byte máy chủ giống như thứ tự byte mạng, đây là không có op; Nếu không, nó thực hiện hoạt động hoán đổi 2 byte.
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
30
Chuyển đổi số nguyên dương 32 bit từ máy chủ sang đơn đặt hàng byte. Trên các máy nơi đơn đặt hàng byte máy chủ giống như thứ tự byte mạng, đây là không có op; Nếu không, nó thực hiện hoạt động hoán đổi 4 byte.
# echo-client.py

import socket

HOST = "127.0.0.1"  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world")
    data = s.recv(1024)

print(f"Received {data!r}")
31
Chuyển đổi số nguyên dương 16 bit từ máy chủ sang đơn đặt hàng byte. Trên các máy nơi đơn đặt hàng byte máy chủ giống như thứ tự byte mạng, đây là không có op; Nếu không, nó thực hiện hoạt động hoán đổi 2 byte.

Bạn cũng có thể sử dụng mô -đun struct để đóng gói và giải nén dữ liệu nhị phân bằng các chuỗi định dạng:

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
6

Sự kết luận

Bạn đã đề cập rất nhiều nền tảng trong hướng dẫn này! Mạng và ổ cắm là những chủ đề lớn. Nếu bạn mới sử dụng mạng hoặc ổ cắm, thì hãy không được khuyến khích bởi tất cả các thuật ngữ và từ viết tắt.

Có rất nhiều phần để làm quen với để hiểu cách mọi thứ hoạt động cùng nhau. Tuy nhiên, giống như Python, nó sẽ bắt đầu có ý nghĩa hơn khi bạn biết các mảnh riêng lẻ và dành nhiều thời gian hơn với chúng.

Trong hướng dẫn này, bạn:

  • Nhìn vào API ổ cắm ở mức độ thấp trong mô-đun Python từ
    # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    68 và thấy cách nó có thể được sử dụng để tạo các ứng dụng máy khách-máy kháchlow-level socket API in Python’s
    # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    68 module and saw how it can be used to create client-server applications
  • Được xây dựng một máy khách và máy chủ có thể xử lý nhiều kết nối bằng cách sử dụng đối tượng
    # echo-client.py
    
    import socket
    
    HOST = "127.0.0.1"  # The server's hostname or IP address
    PORT = 65432  # The port used by the server
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(b"Hello, world")
        data = s.recv(1024)
    
    print(f"Received {data!r}")
    
    33multiple connections using a
    # echo-client.py
    
    import socket
    
    HOST = "127.0.0.1"  # The server's hostname or IP address
    PORT = 65432  # The port used by the server
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(b"Hello, world")
        data = s.recv(1024)
    
    print(f"Received {data!r}")
    
    33 object
  • Đã tạo lớp tùy chỉnh của riêng bạn và sử dụng nó như một giao thức lớp ứng dụng để trao đổi tin nhắn và dữ liệu giữa các điểm cuốicustom class and used it as an application-layer protocol to exchange messages and data between endpoints

Từ đây, bạn có thể sử dụng lớp tùy chỉnh của mình và xây dựng nó để tìm hiểu và giúp tạo ra các ứng dụng ổ cắm của riêng bạn dễ dàng và nhanh hơn.

Để xem lại các ví dụ, bạn có thể nhấp vào liên kết bên dưới:

Chúc mừng bạn đã đến cùng! Bây giờ bạn đang trên đường sử dụng ổ cắm trong các ứng dụng của riêng bạn. Chúc may mắn trên hành trình phát triển ổ cắm của bạn.