Làm cách nào để kiểm tra xem ổ cắm có còn hoạt động trong python không?

Khi tạo mạng bằng cách sử dụng ổ cắm, đôi khi có thể cần triển khai hệ thống kết nối lại tự động khi ổ cắm máy khách mất kết nối với ổ cắm máy chủ. Đây có thể là một điều cần thiết cho ứng dụng hoặc chỉ để thuận tiện cho người dùng


Trong Python, khi một ổ cắm mất kết nối với máy chủ, hệ điều hành sẽ đóng ổ cắm đó, điều này sẽ dẫn đến lỗi nếu bạn cố đọc hoặc ghi vào luồng ổ cắm, điều này ít nhất cung cấp cho bạn một cách để phát hiện . Tất cả những gì bạn cần làm sau đó là tạo một ổ cắm mới và cố gắng kết nối lại với máy chủ


Các ví dụ dưới đây tương thích với Python phiên bản 3 trở lên


Phát hiện mất kết nối với máy chủ


Khi xảy ra mất kết nối giữa ổ cắm máy khách và máy chủ, hệ điều hành sẽ đóng ổ cắm đang giữ kết nối mở, vì vậy nếu bạn cố đọc hoặc ghi vào luồng ổ cắm, nó sẽ dẫn đến một ngoại lệ


Để phát hiện mất kết nối, tất cả những gì bạn cần làm là bao quanh bất kỳ thao tác đọc hoặc ghi ổ cắm nào trong câu lệnh thử ngoại trừ để lắng nghe ngoại lệ ổ cắm


try:  
    clientSocket.send[ bytes[ "test", "UTF-8" ] ]  
except socket.error:  
    # reconnect to the server here  


Nếu ngoại lệ này bị phát hiện, bạn sẽ biết rằng rất có thể bạn cần kết nối lại với máy chủ


Kết nối lại với máy chủ khi phát hiện mất kết nối


Vì đối tượng ổ cắm không còn hữu ích sau khi mất kết nối, nên cần phải tạo một đối tượng ổ cắm mới [có thể gán đối tượng ổ cắm này cho biến ổ cắm ban đầu]


Khi bạn đã tạo lại ổ cắm máy khách, bạn có thể thử kết nối lại với máy chủ bằng phương thức connect[]. Vấn đề với điều này là sự cố gây mất kết nối vẫn có thể ảnh hưởng đến hệ thống, điều này có nghĩa là phương thức connect[] có thể dẫn đến một ngoại lệ, chẳng hạn như "ConnectionRefusedError" [do máy chủ không chủ động lắng nghe kết nối]


Một giải pháp đơn giản cho vấn đề này là đặt phương thức connect[] trong vòng lặp while và bao quanh nó bằng câu lệnh try-except. Nếu kết nối thành công, thì ứng dụng sẽ tiếp tục với phần còn lại của tập lệnh, nếu không, nó sẽ đợi vài giây và cố gắng kết nối lại


print[ "connection lost.. reconnecting" ]  
connected = False  
  
# recreate socket  
clientSocket = socket.socket[]  
  
while not connected:  
    # attempt to reconnect, otherwise sleep for 2 seconds  
    try:  
        clientSocket.connect[ [ host, port ] ]  
        connected = True  
        print[ "re-connection successful" ]  
    except socket.error:  
        sleep[ 2 ]  
  
# continue normal operations  

Đoạn script trên sẽ giữ ứng dụng trong vòng lặp while cho đến khi nó kết nối lại thành công với ổ cắm máy chủ


ví dụ đầy đủ


Ở đây, chúng tôi sẽ tạo một máy chủ và máy khách cục bộ đơn giản sẽ gửi và nhận các tin nhắn đơn giản từ nhau miễn là chúng được kết nối. Nếu máy khách mất kết nối với máy chủ, nó sẽ cố gắng kết nối lại

Ổ cắm và API ổ cắm được sử dụng để gửi tin nhắn qua 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ạng cục bộ, hợp lý với máy tính hoặc mạng được kết nối vật lý với mạng bên ngoài, 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 mình

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

  • Một máy chủ và máy khách ổ cắm đơn giản
  • Phiên bản cải tiến xử lý đồng thời nhiều kết nối
  • Một ứng dụng máy chủ-máy khách có chức năng giống như một ứ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 của riêng nó

Đến cuối hướng dẫn này, bạn sẽ hiểu cách sử dụng các hàm và phương thức chính trong mô-đun socket của Python để viết các ứng dụng máy khách-máy 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 mình

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 bạn nên tải xuống mã nguồn và có sẵn nó để tham khảo khi đọc

Nhận mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng cho các ví dụ trong hướng dẫn này

Mạng và ổ cắm là những chủ đề lớn. Khối lượng văn học đã được viết về họ. Nếu bạn chưa quen với ổ cắm hoặc kết nối mạng, việc bạn cảm thấy choáng ngợp với tất cả các thuật ngữ và phần là hoàn toàn bình thường.

Đừng nản lòng mặc dù. Hướng dẫn này là dành cho bạn. Như với bất kỳ thứ gì liên quan đến Python, bạn có thể học từng chút một. Đá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] phát hành năm 1983 được gọi là ổ cắm Berkeley

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ủ và trình duyệt web không phải là ứ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ủ thuộc mọi loại và kích 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 đã được phát triển, nhưng API cấp thấp vẫn giữ nguyên

Loại ứng dụng ổ cắm phổ biến nhất là ứng dụng máy khách-máy chủ, trong đó một bên đóng vai trò là 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 tên 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ủ

Loại bỏ các quảng cáo

Tổng quan về API ổ cắm

Mô-đun ổ cắm của Python cung cấp giao diện cho API ổ cắm Berkeley. Đâ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à

  • $ python echo-server.py
    
    8
  • $ python echo-server.py
    
    9
  • # 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
  • # 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
  • # 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
  • # 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
  • # 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
  • # 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
  • # 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

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

Là một phần của thư viện chuẩn, Python cũng có các lớp giúp sử dụng các hàm ổ 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, nhưng bạn có thể xem mô-đun socketserver, một khung dành cho máy chủ mạng. Ngoài ra còn có nhiều mô-đun triển khai các giao thức Internet cấp cao hơn như HTTP và SMTP. Để biết tổng quan, hãy xem Hỗ trợ và Giao thức Internet

ổ cắm TCP

Bạn sẽ tạo một đối tượng ổ 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]]
    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, chỉ định loại ổ cắm 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]
8. Khi bạn làm điều đó, giao thức mặc định được sử dụng là Giao thức điều khiển truyền dẫn [TCP]. Đây là một mặc định tốt và có thể là những gì bạn muốn

Tại sao bạn nên sử dụng TCP?

  • Đá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
  • Có phân phối dữ liệu theo thứ tự. 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

Ngược lại, ổ cắm Giao thức gói dữ liệu người dùng [UDP] được tạo 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]
9 không đáng tin cậy và dữ liệu được đọc bởi người nhận có thể không theo thứ tự từ việc ghi của người gửi

Tại sao nó quan trọng? . 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à bộ chuyển mạch, 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 chúng. Chúng có CPU, bộ nhớ, bus 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, dữ liệu đến không theo thứ tự và các cạm bẫy khác luôn xảy ra khi bạn liên lạc qua mạng

Để hiểu rõ hơn về điều này, hãy xem trình tự lệnh gọi API ổ cắm và luồng dữ liệu cho TCP

Luồ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, hãy lưu ý các lệnh gọi API mà máy chủ thực hiện để thiết lập ổ cắm “nghe”

  • $ python echo-server.py
    
    8
  • $ python echo-server.py
    
    9
  • # 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
  • # 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

Ổ cắm nghe thực hiện đúng như tên gọi của nó. 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

# 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 để chấp nhận hoặc hoàn tất kết nối

Máy khách 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
2 để thiết lập kết nối với máy chủ và bắt đầu quá trình bắt tay ba bước. 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 đều có thể truy cập được trong mạng, hay nói cách khác là máy khách có thể truy cập máy chủ và ngược lại. Có thể chỉ một máy chủ, máy khách hoặc máy chủ có thể kết nối với nhau

Ở giữa là phần khứ hồi, nơi dữ liệu được trao đổi giữa máy khách và máy chủ bằng cách gọi tớ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]
4 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]
5

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

Loại bỏ các quảng cáo

Máy khách và máy chủ Echo

Bây giờ bạn đã có cái nhìn tổng quan về API socket và cách máy khách và máy chủ giao tiếp với nhau, 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ủ sẽ chỉ lặp lại bất cứ điều gì nó nhận được trở lại máy khách

Máy chủ tiếng vang

Đây là máy chủ

$ python echo-server.py
2

Ghi chú. Đừng lo lắng về việc hiểu mọi thứ ở trên ngay bây giờ. Có rất nhiều thứ đang diễn ra trong vài dòng mã này. Đây chỉ là điểm khởi đầu để bạn có thể thấy một máy chủ cơ bản đang hoạt động

Có một phần tham khảo ở cuối hướng dẫn này có thêm thông tin và liên kết đến các tài nguyên bổ sung. Bạn cũng sẽ tìm thấy những liên kết này và các liên kết hữu ích khác trong suốt hướng dẫn

Được rồi, vậy chính xác điều gì đang xảy ra trong lệnh gọi API?

# 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 tạo một đối tượng ổ cắm hỗ trợ loại trình quản lý bối cảnh, vì vậy bạn có thể sử dụng nó trong câu lệnh
$ python echo-server.py
49. Không cần gọi
$ python echo-server.py
50

$ python echo-server.py
6

Các đối số được truyền cho

$ python echo-server.py
8 là các hằng số được sử dụng để chỉ định họ địa chỉ và loại ổ cắm.
$ python echo-server.py
52 là họ địa chỉ Internet cho IPv4.
$ python echo-server.py
53 là loại ổ cắm cho TCP, giao thức sẽ được sử dụng để vận chuyển thông điệp trong mạng

Phương pháp

$ python echo-server.py
9 được sử dụng để liên kết ổ cắm với một giao diện mạng và số cổng cụ thể

$ python echo-server.py
1

Các giá trị được chuyển đến

$ python echo-server.py
9 phụ thuộc vào họ địa chỉ của ổ cắm. Trong ví dụ này, bạn đang sử dụng
$ python echo-server.py
56 [IPv4]. Vì vậy, nó mong đợi một hai tuple.
$ python echo-server.py
57

$ python echo-server.py
58 có thể là tên máy chủ, địa chỉ IP hoặc chuỗi rỗng. Nếu địa chỉ IP được sử dụng, thì
$ python echo-server.py
58 phải là chuỗi địa chỉ có định dạng IPv4. Địa chỉ IP
$ python echo-server.py
600 là địa chỉ IPv4 tiêu chuẩn cho giao diện loopback, vì vậy chỉ các quy trình trên máy chủ mới có thể kết nối với máy chủ. Nếu bạn chuyển một chuỗi trống, máy chủ sẽ chấp nhận các kết nối trên tất cả các giao diện IPv4 có sẵn

$ python echo-server.py
601 đạ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ừ
$ python echo-server.py
602 đến
$ python echo-server.py
603, vì
$ python echo-server.py
604 được dành riêng. Một số hệ thống có thể yêu cầu đặc quyền siêu người dùng nếu số cổng nhỏ hơn
$ python echo-server.py
605

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

$ python echo-server.py
9

“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 phân giải khác 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 đị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 Sử dụng tên máy chủ. Hiện tại, bạn 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ì được trả về từ quá trình phân giải 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ỉ

$ python echo-server.py
607. Lần sau, bạn nhận được một địa chỉ khác,
$ python echo-server.py
608. Lần thứ ba, bạn có thể nhận được
$ python echo-server.py
609, v.v.

Trong ví dụ về 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]
0 cho phép máy chủ chấp nhận kết nối. Nó làm cho máy chủ trở thành ổ cắm “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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
8

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]
0 có tham số
$ python echo-server.py
612. Nó chỉ định số lượng 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 kết nối mới. Bắt đầu bằng Python 3. 5, nó là tùy chọn. Nếu không được chỉ định, giá trị
$ python echo-server.py
612 mặc định sẽ được chọn

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

$ python echo-server.py
612 có thể hữu ích 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
$ python echo-server.py
615

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]
1 chặn thực thi và đợi kết nối đến. Khi một máy khách kết nối, nó trả về một đối tượng ổ cắm mới đại diện cho kết nối và một bộ chứa địa chỉ của máy khách. Bộ dữ liệu sẽ chứa
$ python echo-server.py
57 cho các kết nối IPv4 hoặc
$ python echo-server.py
618 cho IPv6. Xem Các họ địa chỉ ổ cắm trong phần tham khảo để biết chi tiết về các giá trị bộ dữ liệu

Một điều 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ừ

# 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. Điều này rất quan trọng vì nó là ổ cắm mà bạn sẽ sử dụng để giao tiếp với khách hàng. Nó khác 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]
8

Sau 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
1 cung cấp đối tượng ổ cắm máy khách
$ python echo-server.py
621, một vòng lặp vô hạn
$ python echo-server.py
622 được sử dụng để lặp lại các cuộc gọi chặn tới
$ python echo-server.py
623. Đ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 dữ liệu đó bằng cách sử dụng
$ python echo-server.py
624

Nếu

$ python echo-server.py
623 trả về một đối tượng
$ python echo-server.py
626 trống,
$ python echo-server.py
627, báo hiệu rằng máy khách đã đóng kết nối và vòng lặp kết thúc. Câu lệnh
$ python echo-server.py
49 được sử dụng với
$ python echo-server.py
621 để tự động đóng socket ở cuối khối

Loại bỏ các quảng cáo

Máy khách Echo

Bây giờ hãy nhìn vào 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
9

So với máy chủ, máy khách khá đơn giản. Nó tạo một đối tượng ổ cắm, 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]
2 để kết nối với máy chủ và gọi
$ python echo-server.py
631 để gửi tin nhắn của nó. Cuối cùng, nó gọi
$ python echo-server.py
632 để đọc câu trả lời của 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 chúng hoạt động và kiểm tra điều gì đang xảy ra

Ghi chú. Nếu bạn gặp khó khăn khi lấy các ví dụ hoặc mã của riêng mình để chạy từ dòng lệnh, hãy đọc Làm cách nào để tôi tạo các lệnh dòng lệnh của riêng mình bằng Python? . Nếu bạn đang sử dụng Windows, hãy xem Câu hỏi thường gặp về Windows về Python

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

$ python echo-server.py

Thiết bị đầu cuối của bạn sẽ xuất hiện để treo. Đó là do máy chủ bị chặn hoặc bị treo trê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

# 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ó đang chờ kết nối máy khách. Bây giờ, hãy mở một cửa sổ đầu cuối khác hoặc dấu nhắc lệnh và chạy ứng dụng khách

$ python echo-server.py
4

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

$ python echo-server.py
5

Trong kết quả ở trên, máy chủ đã in bộ dữ liệu

$ python echo-server.py
634 được trả về từ
$ python echo-server.py
635. Đây là địa chỉ IP và số cổng TCP của máy khách. Số cổng,
$ python echo-server.py
636, rất có thể sẽ khác khi bạn chạy nó trên máy của mình

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

$ python echo-server.py
637. Nó có sẵn theo mặc định trên macOS, Linux và Windows

Đây là đầu ra netstat từ macOS sau khi khởi động máy chủ

$ python echo-server.py
60

Lưu ý rằng

$ python echo-server.py
638 là
$ python echo-server.py
639. Nếu
$ python echo-server.py
640 đã sử dụng
$ python echo-server.py
641 thay vì
$ python echo-server.py
642, netstat sẽ hiển thị điều này

$ python echo-server.py
61

$ python echo-server.py
638 là
$ python echo-server.py
644, 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,
$ python echo-server.py
56 đã được sử dụng [IPv4] trong lệnh gọi tới
$ python echo-server.py
8. Bạn có thể thấy điều này trong cột
$ python echo-server.py
647.
$ python echo-server.py
648

Đầu ra ở trên được cắt bớt để chỉ hiển thị máy chủ tiếng vang. Bạn có thể sẽ 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

$ python echo-server.py
647,
$ python echo-server.py
638 và
$ python echo-server.py
651. Trong ví dụ cuối cùng ở trên, netstat cho thấy rằng máy chủ echo đang sử dụng ổ cắm IPv4 TCP [
$ python echo-server.py
648], trên cổng 65432 trên tất cả các giao diện [
$ python echo-server.py
644] và nó đang ở trạng thái lắng nghe [
$ python echo-server.py
654]

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

$ python echo-server.py
655 [liệt kê các tệp đang mở]. Nó có sẵn theo mặc định trên macOS và có thể được cài đặt trên Linux bằng trình quản lý gói của bạn, nếu nó chưa có

$ python echo-server.py
62

$ python echo-server.py
655 cung cấp cho bạn
$ python echo-server.py
657,
$ python echo-server.py
658 [ID quy trình] và
$ python echo-server.py
659 [ID người dùng] của ổ cắm Internet đang mở khi được sử dụng với tùy chọn
$ python echo-server.py
660. Trên đây là quá trình echo server

$ python echo-server.py
637 và
$ python echo-server.py
655 có rất nhiều tùy chọn khả dụng 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
$ python echo-server.py
663 hoặc tài liệu cho cả hai. Họ chắc chắn đáng để dành một chút thời gian và tìm hiểu. Bạn sẽ được thưởng. Trên macOS và Linux, hãy sử dụng
$ python echo-server.py
664 và
$ python echo-server.py
665. Đối với Windows, hãy sử dụng
$ python echo-server.py
666

Đây là một lỗi phổ biến mà bạn sẽ gặp phải khi thử kết nối với một cổng không có ổ cắm nghe

$ python echo-server.py
63

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

$ python echo-server.py
667. Thêm quy tắc tường lửa cho phép máy khách 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 khảo

Loại bỏ các quảng cáo

sự cố truyền thông

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

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

$ python echo-server.py
600 hoặc địa chỉ IPv6
$ python echo-server.py
669], 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 truyền qua nó là cục bộ của 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
$ python echo-server.py
600 hoặc
$ python echo-server.py
669 được gọi là “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 đang chạy trên máy chủ và để bảo mật và cách ly với mạng bên ngoài. Bởi vì nó là 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 đang hoạt độ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 định cấu hình để chỉ lắng 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 không thể kết nối với nó

Khi bạn sử dụng địa chỉ IP không phải là

$ python echo-server.py
600 hoặc
$ python echo-server.py
669 trong ứng dụng của mình, địa chỉ đó có thể bị ràng buộc với giao diện Ethernet được kết nối với mạng bên ngoài. Đây là cổng vào các máy chủ khác bên ngoài vương quốc “localhost” của bạn

Hãy cẩn thận ở ngoài đó. Đó là một thế giới khó chịu, tàn nhẫn. Hãy nhớ đọc phần Sử dụng tên máy chủ trước khi mạo hiểm thoát khỏi giới hạn an toàn của “localhost. ” Có một lưu ý bảo mật áp dụng ngay cả khi bạn không sử dụng tên máy chủ mà 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ó. Cái lớn nhất là nó chỉ phục vụ một khách hàng và sau đó thoát ra. Ứng dụng khách echo cũng có giới hạn này, nhưng có một vấn đề khác. Khi máy khách sử dụng

$ python echo-server.py
632, có thể nó sẽ chỉ trả về một byte,
$ python echo-server.py
675 từ
$ python echo-server.py
676

$ python echo-server.py
64

Đối số

$ python echo-server.py
677 của
$ python echo-server.py
605 được sử dụng ở trên là lượng dữ liệu tối đa được nhận cùng một lúc. Điều đó không có nghĩa 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]
5 sẽ trả về
$ python echo-server.py
605 byte

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]
4 cũng hoạt động theo cách này. Nó trả về số byte đã gửi, có thể nhỏ hơn kích thước của dữ liệu được truyền vào. Bạn chịu trách nhiệm kiểm tra điều này và gọi 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]
4 nhiều lần nếu 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 chưa; . " [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

$ python echo-server.py
683

“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.

$ python echo-server.py
684 được trả lại khi thành công. " [Nguồn]

Bạn có hai vấn đề tại 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
    # 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 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]
    
    5 cho đến khi tất cả dữ liệu được gửi hoặc nhận

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

$ python echo-server.py
687 đã được đưa vào thư viện chuẩn trong Python 3. 4. Sự lựa chọn truyền thống là sử dụng chủ đề

Rắc rối với đồng thời là rất khó để hiểu đúng. Có nhiều điều tế nhị để xem xét và đề phòng. Tất cả những gì cần làm là để một trong số chúng tự hiển thị và ứng dụng của bạn có thể đột nhiên bị lỗi theo những cách không mấy tế nhị

Điều này không có nghĩa là làm 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à điều cần thiết nếu bạn muốn sử dụng nhiều 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 thứ gì đó thậm chí còn truyền thống hơn các chủ đề và dễ suy luận hơn. Bạn sẽ sử dụng các cuộc gọi hệ thống.

$ python echo-server.py
688

Phương pháp

$ python echo-server.py
688 cho phép bạn kiểm tra việc hoàn thành I/O trên nhiều ổ cắm. Vì vậy, bạn có thể gọi
$ python echo-server.py
688 để xem ổ cắm nào có sẵn I/O để đọc và/hoặc ghi. 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 chuẩn để triển khai hiệu quả nhất được sử dụng, bất kể hệ điều hành bạn đang chạy trên đó là gì.

“Mô-đun này cho phép ghép kênh I/O ở mức độ cao và hiệu quả, được xây dựng dựa trên các nguyên mẫu 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 mẫu cấp hệ điều hành được sử dụng. " [Nguồn]

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

$ python echo-server.py
688, 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 còn rất nhanh. Nó phụ thuộc vào những gì ứng dụng của bạn cần thực hiện khi nó phục vụ một yêu cầu và số lượng máy khách mà nó cần hỗ trợ

$ python echo-server.py
687 sử dụng đa nhiệm hợp tác đơn luồng và vòng lặp sự kiện để quản lý tác vụ. Với
$ python echo-server.py
688, bạn sẽ viết phiên bản vòng lặp sự kiện của riêng mình, 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, nhưng 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 hạn chế hiệu quả 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

$ python echo-server.py
688 có thể là một lựa chọn hoàn toàn tốt. Đừng cảm thấy như bạn phải sử dụng
$ python echo-server.py
687, chủ đề hoặc thư viện không đồng bộ mới nhất. Thông thường, trong ứng dụng mạng, ứng dụng của bạn vẫn bị ràng buộc I/O. nó có thể đang đợi trên mạng cục bộ, các điểm cuối ở phía bên kia của mạng, để ghi đĩa, v.v.

Nếu bạn đang nhận được yêu cầu từ các máy khách bắt đầu công việc liên kết với CPU, hãy xem đồng thời. mô-đun tương lai. Nó chứa lớp ProcessPoolExecutor, sử dụng một nhóm các quy trình để thực hiện các lệnh 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. Để có ý tưởng và cảm hứng, hãy xem buổi nói chuyện về PyCon John Reese - Tư duy bên ngoài GIL với AsyncIO và Đa xử lý - PyCon 2018

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

$ python echo-server.py
688 để xử lý đồng thời nhiều kết nối và 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
4 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]
5 nhiều lần nếu cần

Loại bỏ các quảng cáo

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

$ python echo-server.py
699 được tạo từ mô-đun bộ chọn

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

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

$ python echo-server.py
65

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

$ python echo-server.py
100 để định cấu hình ổ cắm ở chế độ không chặn. Các cuộc gọi đến ổ cắm này sẽ không còn bị chặn. Khi nó được sử dụng với
$ python echo-server.py
101, 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, sau đó đọc và ghi dữ liệu khi sẵn sàng

$ python echo-server.py
102 đăng ký ổ cắm sẽ được giám sát với
$ python echo-server.py
101 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.
$ python echo-server.py
104

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

$ python echo-server.py
105. Nó được trả lại khi
$ python echo-server.py
688 trả về. Bạn sẽ sử dụng
$ python echo-server.py
105 để 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

$ python echo-server.py
66

Khối

$ python echo-server.py
108 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 bộ cho mỗi ổ cắm. Mỗi bộ chứa một
$ python echo-server.py
109 và một
$ python echo-server.py
110.
$ python echo-server.py
109 là SelectorKey
$ python echo-server.py
112 có chứa thuộc tính
$ python echo-server.py
113.
$ python echo-server.py
114 là đối tượng ổ cắm và
$ python echo-server.py
110 là mặt nạ sự kiện của các hoạt động đã sẵn sàng

Nếu

$ python echo-server.py
116 là
$ python echo-server.py
684, thì bạn biết đó là từ ổ cắm đang nghe và bạn cần chấp nhận kết nối. Bạn sẽ gọi hàm
$ python echo-server.py
118 của riêng mình để lấy đối tượng ổ cắm mới và đăng ký nó với bộ chọn. Bạn sẽ nhìn vào đó trong giây lát

Nếu

$ python echo-server.py
116 không phải là
$ python echo-server.py
684, thì bạn biết đó là ổ cắm máy khách đã được chấp nhận và bạn cần bảo dưỡng nó. Sau đó,
$ python echo-server.py
121 được gọi với
$ python echo-server.py
109 và
$ python echo-server.py
110 làm đối số và đó là mọi thứ bạn cần để vận hành trên ổ cắm

Đây là chức năng của hàm

$ python echo-server.py
118 của bạn

$ python echo-server.py
67

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

$ python echo-server.py
104, nó sẽ sẵn sàng để đọc. Bạn gọi
$ python echo-server.py
126 và sau đó gọi
$ python echo-server.py
127 để đặ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 máy chủ này vì bạn không muốn nó bị chặn. Nếu nó chặn, thì toàn bộ máy chủ sẽ bị đình trệ cho đến khi nó hoạt động trở lại. Điều đó có nghĩa là các ổ cắm khác đang chờ mặc dù máy chủ không hoạt động tích cực. Đây là trạng thái “treo” đáng sợ mà bạn không muốn máy chủ của mình gặp phải

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

$ python echo-server.py
128. 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à ghi, cả hai sự kiện đó đều được đặt bằng toán tử OR theo bit

$ python echo-server.py
68

Sau đó, các đối tượng mặt nạ, ổ cắm và dữ liệu của

$ python echo-server.py
129 được chuyển đến
$ python echo-server.py
102

Bây giờ hãy xem

$ python echo-server.py
121 để xem kết nối máy khách được xử lý như thế nào khi sẵn sàng

$ python echo-server.py
69

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

$ python echo-server.py
109 là
$ python echo-server.py
112 được trả về từ
$ python echo-server.py
688 có chứa đối tượng ổ cắm [
$ python echo-server.py
113] và đối tượng dữ liệu.
$ python echo-server.py
110 chứa các sự kiện đã sẵn sàng

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

$ python echo-server.py
137 sẽ đánh giá thành
$ python echo-server.py
138, vì vậy,
$ python echo-server.py
139 được gọi là. Mọi dữ liệu đã đọc đều được thêm vào
$ python echo-server.py
140 để có thể gửi sau

Lưu ý khối

$ python echo-server.py
141 để kiểm tra nếu không nhận được dữ liệu

$ python echo-server.py
10

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 đừng quên gọi cho

$ python echo-server.py
142 trước khi đóng cửa, để nó không còn bị giám sát bởi
$ python echo-server.py
688

Khi ổ cắm đã sẵn sàng để ghi, đây luôn là trường hợp đối với ổ cắm khỏe mạnh, mọi dữ liệu đã nhận được lưu trữ trong

$ python echo-server.py
140 sẽ được gửi lại cho máy khách bằng cách sử dụng
$ python echo-server.py
145. Các byte được gửi sau đó được xóa khỏi bộ đệm gửi

$ python echo-server.py
11

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]
4 trả về số byte đã gửi. Số này sau đó có thể được sử dụng với ký hiệu lát trên bộ đệm
$ python echo-server.py
147 để loại bỏ các byte đã gửi

Loại bỏ các quảng cáo

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

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

$ python echo-server.py
148. Nó rất giống với máy chủ, nhưng thay vì lắng nghe kết nối, nó bắt đầu bằng cách bắt đầu kết nối qua
$ python echo-server.py
149

$ python echo-server.py
12

$ python echo-server.py
150 được đọc từ dòng lệnh và là số lượng kết nối cần tạo tới máy chủ. Giống như máy chủ, mỗi ổ cắm được đặt ở chế độ không chặn

Bạn 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]
3 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
2 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]
2 sẽ ngay lập tức đưa ra một ngoại lệ
$ python echo-server.py
154. 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]
3 ban đầu trả về một chỉ báo lỗi,
$ python echo-server.py
156, thay vì đưa ra một ngoại lệ có thể cản trở kết nối đang diễn ra. Sau khi kết nối hoàn tất, ổ cắm đã sẵn sàng để đọc và ghi và được trả về bởi
$ python echo-server.py
688

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 cách sử dụng

$ python echo-server.py
128. Các tin nhắn mà máy khách sẽ gửi đến máy chủ được sao chép bằng cách sử dụng
$ python echo-server.py
159 vì mỗi kết nối sẽ gọi
$ python echo-server.py
160 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, bao gồm tổng số byte trong tin nhắn, được lưu trữ trong đối tượng
$ python echo-server.py
105

Kiểm tra các thay đổi được thực hiện từ

$ python echo-server.py
121 của máy chủ đối với phiên bản của máy khách

$ python echo-server.py
13

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

Lưu ý rằng bằng cách này, máy chủ phụ thuộc vào máy khách có hoạt động tốt không. máy chủ yêu cầu máy khách đóng phía kết nối của nó khi gửi tin nhắn xong. 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 tế, 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 các kết nối máy khách tích lũy nếu chúng 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ờ là lúc để chạy

$ python echo-server.py
163 và
$ python echo-server.py
148. Cả hai đều sử dụng đố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ủ, vượt qua số

$ python echo-server.py
58 và
$ python echo-server.py
601

$ python echo-server.py
14

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

$ python echo-server.py
167

$ python echo-server.py
15

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

$ python echo-server.py
16

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

$ python echo-server.py
17

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ẽ sử dụng ví dụ này nhiều hơn nữa

Loại bỏ các quảng cáo

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

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 nữa và giải quyết những thiếu sót của ví dụ

$ python echo-server.py
168 trước đó trong lần triển khai cuối cùng. ứng dụng khách và máy chủ

Bạn muốn có một máy khách và máy chủ xử lý lỗi một cách thích hợp để các kết nối khác không bị ảnh hưởng. Rõ ràng, máy khách hoặc máy chủ của bạn sẽ không bị sập trong cơn thịnh nộ nếu một ngoại lệ không bị phát hiện. Đây là điều mà bạn không 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 ngắn gọn và rõ ràng

Bây giờ bạn đã quen thuộc với API cơ bản, non-blocking sockets và

$ python echo-server.py
688, bạn có thể thêm một số cách xử lý lỗi và xử lý con voi trong phòng, mà các ví dụ đã giấu bạn đằng sau bức màn lớn đằng kia. Hãy nhớ rằng lớp tùy chỉnh đã được đề cập trở lại trong phần giới thiệu?

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

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

$ python echo-server.py
170 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à nắm bắt

$ python echo-server.py
170. 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 chúng được thảo luận ở nhiều nơi trong tài liệu. Hết thời gian chờ xảy ra và đượ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ổng chuyển đổi bị hỏng, cáp bị hỏng, 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

Còn con voi trong phòng thì sao? . Nó giống như đọc từ một tệp trên đĩa, nhưng thay vào đó bạn đang đọc các byte từ mạng. Tuy nhiên, không giống như đọc tệp, không có

$ python echo-server.py
173

Nói cách khác, bạn không thể định vị lại con trỏ ổ cắm, nếu có 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. Sau khi bạn đã đọc chúng, chúng cần được lưu ở đâu đó, nếu không bạn sẽ đánh rơi chú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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5 để đọc luồng byte tiếp theo có sẵn từ ổ cắm

Bạn sẽ đọc từ ổ cắm theo khối. Vì vậy, bạn cầ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5 và lưu dữ liệu vào bộ đệm cho đến khi bạn đọc đủ byte để có một thông báo hoàn chỉnh có ý nghĩa đối với ứng dụng của bạn

Tùy thuộc vào bạn để xác định và theo dõi vị trí của ranh giới tin nhắn. Đối với ổ 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ề ý nghĩa của những byte thô đó

Đây là lý do tại sao bạn cần xác định giao thức tầng ứng dụng. Giao thức tầng ứng dụng là gì? . Định dạng của những thông báo này là giao thức của ứ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 sẽ 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 đang đọc các byte 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]
5, bạn cần theo dõi xem có bao nhiêu byte đã được đọc và tìm ra ranh giới của thông báo nằm ở đâu

Làm thế nào bạn có thể làm điều này? . Nếu chúng luôn có cùng kích thước, thì thật dễ dàng. Khi bạn đã đọc số byte đó vào bộ đệm, thì bạn biết mình có một thông báo hoàn chỉnh

Tuy nhiên, việc sử dụng các tin nhắn có độ dài cố định sẽ không hiệu quả đối với các tin nhắn nhỏ mà bạn cần sử dụng phần đệm để điền vào chúng. Ngoài ra, bạn vẫn gặp phải vấn đề phải làm gì với 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 một cách tiếp cận chung, một cách tiếp cận được sử dụng bởi nhiều giao thức, bao gồm cả HTTP. Bạn sẽ thêm tiền tố vào thư với tiêu đề bao gồm độ dài nội dung cũng như bất kỳ trường nào khác mà 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 thư. Với độ dài nội dung, bạn có thể đọc số byte đó để sử dụng nó

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 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 mình. Điều quan trọng nhất là bạn sẽ có thể xem một ví dụ về cách thực hiện điều này

Trước khi bắt đầu, có một số điều bạn cần biết về socket và byte. Như bạn đã biết trước đó, khi gửi và nhận dữ liệu qua socket, bạn đang 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 dữ liệu đó trong ngữ cảnh mà dữ liệu đó được hiểu là nhiều byte, chẳng hạn như số nguyên 4 byte, thì bạn cần tính đến việc dữ liệu đó có thể ở định dạng không có nguồn gốc từ CPU của máy bạn. Máy khách hoặc máy chủ ở đầu bên kia có thể có CPU sử dụng thứ tự byte khác với thứ tự byte của bạn. Nếu trường hợp này xảy ra, thì bạn sẽ cần chuyển đổi nó thành thứ tự byte gốc của máy chủ trước khi sử dụng

Thứ tự byte này được gọi là tuổi thọ của CPU. Xem Byte Endianness trong phần tham khảo để 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 đề thư của mình và sử dụng mã hóa UTF-8. Vì UTF-8 sử dụng mã hóa 8 bit nên không có vấn đề về thứ tự byte

Bạn có thể tìm thấy lời giải thích trong tài liệu Mã hóa và Unicode của Python. 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 đang được gửi, trọng tải tin nhắn. Điều này sẽ cho phép bạn chuyển bất kỳ dữ liệu nào bạn muốn [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

$ python echo-server.py
177. Ví dụ, bạn có thể thấy một cái gì đó như thế này

$ python echo-server.py
18

Nếu bạn chạy cái này trong một máy ảo mô phỏng CPU big-endian [PowerPC], thì điều tương tự sẽ xảy ra

$ python echo-server.py
19

Trong ứng dụng ví dụ này, giao thức lớp ứng dụng của bạn xác định 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, trọng tải tin nhắn, bạn sẽ vẫn phải hoán đổ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 nhiều byte từ một máy có độ bền khác hay không. Bạn có thể giúp máy khách 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 tham số, tương tự như HTTP

Đừng lo lắng nếu điều này chưa có ý nghĩa. Trong phần tiếp theo, bạn sẽ thấy tất cả những thứ này hoạt động và ăn khớp với nhau như thế nào

Loại bỏ các quảng cáo

Tiêu đề giao thức ứng dụng

Bây giờ bạn sẽ xác định đầy đủ tiêu đề giao thức. Tiêu đề giao thức là

  • Văn bản có độ dài thay đổi
  • Unicode với bảng mã UTF-8
  • Một từ điển Python được tuần tự hóa bằng JSON

Các tiêu đề hoặc tiêu đề phụ bắt buộc trong từ điển của tiêu đề giao thức như sau

TênMô tả

$ python echo-server.py
178 Thứ tự byte của máy [sử dụng
$ python echo-server.py
177]. Điều này có thể không cần thiết cho ứng dụng của bạn.
$ python echo-server.py
180Độ dài của nội dung tính bằng byte.
$ python echo-server.py
181Loại nội dung trong tải trọng, ví dụ:
$ python echo-server.py
182 hoặc
$ python echo-server.py
183.
$ python echo-server.py
184Mã hóa mà nội dung sử dụng, ví dụ:
$ python echo-server.py
185 cho văn bản Unicode hoặc
$ python echo-server.py
186 cho dữ liệu nhị phân

Các tiêu đề này thông báo cho người nhận về nội dung trong tải trọng của tin nhắn. Điều này cho phép bạn gửi dữ liệu tùy ý trong khi cung cấp đủ thông tin để người nhận có thể giải mã và diễn giải chính xác nội dung. Vì các tiêu đề nằm trong từ điển nên bạn có thể dễ dàng thêm các tiêu đề bổ sung bằng cách chèn các cặp khóa-giá trị nếu cần

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ó 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]
5?

Trước đây, khi bạn đã học về 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]
5 và ranh giới thư, bạn cũng đã biết 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 để làm tiền tố cho tiêu đề JSON chứa độ dài của nó

Bạn có thể coi đây là một cách tiếp cận hỗn hợp để gửi tin nhắn. Trên thực tế, bạn đang khởi động quá trình nhận tin nhắn 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 cấu trúc tin nhắn

Để giúp bạn hiểu rõ hơn về định dạng thư, hãy xem toàn bộ thư

Một thông báo bắt đầu với tiêu đề có độ dài cố định gồm 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 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]
5, thì bạn biết rằng bạn có thể xử lý hai byte dưới dạng số nguyên rồi đọc số byte đó trước khi giải mã tiêu đề JSON UTF-8

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

$ python echo-server.py
180, là số byte nội dung của tin nhắn [không bao gồm tiêu đề JSON]. Khi bạ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5 và đọc
$ python echo-server.py
180 byte, thì bạn đã đạt đến ranh giới tin nhắn, nghĩa là bạn đã đọc toàn bộ tin nhắn

Lớp tin nhắn ứng dụng

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

$ python echo-server.py
193 và xem nó được sử dụng như thế nào với
$ python echo-server.py
688 khi các sự kiện đọc và ghi xảy ra trên ổ cắm

Ứng dụng ví dụ này phản ánh loại thông báo nào mà máy khách và máy chủ có thể sử dụng một cách hợp lý. Tại thời điểm này, bạn vượt xa các máy khách và máy chủ đồ chơi

Để giữ cho mọi thứ đơn giản và vẫn chứng minh cách mọi thứ sẽ hoạt động trong một ứng dụng thực tế, ví dụ này sử dụng một giao thức ứng dụng triển khai 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 kết quả khớp. Nếu yêu cầu do khách hàng gửi không được công nhận là tìm kiếm, thì máy chủ sẽ cho rằng đó là 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

$ python echo-server.py
193 làm điểm bắt đầu và sửa đổi nó để sử dụng cho riêng mình

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

$ python echo-server.py
168. Mã vòng lặp sự kiện giữ nguyên trong
$ python echo-server.py
197 và
$ python echo-server.py
198. Những gì bạn sẽ làm là di chuyển mã tin nhắn vào một lớp có tên là
$ python echo-server.py
193 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 cho việc sử dụng một lớp

Như bạn đã học trước đây và bạn sẽ thấy bên dưới, làm việc với socket 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 nhóm lại với nhau trong một đơn vị có tổ chức. Một phiên bả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 được chấp nhận

Lớp này hầu như giống nhau cho cả máy khách và máy chủ đối với các phương thức tiện ích và trình bao bọc. Chúng bắt đầu bằng dấu gạch dưới, chẳng hạn 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]
800. Các phương thức này đơn giản hóa việc làm việc với lớp. Chúng hỗ trợ các phương pháp khác bằng cách cho phép chúng ở lại ngắn hơn và hỗ trợ nguyên tắc DRY

Lớp

$ python echo-server.py
193 của máy chủ về cơ bản hoạt động giống như của 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, sau đó xử lý thông báo phản hồi của 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, sau đó gửi thông báo phản hồi

Nó trông như thế này

StepEndpointAction / Nội dung thông điệp1ClientGửi một

$ python echo-server.py
193 chứa nội dung yêu cầu2ServerNhận và xử lý yêu cầu của máy khách
$ python echo-server.py
1933ServerGửi một
$ python echo-server.py
193 chứa nội dung phản hồi4ClientNhận và xử lý phản hồi của máy chủ
$ python echo-server.py
193

Đây là bố cục tệp và mã

ApplicationFileCodeServer

$ python echo-server.py
198Kịch bản chính của máy chủServer
# 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]
807Lớp
$ python echo-server.py
193 của máy chủClient
$ python echo-server.py
197Kịch bản chính của máy kháchClient
# 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]
810Lớp
$ python echo-server.py
193 của khách hàng

Điểm nhập tin nhắn

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

$ python echo-server.py
193 có thể là một thách thức vì có một khía cạnh trong thiết kế của nó có thể không rõ ràng ngay lập tức. Tại sao?

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

$ python echo-server.py
193 được tạo, nó được liên kết với một ổ cắm được 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
814

# 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]
80

Ghi chú. Một số ví dụ mã trong phần này là từ tập lệnh chính của máy chủ và lớp

$ python echo-server.py
193, nhưng phần này và cuộc thảo luận cũng áp dụng như nhau cho máy khách. Bạn sẽ được cảnh báo khi phiên bản của khách hàng khác

Khi các sự kiện đã sẵn sàng trên ổ cắm, chúng sẽ được trả về 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]
816. Sau đó, bạn có thể lấy tham chiếu trở lại đối tượng thông báo bằng cách sử dụng thuộc tính
$ python echo-server.py
105 trên đối tượng
$ python echo-server.py
109 và gọi một phương thức trong
$ python echo-server.py
193

# 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]
81

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

$ python echo-server.py
101 đang ngồi ở ghế lái. Nó đang chặn, chờ sự kiện ở đầu vòng lặp. Nó chịu trách nhiệm đánh thức khi các sự kiện đọc và ghi đã 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821. Đó 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821 là điểm vào

Đây là những gì 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]
821 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
82

Tốt đấ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821 thật đơn giản. Nó chỉ có thể làm hai việc. 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
825 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]
826

Đây là nơi quản lý nhà nước đến. Nếu một phương thức 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
825 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]
826. Điều này giữ cho logic càng đơn giản càng tốt khi các sự kiện xuất hiện trên ổ cắm để xử lý

Bạn có thể muốn sử dụng kết 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, 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
825 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
826. Cuối cùng, điều này có thể sẽ tỏ ra 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 mình để nó hoạt động tốt nhất cho bạn, nhưng bạn có thể sẽ có kết quả tốt nhất nếu bạn giữ kiểm tra trạng thái và gọi các phương thức phụ thuộc vào trạng thái đó đố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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
825 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]
826

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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
825. Đây là phiên bản của máy chủ, nhưng của khách hàng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
834 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
835

# 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

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]
836 được gọi đầ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
837 để đọ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
837 được gọi, tất cả dữ liệu tạo thành một thông báo 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
837 có thể cần được gọi lại. Đây là lý do tại sao có các kiểm tra trạng thái cho từng phần của thông báo trước khi phương thức thích hợp để xử lý nó được gọi là

Trước khi một phương thức xử lý một phần thông báo của nó, trước tiên, nó sẽ kiểm tra để đảm bảo rằng đã đọc đủ byte vào bộ đệm nhận. Nếu có, nó sẽ xử lý các byte tương ứng của nó, loại bỏ chúng khỏi bộ đệm và ghi đầu ra của nó vào một biến được sử dụng trong giai đoạn xử lý tiếp theo. Vì có ba thành phần trong một thông báo nên có ba lần kiểm tra trạng thái và 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
840

Message ComponentMethodOutputFixed-length header

# 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]
841
# 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]
842JSON header
# 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]
843
# 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]
844Content
# 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]
845
# 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]
846

Tiếp theo, hãy xem

# 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]
826. Đây là phiên bản 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
84

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]
826 kiểm tra đầu tiên cho 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
849. Nếu một cái tồn tại và một phản hồi chưa được tạo, thì
# 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]
850 được gọi là. 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]
850 đặ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
852 và ghi phản hồi vào 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
853 gọi
$ python echo-server.py
160 nếu có dữ liệu trong bộ đệm gửi

Hãy nhớ rằng khi

$ python echo-server.py
160 được gọi, tất cả dữ liệu trong bộ đệm gửi có thể chưa được xếp hàng đợi để truyền. Bộ đệm mạng cho ổ cắm có thể đầy và có thể cần phải gọi lại
$ python echo-server.py
160. Đây là lý do tại sao có kiểm tra nhà nướ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
850 chỉ nên được gọi một lần, nhưng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
853 sẽ cần được gọi 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
826 cũng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
85

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 nên 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]
860 được chọn. Nếu một yêu cầu chưa được xếp hàng đợi, 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
861. 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]
862 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
860 để nó chỉ được gọi một lần

Cũng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
853 gọi
$ python echo-server.py
160 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
826 của khách hàng là lần kiểm tra cuối cùng để xem yêu cầu đã được xếp hàng chưa. Điều này sẽ được giải thích thêm trong phần Client Main Script, nhưng lý do của việc này là để báo 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]
816 ngừng theo dõi ổ cắm để ghi các sự kiện. Nếu yêu cầu đã được xếp hàng đợi 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

Để 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
816 đang gọi vào lớp
$ python echo-server.py
193 thông qua 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]
821 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821 sẽ được gọi nhiều lần trong suốt thời gian 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 đang tự kiểm tra biến trạng thái hoặc biến trạng thái do phương thức đặt được kiểm tra bởi người gọi

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

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

$ python echo-server.py
198, các đối số được đọc từ dòng lệnh chỉ định giao diện và cổng để nghe trê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]
86

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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
873, hãy 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
87

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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
874 để 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 tớ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]
875 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
876

# 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]
88

Đặt tùy chọn ổ cắm này sẽ tránh được 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
877. Bạn sẽ thấy điều này khi khởi động máy chủ trên một cổng 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ẽ duy trì ở 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]
878 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
878 hết hạn, thì bạn sẽ nhận được một ngoại lệ
$ python echo-server.py
170 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
877. Đây là một biện pháp bảo vệ để đảm bảo rằng bất kỳ gói bị trì hoãn nào trong mạng không được gửi đến ứng dụng sai

Vòng lặp sự kiện bắt bất kỳ lỗi nào để máy chủ có thể duy trì 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
89

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

$ python echo-server.py
193 đượ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
80

Đối tượng

$ python echo-server.py
193 được liên kết với ổ cắm trong lệnh gọi tới
$ python echo-server.py
102 và ban đầu được đặt để chỉ giám sát các sự kiện đọc. Khi yêu cầu đã được đọc, bạn sẽ sửa đổi nó để chỉ lắng nghe các sự kiện ghi

Một lợi thế của việc sử dụng phương pháp này trong máy chủ là trong hầu hết các trường hợp, khi ổ cắm hoạt động tốt và không có sự cố mạng, nó sẽ luôn có thể ghi được

Nếu bạn yêu cầu

$ python echo-server.py
102 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
886, 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
# 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 trên ổ cắm. Không có phản hồi để gửi vì yêu cầu chưa được xử lý. Điều này sẽ tiêu tốn và lãng phí các chu kỳ CPU có giá trị

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

Trong phần Điểm nhập thông báo, bạn đã học cách đối tượng

$ python echo-server.py
193 được gọi vào hoạt độ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821. Bây giờ, bạn sẽ tìm hiểu điều gì sẽ xảy ra khi dữ liệu được đọc trên ổ cắm và một thành phần hoặc một phần của thông báo đã sẵn sàng để máy chủ xử lý

Lớp thông báo của máy 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
807, đây là một phần của mã nguồn mà 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

Nhận mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng cho các ví dụ trong hướng dẫn này

Các phương thức xuất hiện trong lớp theo thứ tự xử lý thông báo

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]
81

Tiêu đề có độ dài cố định là một số nguyên 2 byte theo thứ tự byte mạng hoặc big-endian. Nó chứa độ dài của tiêu đề JSON. Bạn sẽ sử dụng cấu trúc. unpack[] để đọc giá trị, giải mã và lưu trữ 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
842. Sau khi xử lý đoạn tin nhắn 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
892 xóa nó khỏi bộ đệm nhận

Cũng giống như 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]
82

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]
893 được gọi để giải mã và giải tuần tự hóa tiêu đề JSON thành một từ điển. Vì tiêu đề JSON được xác định là Unicode với mã hóa UTF-8, nên
$ python echo-server.py
185 đượ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
844. Sau khi xử lý đoạn tin nhắn 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
843 xóa 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
844. Khi có sẵn byte
$ python echo-server.py
180 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]
83

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

$ python echo-server.py
105,
# 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]
835 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
835 sẽ giải mã và giải tuần tự hóa nó. Nếu không, ứng dụng ví dụ này giả định rằng đó 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 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]
835 làm 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ủ,
$ python echo-server.py
198, ổ cắm ban đầu được đặt để chỉ giám sát các sự kiện đã đọc. Giờ đây, yêu cầu đã được xử lý hoàn toàn, bạn không còn hứng thú đọc nữa

Bây giờ một phản hồi 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
850 đượ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
826

# 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]
84

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, một 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
806. Đố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 thức khác được gọi tạ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
807 đượ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]
826 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
850. 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
853

Một chút khó khăn để 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 tớ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]
6 theo 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]
853

# 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]
85

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

$ python echo-server.py
193 chỉ xử lý một thông báo cho mỗi kết nối. Sau khi phản hồi được viết, máy chủ không còn việc gì để làm. Nó đã hoàn thành công việc của nó

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,

$ python echo-server.py
197, các đối số được đọc từ dòng lệnh và được sử dụng để tạo yêu cầu và bắt đầu 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]
86

Đâ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]
87

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]
815

# 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]
88

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

$ python echo-server.py
193 sử dụ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
849

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

$ python echo-server.py
193 được liên kết với ổ cắm trong lệnh gọi tới
$ python echo-server.py
102. Tuy nhiên, đối với máy khách, ổ cắm ban đầu được đặt để được giám sát cho cả sự kiện đọc và ghi. Khi yêu cầu đã được viết, bạn sẽ sửa đổi nó để chỉ lắng nghe các sự kiện đã đọc

Cách tiếp cận này mang lại cho bạn lợi thế giống 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 nữa, 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 Điểm nhập thông báo, bạn đã học cách gọi đối tượng thông báo 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
821. Bây giờ bạn sẽ tìm hiểu điều gì sẽ 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 để máy khách xử lý

Lớp thông báo của khách hàng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
810, đây là một phần của mã nguồn mà 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

Nhận mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng cho các ví dụ trong hướng dẫn này

Các phương thức xuất hiện trong lớp theo thứ tự xử lý thông báo

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]
89

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

$ python echo-server.py
197. Từ điển yêu cầu được truyền dưới dạng đối số cho lớp khi đối tượng
$ python echo-server.py
193 đượ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 xem 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
853. 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]
825 đượ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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
861 không đượ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 pháp đọc và xử lý tin nhắn trong máy khách cũng giống như đối với máy chủ. Khi 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
840 được 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
892 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]
829

Sự khác biệt nằm ở cách đặt tên của 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
840 cuối cùng và thực tế là chúng đang xử lý một phản hồi chứ không phải tạo một phản hồ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]
834,
# 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]
832 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]
833

Cuối cùng nhưng 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
834

# 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]
90

Gói lớp tin nhắn

Để kết thúc quá trình tìm hiểu của bạn về lớp

$ python echo-server.py
193, cần đề cập đến một số điều quan trọng cần lưu ý cùng với một số phương pháp hỗ trợ

Bất kỳ ngoại lệ nào do lớp đưa ra đều bị tập lệnh chính bắt trong mệ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]
836 bên trong vòng lặp sự kiệ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

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]
837

Đâ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]
837 còn loại bỏ ổ cắm khỏi sự giám sát của
$ python echo-server.py
688. Điều này đơn giản hóa đáng kể 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êu ra một cách rõ ràng, bạn biết 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
6 sẽ lo 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]
841 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]
842 cũng chứa một số điều thú 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

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]
843

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]
853 cũng có một. Những dòng này rất quan trọng vì chúng phát hiện lỗi tạm thời và bỏ qua 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]
845. Lỗi tạm thời là khi ổ cắm bị chặn, chẳng hạn như nếu nó đang chờ 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]
845,
$ python echo-server.py
688 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 ghi dữ liệu

Loại bỏ các quảng cáo

Chạy ứng dụng Client và Server

Sau tất cả những công việc khó khăn này, đã đến lúc vui chơi và thực hiện 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ố

$ python echo-server.py
58. Điều này sẽ cho phép bạn chạy ứng dụng khách và kết nối từ một máy ảo trên một mạng khác. Nó mô phỏng một cỗ máy PowerPC lớn

Đầu tiên, khởi động 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]
93

Bây giờ hãy chạy ứng dụng khách và nhập tìm kiếm. Xem nếu bạn có thể tìm thấy anh ta

# 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

Bạn có thể nhận thấy rằng thiết bị đầu cuối đang chạy trình bao sử dụng mã hóa văn bản Unicode [UTF-8], do đó, đầu ra ở trên được in đẹp mắt với các biểu tượng cảm xúc

Bây giờ hãy xem bạn có thể tìm thấy những chú chó con khô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]
95

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]
849. Sẽ dễ dàng hơn nếu bạn tìm kiếm các byte được in ở dạng hex đại diện cho biểu tượng cảm xúc cún 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]
850. Nếu thiết bị đầu cuối của bạn đang sử dụng Unicode với mã hóa UTF-8, bạn sẽ có thể nhập biểu tượng cảm xúc cho tìm kiếm

Điều này chứng tỏ rằng bạn đang gửi các byte thô qua mạng và chúng cần được người nhận giải mã để được diễn giải chính xác. Đây là lý do tại sao bạn gặp khó khăn khi tạo tiêu đề chứa loại nội dung và mã hóa

Đây là đầu ra máy chủ từ cả hai kết nối máy khách ở trê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]
96

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]
849 để 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 việc 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]
852 là bất kỳ thứ 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]
853

# 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

Bởi vì yêu cầu

$ python echo-server.py
181 không phải là
$ python echo-server.py
182, nên 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ỉ cần in
$ python echo-server.py
181 và trả về mười byte đầu tiên cho 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]
98

Xử lý sự cố

Chắc chắn, một cái gì đó sẽ không hoạt động 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ự trợ giúp của hướng dẫn này, trình gỡ lỗi và công cụ tìm kiếm yêu thích của bạn, bạn sẽ có thể bắt đầu 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 về mô-đun ổ cắm của Python. Đảm bảo rằng bạn đã đọc tất cả tài liệu cho từng chức năng hoặc phương thức mà bạn đang gọi. Ngoài ra, hãy đọc qua phần Tham khảo bên dưới để biết ý tưởng. Cụ thể, hãy 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ể đúng và đó chỉ là máy chủ, máy khách hoặc máy chủ khác. 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 đóng vai trò trung gian

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

Loại bỏ các quảng cáo

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]
857 sẽ kiểm tra xem máy chủ có còn hoạt động và được kết nối với mạng hay không bằng cách gửi yêu cầu tiếng vang 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, vì vậy nó hoạt động độc lập với bất kỳ ứng dụng nào đang chạy trên máy chủ

Dưới đây là ví dụ chạy ping trên macOS

# 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

Lưu ý 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 sự cố kết nối không liên tục. Ví dụ, có mất gói nào không?

Nếu có tường lửa giữa bạn và máy chủ khác, yêu cầu tiếng vang của 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 máy chủ của mình có thể được khám phá. Nếu trường hợp này xảy ra và bạn đã thêm các quy tắc tường lửa để 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 chuyển 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]
857, nhưng nó cũng là giao thức mà TCP và các giao thức cấp thấp khác sử dụng để truyền thông báo lỗi. Nếu bạn đang gặp hành vi lạ hoặc kết nối chậm, đây có thể là lý do

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

Loại ICMP Mã ICMP Mô tả80Yêu cầu tiếng vang00Trả lời tiếng vang30Không thể truy cập mạng đích31Không thể truy cập máy chủ đích32Không thể truy cập giao thức đích33Không thể truy cập cổng đích34Yêu cầu phân đoạn và đặt cờ DF110TTL đã hết hạn trong quá trình vận chuyển

Xem bài viết Khám phá đường dẫn MTU để biết thông tin về phân mảnh và thông báo ICMP. Đây là một ví dụ về điều gì đó có thể gây ra hành vi lạ

netstat

Trong phần Xem trạng thái ổ cắm, bạn đã học cách sử dụng

$ python echo-server.py
637 để 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]
860 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]
861 trong đầu ra ví dụ. Các cột này sẽ hiển thị cho bạn số lượng byte được giữ trong bộ đệm mạng được xếp hàng đợi để truyền hoặc nhận, nhưng vì lý do nào đó chưa được đọc hoặc ghi bởi ứng dụng từ xa hoặc cục bộ

Nói cách khác, các byte đang đợi trong bộ đệm mạng trong hàng đợi của 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
837 hoặc
$ python echo-server.py
160 và xử lý các byte. Hoặc có thể có sự cố mạng ảnh hưởng đến hoạt động liên lạc, chẳng hạn như tắc nghẽn hoặc lỗi phần cứng hoặc cáp mạng

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

$ python echo-server.py
160. 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
837. Nó chỉ chấp nhận kết nối. Điều này khiến bộ đệm mạng trên máy chủ bị lấp đầy, điều này cuối cùng sẽ gây ra lỗi trên máy khách

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

$ python echo-server.py
0

Sau đó chạy client xem lỗi là gì

$ python echo-server.py
1

Đây là kết quả đầu ra của

$ python echo-server.py
637 trong khi máy khách và máy chủ vẫn đang chạy, với máy khách in ra thông báo lỗi ở trên nhiều lần

$ python echo-server.py
2

Mục đầu tiên là máy chủ [______2638 có cổng 65432]

$ python echo-server.py
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]
860.
# 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]
869

Mục thứ hai là máy khách [____5870 có cổng 65432]

$ python echo-server.py
4

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]
861.
# 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]
872

Máy khách chắc chắn đang cố ghi byte, nhưng máy chủ không đọc chúng. Điều này khiến hàng đợi bộ đệm mạng của máy chủ được lấp đầy ở bên nhận và hàng đợi bộ đệm mạng của máy khách được lấp đầy ở bên 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 xem qua nếu chưa có. hệ thống Windows

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]
873. TCPView là một
$ python echo-server.py
637 đồ họa cho Windows. Ngoài địa chỉ, số cổng và trạng thái ổ cắm, nó sẽ hiển thị cho bạn tổng số đang chạy cho số lượng gói và byte được gửi và nhận. Giống như với tiện ích Unix
$ python echo-server.py
655, bạn cũng nhận được tên quy trình và ID. Kiểm tra các menu để biết các tùy chọn hiển thị khác

cá mập

Đôi khi bạn cần xem những gì đang xảy ra trên dây. Quên những gì nhật ký ứng dụng nói hoặc giá trị được trả về 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 trình gỡ lỗi, khi bạn cần xem nó, không có gì thay thế

Wireshark là một ứng dụng phân tích giao thức mạng và nắm bắt lưu lượng chạy trên macOS, Linux và Windows, trong số những ứng dụng khác. Có một phiên bản GUI 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]
876 và cũng có một phiên bản dựa trên văn bản dựa trên thiết bị đầu cuối 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]
877

Chạy nắm bắt lưu lượng là một cách tuyệt vời để xem ứng dụng hoạt động như thế nào trên mạng và thu thập bằng chứng về những gì nó gửi và nhận cũng như tần suất và mức độ. Bạn cũng có thể biết khi nào máy khách hoặc máy chủ đóng hoặc hủy 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 khắc phục sự cố

Có rất nhiều hướng dẫn hay và các tài nguyên khác trên web sẽ hướng dẫn bạn những kiến ​​thức cơ bản về cách sử dụng Wireshark và TShark

Đây là một ví dụ về nắm bắt lưu lượng sử dụng Wireshark trên giao diện loopback

Đây là 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]
877

$ python echo-server.py
5

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 mình

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 nguồn bên ngoài

Tài liệu Python

  • Mô-đun ổ cắm của Python
  • Lập trình socket của Python HOWTO

lỗi

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

# 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]
879 của Python

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

$ python echo-server.py
170 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 socket

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]
881 ConstantDescriptionBlockingIOErrorEWOULDBLOCKTài nguyên tạm thời không khả dụng. Ví dụ: ở chế độ không chặn, khi 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
4 và máy ngang hàng đang bận và không đọc, hàng đợi gửi [bộ đệm mạng] đã đầy. Hay mạng có vấn đề. 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 không có quy trình nào khác đang chạy sử dụng cùng 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]
883.
# 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]
884. ConnectionResetErrorECONNRESETThiết lập lại kết nối theo ngang hàng. Quá trình từ xa bị lỗi hoặc không đóng ổ cắm đúng cách, còn được gọi là tắt máy không sạch sẽ. Hoặc có tường lửa hoặc thiết bị khác trong đường dẫn mạng thiếu quy tắc hoặc hoạt động sai. TimeoutErrorETIMEDOUTThao tác đã hết thời gian chờ. Không có phản hồi từ đồng nghiệp. ConnectionRefusedErrorECONNREFUDKết nối bị từ chối. Không có ứng dụng nào lắng nghe trên cổng được chỉ định

Gia đình địa chỉ ổ cắm

$ python echo-server.py
56 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]
886 đại diện cho họ địa chỉ và giao thức được sử dụng cho đối số đầu tiên 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
7. Các 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
$ python echo-server.py
56 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]
886

Địa chỉ FamilyProtocolAddress TupleDescription

$ python echo-server.py
56IPv4
$ python echo-server.py
57
$ python echo-server.py
58 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]
893 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]
894.
$ python echo-server.py
601 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]
886IPv6
$ python echo-server.py
618
$ python echo-server.py
58 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]
893 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]
900.
$ python echo-server.py
601 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]
902 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]
903 đạ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]
904 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]
905 trong cấu trúc 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]
906

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

$ python echo-server.py
58 của bộ địa chỉ

“Đối với địa chỉ IPv4, hai hình thức đặ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]
908 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]
909 đạ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]
910. 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 mình. " [Nguồn]

Xem tài liệu về họ Ổ cắm của Python để 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ợ, 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à sử dụng ổ cắm chức năng. getaddrinfo[]. Nó dịch các đối số

$ python echo-server.py
58 và
$ python echo-server.py
601 thành một chuỗi gồm năm bộ chứa tất cả các đối số cần thiết để tạo một ổ cắm được kết nối với dịch 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]
913 sẽ hiểu và giải thích các địa chỉ IPv6 được truyền vào và tên máy chủ phân giải thành địa chỉ IPv6, ngoài IPv4

Ví dụ sau trả về thông tin địa chỉ cho kết nối TCP tớ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]
914 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
915

>>>

$ python echo-server.py
6

Kết quả có thể khác trên hệ thống của bạn nếu IPv6 không đượ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 tớ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]
7 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]
917. 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 của Python

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

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

$ python echo-server.py
9 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]
2 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
3, khi bạn định sử dụng giao diện loopback, “localhost. ” Tuy nhiên, nó cũng áp dụng bất cứ khi nào bạn đang sử dụng tên máy chủ và có kỳ vọng rằng nó sẽ phân giải thành một địa chỉ nhất định và có ý nghĩa đặc biệt đối với ứng dụng của bạn, điều này ảnh hưởng đến hành vi hoặc giả định của nó. Điều này trái ngược với trường hợp điển hình của một máy khách sử dụng tên máy chủ để kết nối với máy chủ được phân giải bằng DNS, chẳng hạn như www. thí dụ. com

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

# 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]
879 của Python

“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 phân giải khác 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 địa chỉ số trong phần máy chủ. " [Nguồn]

Quy ước tiêu chuẩn cho tên “localhost” là để nó phân giải thành

$ python echo-server.py
600 hoặc
$ python echo-server.py
669, giao diện loopback. Điều này nhiều khả năng sẽ xảy ra với bạn trên hệ thống của mình, 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 để phân giải tên. Như với tất cả mọi thứ về CNTT, 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 “localhost” sẽ kết nối với giao diện loopback

Ví dụ: trên Linux, hãy xem

# 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]
924, 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-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]
925. Trên Windows, xem
# 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]
926. Tệ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]
927 chứa một bảng tĩnh ánh xạ tên tới địa chỉ ở định dạng văn bản đơn giản. DNS hoàn toàn là một phần khác của câu đố

Thật thú vị, kể từ tháng 6 năm 2018, có một dự thảo RFC Hãy để 'localhost' là localhost thảo luận về các quy ước, giả định và bảo mật xung quanh việc sử dụng tên "localhost. ”

Điều quan trọng cần hiểu là khi bạn sử dụng tên máy chủ trong ứng dụng của mình, địa chỉ được trả về có thể là bất kỳ thứ gì. Đừng đưa ra các giả định về 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 của bạn

Ghi chú. Các biện pháp phòng ngừa bảo mật và các phương pháp hay nhất vẫn được áp dụng, ngay cả khi ứng dụng của bạn rõ ràng không nhạy cảm về bảo mật. Nếu ứng dụng của bạn truy cập mạng, nó phải được bảo mật và duy trì. Điều này có nghĩa là, ở mức tối thiểu

  • Các bản cập nhật phần mềm hệ thống và bản vá bảo mật được áp dụng thường xuyên, bao gồm cả Python. Bạn có đang sử dụng bất kỳ thư viện của bên thứ ba nào không?

  • Nếu có thể, hãy sử dụng tường lửa chuyên dụng hoặc dựa trên máy chủ để chỉ hạn chế kết nối với các hệ thống đáng tin cậy

  • Máy chủ DNS nào được cấu hình?

  • Đảm bảo rằng dữ liệu yêu cầu được làm sạch và xác thực càng nhiều càng tốt trước khi gọi mã khác xử lý dữ liệu đó. Sử dụng fuzz tests cho việc này và chạy chúng thường xuyên

Bất kể bạn có đang 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ì có thể bạn sẽ muốn xem xét sử dụng TLS. Đây là chủ đề riêng của nó và nằm ngoài phạm vi của hướng dẫn này. Xem tài liệu mô-đun ssl của Python để 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 các giao diện, địa chỉ IP và độ phân giải tên cần xem xét, có nhiều biến số. Những gì bạn nên làm?

ApplicationUsageRecommendationServerloopback interfaceSử dụng địa chỉ IP, chẳng hạn như

$ python echo-server.py
600 hoặc
$ python echo-server.py
669. Giao diện serverethernetSử dụng địa chỉ IP, chẳng hạn như
$ python echo-server.py
607. Để 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 lưu ý bảo mật ở trên. Giao diện clientloopbackSử dụng địa chỉ IP, chẳng hạn như
$ python echo-server.py
600 hoặc
$ python echo-server.py
669. Giao diện clientethernetSử dụng địa chỉ IP để đảm bảo tính nhất quán 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, hãy sử dụng tên máy chủ. Xem lưu ý 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 cuộc gọi

Hàm hoặc phương thức ổ cắm tạm thời treo ứng dụng của bạn là lệnh gọi chặn. Ví dụ: khố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]
1,
# 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,
# 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 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]
5, nghĩa là chúng không quay trở lại ngay lập tức. Cuộc gọi chặn phải đợi cuộc gọi hệ thống [I/O] hoàn tất trước khi chúng có thể trả về giá trị. Vì vậy, bạn, người gọi, bị chặn cho đến khi họ thực hiện xong hoặc hết thời gian chờ hoặc xảy ra lỗi khác

Các cuộc gọi ổ cắm chặn 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ì ít nhất bạn sẽ cần cấu trúc lại hoặc thiết kế lại ứng dụng của mình để xử lý hoạt động của ổ cắm khi nó sẵn sàng

Vì cuộc gọi trở lại ngay lập tức nên dữ liệu có thể chưa sẵn sàng. Callee đang đợi trên mạng và không 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]
881
# 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]
938. Chế độ không chặn được hỗ trợ với. thiết lập chặn []

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

Đóng kết nối

Một điều thú vị cần lưu ý với TCP là việc 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ở là hoàn toàn hợp pháp. Điều này được gọi là kết nối “nửa mở”. Đó là quyết định của ứng dụng cho dù điều này có được mong muốn hay không. Nói chung là không. Ở trạng thái này, bên đã đóng kết nối không còn có thể gửi dữ liệu. Họ chỉ có thể nhận

Cách tiếp cận này không nhất thiết được khuyến nghị, nhưng ví dụ: HTTP sử dụng tiêu đề có tên "Kết nối" được sử dụng để chuẩn hóa cách các ứng dụng nên đóng hoặc duy trì các kết nối mở. Để biết chi tiết, xem phần 6. 3 trong RFC 7230, Giao thức truyền siêu văn bản [HTTP/1. 1]. Cú pháp tin nhắn và định tuyến

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

Độ bền byte

Xem bài viết về tuổi thọ của Wikipedia để biết chi tiết về cách các CPU khác nhau lưu trữ thứ tự byte trong bộ nhớ. Khi diễn giải các byte riêng lẻ, đây không phải là vấn đề. Tuy nhiên, khi bạn đang xử lý nhiều byte được đọc và xử lý dưới dạng một giá trị, chẳng hạn như số nguyên 4 byte, thì thứ tự byte cần được đảo ngược nếu bạn đang giao tiếp với một máy sử dụng độ cuối khác

Thứ tự byte cũng quan trọng đối với các chuỗi văn bản được biểu diễn dưới dạng chuỗi nhiều byte, như Unicode. Trừ khi bạn luôn sử dụng ASCII đúng, nghiêm ngặt và kiểm soát việc triển khai máy khách và máy chủ, có lẽ tốt hơn hết bạn nên sử dụng Unicode với mã hóa như UTF-8 hoặc mã hỗ trợ dấu thứ tự byte [BOM]

Điều quan trọng là xác định rõ ràng mã hóa được sử dụng trong giao thức lớp ứng dụng của bạn. Bạn có thể làm điều này bằng cách yêu cầu tất cả văn bản là UTF-8 hoặc sử dụng tiêu đề "mã hóa nội dung" chỉ định mã hóa. Điều này ngăn không cho ứng dụng của bạn phát hiện mã hóa mà bạn nên tránh nếu có thể

Điều này trở nên có vấn đề khi có dữ liệu liên quan được lưu trữ trong tệp hoặc cơ sở dữ liệu và không có siêu dữ liệu nào chỉ định mã hóa của dữ liệu đó. Khi dữ liệu được chuyển đến một điểm cuối khác, nó sẽ phải cố gắng phát hiện mã hóa. Để thảo luận, hãy xem bài viết Unicode của Wikipedia, tham khảo 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 thứ tự 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 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 lẻ nào với tập 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]

Điểm nổi bật của điều này là luôn lưu trữ mã hóa được sử dụng cho dữ liệu được ứng dụng của bạn xử lý 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 là UTF-8 hoặc một số mã hóa khác có BOM. Sau đó, bạn có thể gửi mã hóa đó trong tiêu đề cùng với dữ liệu để cho người nhận biết đó là gì

Thứ tự byte được sử dụng trong TCP/IP là thứ tự lớn và được gọi là thứ tự mạng. Thứ tự mạng được sử dụng để biểu thị các số nguyên ở 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 của Python bao gồm các chức năng chuyển đổi số nguyên sang và từ thứ tự byte của mạng và máy chủ

Chức năngMô 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]
939Chuyển đổi số nguyên dương 32 bit từ mạng sang thứ tự byte máy chủ. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh 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]
940Chuyển đổi số nguyên dương 16 bit từ mạng sang thứ tự byte máy chủ. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh 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]
941Chuyển đổi số nguyên dương 32 bit từ máy chủ sang thứ tự byte mạng. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh 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]
942Chuyển đổi số nguyên dương 16 bit từ máy chủ sang thứ tự byte mạng. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh cấm;

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

$ python echo-server.py
7

Sự kết luận

Bạn đã bao phủ 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 chưa quen với mạng hoặc ổ cắm, đừng nản lòng với tất cả các thuật ngữ và từ viết tắt

Có rất nhiều phần cần làm quen để 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 làm quen với từng phần riêng lẻ và dành nhiều thời gian hơn cho chúng

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

  • Đã xem API ổ cắm cấp thấp trong mô-đun
    # 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]
    
    879 của Python và xem cách nó có thể được sử dụng để tạo các ứng dụng máy khách-máy chủ
  • Xây dựng máy khách và máy chủ có thể xử lý nhiều kết nối bằng đối tượ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]
    
    944
  • Tạo lớp tùy chỉnh của riêng bạn và sử dụng lớp đó làm giao thức lớp ứng dụng để trao đổi thông báo và dữ liệu giữa các điểm cuối

Từ đây, bạn có thể sử dụng lớp tùy chỉnh của mình và xây dựng dựa trên lớp đó để tìm hiểu và giúp tạo các ứng dụng socket 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

Nhận mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng cho các ví dụ trong hướng dẫn này

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

Đánh dấu là đã hoàn thành

🐍 Thủ thuật Python 💌

Nhận một Thủ thuật Python ngắn và hấp dẫn được gửi đến hộp thư đến của bạn vài ngày một lần. Không có thư rác bao giờ. Hủy đăng ký bất cứ lúc nào. Được quản lý bởi nhóm Real Python

Gửi cho tôi thủ thuật Python »

Giới thiệu về Nathan Jennings

Nathan là thành viên của nhóm hướng dẫn Real Python, người đã bắt đầu sự nghiệp lập trình viên của mình với C từ lâu, nhưng cuối cùng đã tìm thấy Python. Từ các ứng dụng web và thu thập dữ liệu đến mạng và bảo mật mạng, anh ấy thích mọi thứ Pythonic

» Thông tin thêm về Na-than

Mỗi hướng dẫn tại Real Python được tạo bởi một nhóm các nhà phát triển để nó đáp ứng các tiêu chuẩn chất lượng cao của chúng tôi. Các thành viên trong nhóm đã làm việc trong hướng dẫn này là

Aldren

Brad

Geir Arne

Ian

Jim

Joanna

kate

Bậc thầy Kỹ năng Python trong thế giới thực Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng nghìn hướng dẫn, khóa học video thực hành và cộng đồng các Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bậc thầy Kỹ năng Python trong thế giới thực
Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng ngàn hướng dẫn, khóa học video thực hành và cộng đồng Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bạn nghĩ sao?

Đánh giá bài viết này

Tweet Chia sẻ Chia sẻ Email

Bài học số 1 hoặc điều yêu thích mà bạn đã học được là gì?

Mẹo bình luận. Những nhận xét hữu ích nhất là những nhận xét được viết với mục đích học hỏi hoặc giúp đỡ các sinh viên khác. Nhận các mẹo để đặt câu hỏi hay và nhận câu trả lời cho các câu hỏi phổ biến trong cổng thông tin hỗ trợ của chúng tôi

Những lệnh socket nào được sử dụng trong Python?

Các hàm và phương thức API ổ cắm chính trong mô-đun này là. .
ổ cắm[]
trói buộc[]
nghe[]
Chấp nhận[]
liên kết[]
connect_ex[]
gửi[]
recv[]

Python có tự động đóng ổ cắm không?

Python tiến thêm một bước nữa về tính năng tự động tắt và nói rằng khi một ổ cắm được thu gom rác, nó sẽ tự động đóng nếu cần.

Ổ cắm có sẵn trong Python không?

Thư viện chuẩn của Python bao gồm nhiều mô-đun tích hợp khác nhau hỗ trợ kết nối và giao tiếp giữa các quá trình. Truy cập mạng có sẵn ở hai cấp độ. Mô-đun 'ổ cắm' xác định cách máy chủ và máy khách có thể giao tiếp ở cấp độ phần cứng bằng cách sử dụng các điểm cuối ổ cắm trên đầu hệ điều hành .

Chủ Đề