Liên kết ổ cắm Python

def _testTCPClientOption(bản thân, cấp độ, tùy chọn, giá trị). sock = Không thử. # Trước tiên hãy nghe trên ổ cắm máy chủ để kết nối không bị từ chối. server_sock = ổ cắm. ổ cắm (ổ cắm. AF_INET, ổ cắm. SOCK_STREAM) server_sock. liên kết ((HOST, PORT) ) server_sock. nghe(50) # Bây giờ hãy kiểm tra sock = socket. ổ cắm (ổ cắm. AF_INET, ổ cắm. SOCK_STREAM) tự. _testSetAndGetOption(sock, level, option, values) # bây giờ kết nối ổ cắm i. e. khiến ổ cắm triển khai được tạo # Liên kết đầu tiên, để cài đặt SO_REUSEADDR lan truyền sock. liên kết ((HOST, PORT+1) ) sock. connect( (HOST, PORT) ) msg ​​= "Giá trị tùy chọn '%s'='%s' không truyền tới ổ cắm triển khai" % (tùy chọn, giá trị[-1]) nếu tùy chọn trong (socket. SO_RCVBUF, ổ cắm. SO_SNDBUF). # GHI CHÚ. không có gì đảm bảo rằng bufsize sẽ là giá trị # bộsockopt chính xác, đặc biệt là sau khi # thiết lập kết nối. có vẻ như nó sẽ là *ít nhất* # các giá trị mà chúng tôi kiểm tra (khá nhỏ) trên # BSD. bản thân. khẳng định_(vớ. getockopt(level, option) >= values[-1], msg) other. bản thân. failUnlessEqual(sock. getockopt(cấp độ, tùy chọn), giá trị[-1], msg) self. _testSetAndGetOption(sock, level, option, values) cuối cùng. server_sock. close() if sock. sock. close()

Up until this point, I have shown you how you can use the socket module to make use of socket connections permitted by other clients and programs. Now it is time to cover how to do this ourselves

The way this is done should sound fairly expectable, as you know the requirements of sockets. First you bind a socket, then you listen on a port for incoming connections

		
		
import socket
import sys
 
HOST = '' 
PORT = 5555 
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


		

Socket import for sockets, sys import to execute a quit if there is a failure

Next, we specify our host and port. You don't really need the host to be specified, but it is good practice to include it in your code, so we have. Finally, we're specifying a port. Pick whatever you want, just choose a high number so it hopefully doesn't conflict with another program of yours

try:
    s.bind((HOST, PORT))
    
except socket.error as msg:
    print('Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1])
    sys.exit()
	
print('Socket bind complete')
		

Here, we are simply attempeting to bind a socket locally, on port 5555. If that fails, then we post the error and exit the program

s.listen(10)

conn, addr = s.accept()

print('Connected with ' + addr[0] + ':' + str(addr[1]))

		

Next, we're going to use s. listen to listen. Here, the "10" stands for how many incoming connections we're willing to queue before denying any more

Now you can run your script. Once you've done that, you should be able to make a connection. You will likely get a security notifcation that you must accept in order to continue with the tutorial. You are getting this notification because the program is trying to open and listen for incoming connections on your behalf. As you might imagine, people might attempt to give you malicious software to do just this

Accept the warning if you get one, and now you should be able to telnet localhost on the port you chose, which was 5555 for me. Vì vậy, mở bash, shell hoặc cmd. exe, type

telnet localhost 5555

For now, nothing much will happen, but you should see a black window and your running python script should update with the incoming connection address

Sockets and the socket API are used to send messages across a network. They provide a form of inter-process communication (IPC). The network can be a logical, local network to the computer, or one that’s physically connected to an external network, with its own connections to other networks. 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

Background

Ổ cắm có một lịch sử lâu dài. Chúng được sử dụng vào năm 1971 và sau đó trở thành API trong hệ điều hành Berkeley Software Distribution (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 tải (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

Sao nó lại 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

Liên kết ổ cắm Python
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ột 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 thành 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ủ

# echo-server.py

import socket

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

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

Note. Đừ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

Ở 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ợ , vì vậy bạn có thể sử dụng nó trong. Không cần gọi
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
0

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

Các đối số được chuyển đến được sử dụng để chỉ định loại và ổ cắm.

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
2 is the Internet address family for IPv4.
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
3 is the socket type for , the protocol that will be used to transport messages in the network

The

$ python echo-server.py
9 method is used to associate the socket with a specific network interface and port number

# echo-server.py

# ...

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

The values passed to

$ python echo-server.py
9 depend on the of the socket. In this example, you’re using
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
6 (IPv4). So it expects a two-tuple.
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
7

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 can be a hostname, IP address, or empty string. If an IP address is used,
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 should be an IPv4-formatted address string. The IP address
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 is the standard IPv4 address for the loopback interface, so only processes on the host will be able to connect to the server. If you pass an empty string, the server will accept connections on all available IPv4 interfaces

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01 represents the number to accept connections on from clients. It should be an integer from
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
02 to
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
03, as
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
04 is reserved. Some systems may require superuser privileges if the port number is less than
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
05

Here’s a note on using hostnames with

$ python echo-server.py
9

“If you use a hostname in the host portion of IPv4/v6 socket address, the program may show a non-deterministic behavior, as Python uses the first address returned from the DNS resolution. The socket address will be resolved differently into an actual IPv4/v6 address, depending on the results from DNS resolution and/or the host configuration. For deterministic behavior use a numeric address in host portion. " (Nguồn)

Bạn sẽ tìm hiểu thêm về điều này sau, trong. 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ỉ

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
07. Lần sau, bạn nhận được một địa chỉ khác,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
08. Lần thứ ba, bạn có thể nhận được
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
09, 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()
    # ...

Phương thức có tham số

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
12. 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ị
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
12 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ị

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
12 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
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
15

Thực thi phương thức và chờ 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. Tuple sẽ chứa

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
7 cho các kết nối IPv4 hoặc
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
18 cho IPv6. Xem 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)

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
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
21, một vòng lặp vô hạn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
22 được sử dụng để lặp tới
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
23. Đ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
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
24

Nếu

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
23 trả về một đối tượng trống, thì
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
27, 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-client.py 
Received b'Hello, world'
9 được sử dụng với
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
21 để tự động đóng ổ cắm ở 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-client.py

import socket

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

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

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

So với máy chủ, máy khách khá đơn giản. Nó tạo một đối tượng socket, sử dụng để kết nối với máy chủ và gọi

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
31 để gửi tin nhắn của nó. Cuối cùng, nó gọi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
32 để đọc câu trả lời của máy chủ và sau đó in nó ra

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à bởi vì máy chủ đang , hoặc bị treo, vào ngà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)
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-client.py 
Received b'Hello, world'

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 
Connected by ('127.0.0.1', 64623)

In the output above, the server printed the

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
34 tuple returned from
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
35. Đây là địa chỉ IP và số cổng TCP của máy khách. Số cổng,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
36, 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

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37. 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ủ

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

Lưu ý rằng

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

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

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
38 là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
44, 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. In this example,
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
6 was used (IPv4) in the call to
$ python echo-server.py
8. You can see this in the
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
47 column.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
48

Đầu ra ở trên được cắt bớt để chỉ hiển thị máy chủ tiếng vang. You’ll likely see much more output, depending on the system you’re running it on. Những điều cần chú ý là các cột

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
47,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
38 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
51. 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 (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
48), trên cổng 65432 trên tất cả các giao diện (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
44) và nó đang ở trạng thái lắng nghe (
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
54)

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

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
55 (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ó

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

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
55 cung cấp cho bạn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
57,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
58 (ID quy trình) và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
59 (ID người dùng) của ổ cắm Internet đang mở khi được sử dụng với tùy chọn
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
60. Above is the echo server process

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 and
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
55 have a lot of options available and differ depending on the OS that you’re running them on. Check the
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
63 page or documentation for both. They’re definitely worth spending a little time with and getting to know. You’ll be rewarded. On macOS and Linux, use
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
64 and
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
65. For Windows, use
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
66

Here’s a common error that you’ll encounter when a connection attempt is made to a port with no listening socket

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

Either the specified port number is wrong or the server isn’t running. Or maybe there’s a firewall in the path that’s blocking the connection, which can be easy to forget about. You may also see the error

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
67. Get a firewall rule added that allows the client to connect to the TCP port

There’s a list of common in the reference section

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

Communication Breakdown

Now you’ll take a closer look at how the client and server communicated with each other

Liên kết ổ cắm Python

When using the loopback interface (IPv4 address

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or IPv6 address
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69), data never leaves the host or touches the external network. In the diagram above, the loopback interface is contained inside the host. This represents the internal nature of the loopback interface and shows that connections and data that transit it are local to the host. This is why you’ll also hear the loopback interface and IP address
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69 referred to as “localhost. ”

Applications use the loopback interface to communicate with other processes running on the host and for security and isolation from the external network. Because it’s internal and accessible only from within the host, it’s not exposed

You can see this in action if you have an application server that uses its own private database. If it’s not a database used by other servers, it’s probably configured to listen for connections on the loopback interface only. If this is the case, other hosts on the network can’t connect to it

When you use an IP address other than

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69 in your applications, it’s probably bound to an Ethernet interface that’s connected to an external network. This is your gateway to other hosts outside of your “localhost” kingdom

Liên kết ổ cắm Python

Be careful out there. It’s a nasty, cruel world. Be sure to read the section before venturing from the safe confines of “localhost. ” There’s a security note that applies even if you’re not using hostnames but are using IP addresses only

Handling Multiple Connections

The echo server definitely has its limitations. The biggest one is that it serves only one client and then exits. The echo client has this limitation too, but there’s an additional problem. When the client uses

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
32, it’s possible that it will return only one byte,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
75 from
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
76

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

The

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
77 argument of
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
05 used above is the maximum amount of data to be received at once. It doesn’t mean that
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 will return
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
05 bytes

The

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 method also behaves this way. It returns the number of bytes sent, which may be less than the size of the data passed in. You’re responsible for checking this and calling
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 as many times as needed to send all of the data

“Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. ”

In the example above, you avoided having to do this by using

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

“Unlike send(), this method continues to send data from bytes until either all data has been sent or an error occurs.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
84 is returned on success. ”

You have two problems at this point

  • How do you handle multiple connections concurrently?
  • You need to call
    # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        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 and
    # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        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 until all data is sent or received

What can you do? There are many approaches to concurrency. A popular approach is to use Asynchronous I/O.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 was introduced into the standard library in Python 3. 4. The traditional choice is to use threads

The trouble with concurrency is it’s hard to get right. There are many subtleties to consider and guard against. All it takes is for one of these to manifest itself and your application may suddenly fail in not-so-subtle ways

This isn’t meant to scare you away from learning and using concurrent programming. If your application needs to scale, it’s a necessity if you want to use more than one processor or one core. However, for this tutorial, you’ll use something that’s even more traditional than threads and easier to reason about. You’re going to use the granddaddy of system calls.

The

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 method allows you to check for I/O completion on more than one socket. So you can call
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 to see which sockets have I/O ready for reading and/or writing. But this is Python, so there’s more. You’re going to use the selectors module in the standard library so that the most efficient implementation is used, regardless of the operating system you happen to be running on

“This module allows high-level and efficient I/O multiplexing, built upon the select module primitives. Users are encouraged to use this module instead, unless they want precise control over the OS-level primitives used. ” (Source)

Still, by using

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88, you’re not able to run concurrently. That said, depending on your workload, this approach may still be plenty fast. It depends on what your application needs to do when it services a request, and the number of clients it needs to support

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87 uses single-threaded cooperative multitasking and an event loop to manage tasks. With
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88, you’ll be writing your own version of an event loop, albeit more simply and synchronously. When using multiple threads, even though you have concurrency, you currently have to use the GIL (Global Interpreter Lock) with CPython and PyPy. This effectively limits the amount of work you can do in parallel anyway

This is all to say that using

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 may be a perfectly fine choice. Don’t feel like you have to use
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
87, threads, or the latest asynchronous library. Typically, in a network application, your application is I/O bound anyway. it could be waiting on the local network, for endpoints on the other side of the network, for disk writes, and so forth

If you’re getting requests from clients that initiate CPU bound work, look at the concurrent. futures module. It contains the class , which uses a pool of processes to execute calls asynchronously

If you use multiple processes, the operating system is able to schedule your Python code to run in parallel on multiple processors or cores, without the GIL. For ideas and inspiration, see the PyCon talk John Reese - Thinking Outside the GIL with AsyncIO and Multiprocessing - PyCon 2018

In the next section, you’ll look at examples of a server and client that address these problems. They use

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 to handle multiple connections simultaneously and call
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 and
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 as many times as needed

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

Multi-Connection Client and Server

In the next two sections, you’ll create a server and client that handles multiple connections using a

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
99 object created from the selectors module

Multi-Connection Server

First, turn your attention to the multi-connection server. The first part sets up the listening socket

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

The biggest difference between this server and the echo server is the call to

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
00 to configure the socket in non-blocking mode. Calls made to this socket will no longer . When it’s used with
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01, as you’ll see below, you can wait for events on one or more sockets and then read and write data when it’s ready

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
02 registers the socket to be monitored with
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01 for the events that you’re interested in. For the listening socket, you want read events.
# echo-server.py

# ...

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

To store whatever arbitrary data you’d like along with the socket, you’ll use

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
05. It’s returned when
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 returns. You’ll use
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
05 to keep track of what’s been sent and received on the socket

Next is the event loop

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

until there are sockets ready for I/O. It returns a list of tuples, one for each socket. Each tuple contains a

# echo-server.py

# ...

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

# ...

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

# ...

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

# ...

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

# ...

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

# ...

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

# ...

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

If

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
16 is
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
84, then you know it’s from the listening socket and you need to accept the connection. You’ll call your own
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
18 function to get the new socket object and register it with the selector. You’ll look at that in a moment

Nếu

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
16 không phải là
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
84, 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ó.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
21 is then called with
# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
10 as arguments, and that’s everything you need to operate on the socket

Here’s what your

# echo-server.py

# ...

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

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

Because the listening socket was registered for the event

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
04, it should be ready to read. You call
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
26 and then call
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
27 to put the socket in non-blocking mode

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ó. If it blocks, then the entire server is stalled until it returns. Đ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. 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 với toán tử

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
29 được chuyển đến
# echo-server.py

# ...

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

Bây giờ hãy xem

# echo-server.py

# ...

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

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

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

# echo-server.py

# ...

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

# ...

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

# ...

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

# ...

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

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

# echo-server.py

# ...

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

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
39 được gọi. Bất kỳ dữ liệu nào được đọc đều được thêm vào _______ 30 _______ 40 để có thể gửi sau

Lưu ý khối

# echo-server.py

# ...

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

# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
42 trước khi đóng cửa, để nó không còn bị giám sát bởi
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
40 sẽ được gửi lại cho máy khách bằng cách sử dụng
# echo-server.py

# ...

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

# echo-server.py

# ...

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

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. This number can then be used with on the
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
47 buffer to discard the bytes sent

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,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
48. It’s very similar to the server, but instead of listening for connections, it starts by initiating connections via
# echo-server.py

# ...

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

# echo-server.py

# ...

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
50 đượ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 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ệ
# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 method initially returns an error indicator,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
56, instead of raising an exception that would interfere with the connection in progress. 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
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
28. 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
# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
60 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
# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
21 của máy chủ đối với phiên bản của máy khách

# echo-server.py

# ...

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

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

# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
48. 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 
Connected by ('127.0.0.1', 64623)
8 và
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01

# echo-server.py

# ...

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

Đố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ủ,

# echo-server.py

# ...

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

# echo-server.py

# ...

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

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

# echo-server.py

# ...

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

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

# echo-server.py

# ...

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

Tuyệt. 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ụ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
68 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à

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88, 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
70 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
70. 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. Hosts and routers are rebooted, switch ports go bad, cables go bad, cables get unplugged, you name it. 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 một tập tin, không có

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ô đó

This is why you need to define an application-layer protocol. What’s an application-layer protocol? Put simply, your application will send and receive messages. The format of these messages are your application’s protocol

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? . If they’re always the same size, then it’s easy. When you’ve read that number of bytes into a buffer, then you know you have one complete message

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. By doing this, you’ll only need to keep up with the header. 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 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 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

# echo-server.py

# ...

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

# echo-server.py

# ...

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

If you run this in a virtual machine that emulates a big-endian CPU (PowerPC), then something like this happens

# echo-server.py

# ...

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

In this example application, your application-layer protocol defines the header as Unicode text with a UTF-8 encoding. Đố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ả

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
78 Thứ tự byte của máy (sử dụng
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
77). Điều này có thể không cần thiết cho ứng dụng của bạn.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80Độ dài của nội dung tính bằng byte.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
81Loại nội dung trong tải trọng, ví dụ:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
82 hoặc
# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
84Mã hóa mà nội dung sử dụng, ví dụ:
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
85 cho văn bản Unicode hoặc
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
86 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ông báo, 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ư

Liên kết ổ cắm Python

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80, là số byte 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
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
80 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 và xem nó được sử dụng như thế nào với
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 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. If the request sent by the client isn’t recognized as a search, the server assumes it’s a binary request and returns a binary response

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 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ủ

# echo-server.py

# ...

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

# ...

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

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 và thêm các phương thức để hỗ trợ việc đọc, viết và xử lý các tiêu đề và nội dung. This is a great example for using a class

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, 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()
    # ...
00. 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 của máy chủ hoạt động về cơ bản 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 / Message Content1ClientSends a

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 chứa nội dung yêu cầu2ServerNhận và xử lý yêu cầu của client
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
933ServerSends một
# echo-server.py

# ...

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

# ...

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

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

ApplicationFileCodeServer

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
98The server’s main scriptServer
# echo-server.py

# ...

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

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
97The client’s main scriptClient
# echo-server.py

# ...

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

# ...

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

Điểm nhập tin nhắn

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 đượ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()
    # ...
14

# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93, 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()
    # ...
16. 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
# echo-server.py

# ...

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

# ...

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

# ...

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

# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
01 đ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()
    # ...
21. Đó 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()
    # ...
21 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()
    # ...
21 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()
    # ...
2

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()
    # ...
21 thật đơn giản. It can only do two things. 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()
    # ...
25 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()
    # ...
26

Đây là nơi quản lý nhà nước đến. If another method depended on state variables having a certain value, then they would only be called from

# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
26. Đ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()
    # ...
25 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()
    # ...
26. 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à lệnh 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()
    # ...
25 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()
    # ...
26

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()
    # ...
25. Đâ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()
    # ...
34 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()
    # ...
35

# echo-server.py

# ...

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

Phương 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()
    # ...
36 đượ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()
    # ...
37 để đọ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()
    # ...
37 đượ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()
    # ...
37 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à các cuộc gọi phương thức

# echo-server.py

# ...

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

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()
    # ...
41
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
42JSON 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()
    # ...
43
# echo-server.py

# ...

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

# ...

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

# ...

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

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()
    # ...
26. Đâ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()
    # ...
4

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()
    # ...
26 kiểm tra trước 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()
    # ...
49. 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()
    # ...
50 đượ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()
    # ...
50 đặ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()
    # ...
52 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()
    # ...
53 gọi
# echo-server.py

# ...

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

Hãy nhớ rằng khi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
60 đượ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 gọi lại
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
60. Đâ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()
    # ...
50 chỉ nên được gọi một lần, nhưng dự kiến ​​rằng phương thức
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
53 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()
    # ...
26 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()
    # ...
5

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()
    # ...
60 đượ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()
    # ...
61. 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()
    # ...
62 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()
    # ...
60 để 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()
    # ...
53 gọi
# echo-server.py

# ...

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

Sự khác biệt đáng chú ý trong phiên bản của khách hàng 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()
    # ...
26 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 này, nhưng lý do của việc này là để 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()
    # ...
16 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()
    # ...
16 đang gọi vào lớp
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 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()
    # ...
21 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()
    # ...
21 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ủ

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
98, 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()
    # ...
6

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()
    # ...
73, 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()
    # ...
7

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()
    # ...
74 để 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()
    # ...
75 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()
    # ...
76

# echo-server.py

# ...

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

Đặ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()
    # ...
77. 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()
    # ...
78 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()
    # ...
78 hết hạn, thì bạn sẽ nhận được một ngoại lệ
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
70 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()
    # ...
77. Đâ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

The event loop catches any errors so that the server can stay up and continue to run

# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 đượ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)
0

Đối tượng

# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
02 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
02 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()
    # ...
86, 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 này, bạn đã học cách đối tượng

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 đượ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()
    # ...
21. 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()
    # ...
07, đâ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)
1

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()
    # ...
42. 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()
    # ...
92 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)
2

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()
    # ...
93 đượ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
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
85 đượ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()
    # ...
44. 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()
    # ...
43 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ằng 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()
    # ...
44. Khi
# echo-server.py

# ...

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

# echo-server.py

# ...

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

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

# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
35 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()
    # ...
35 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()
    # ...
35 thực hiện là sửa đổi bộ chọn để chỉ giám sát các sự kiện ghi. Trong tập lệnh chính của máy chủ,
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
98, ổ 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()
    # ...
50 đượ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()
    # ...
26

# echo-server.py

# ...

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

Mộ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)
06. Đố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)
07 đượ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()
    # ...
26 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()
    # ...
50. 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()
    # ...
53

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 đế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)
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()
    # ...
53

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 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,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
97, 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)
6

Đâ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)
7

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)
15

# echo-server.py

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93 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()
    # ...
49

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

# echo-server.py

# ...

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

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
02. 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 này, bạn đã biết 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()
    # ...
21. 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()
    # ...
10, đâ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)
9

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,

# echo-server.py

# ...

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

# ...

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    # ...
53. 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)
25 đượ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()
    # ...
61 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()
    # ...
40 đượ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()
    # ...
92 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)
29

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()
    # ...
40 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 ra 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()
    # ...
34,
# echo-server.py

# ...

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

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

# echo-server.py

# ...

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

# echo-client.py

import socket

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

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

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
93, 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)
36 bên trong vòng lặp sự kiện

# echo-client.py

import socket

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

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

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

Lưu ý dòng.

# echo-server.py

# ...

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

Đâ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 lại 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)
37 còn loại bỏ ổ cắm khỏi sự giám sát của
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88. Đ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)
41 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)
42 cũng chứa một số điều thú vị

# echo-client.py

import socket

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

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

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

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)
43

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()
    # ...
53 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)
45. Lỗi tạm thời là khi ổ cắm sẽ , 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)
45,
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
88 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 
Connected by ('127.0.0.1', 64623)
8. Đ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-client.py

import socket

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

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

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

Bâ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-client.py

import socket

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

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

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

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-client.py

import socket

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

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

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

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)
49. 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)
50. 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-client.py

import socket

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

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

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

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)
49 để 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)
52 không phải là đố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)
53

# echo-client.py

import socket

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

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

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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
81 của yêu cầu không phải là
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
82, 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
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
81 và trả về mười byte đầu tiên cho máy khách

# echo-client.py

import socket

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

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

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

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 bên dưới để biết ý tưởng. Đặc biệt, kiểm tra phần

Đô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)
57 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-client.py

import socket

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

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

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

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)
57, 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 để 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 này, bạn đã học cách sử dụng

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 để 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)
60 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)
61 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 để truyền hoặc nhận, nhưng vì lý do nào đó chưa được ứng dụng từ xa hoặc cục bộ đọc hoặc ghi

Nói cách khác, các byte đang chờ 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 bởi 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()
    # ...
37 hoặc
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
60 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à liên tục gọi

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
60. 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()
    # ...
37. 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

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 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ủ (______26_______38 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)
60.
# echo-server.py

# ...

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

Mục thứ hai là máy khách (______55_______70 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)
61.
# echo-server.py

# ...

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

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ố họ 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)
73. TCPView là một
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
37 đồ 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
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
55, 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

Liên kết ổ cắm Python

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)
76 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)
77

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

Liên kết ổ cắm Python

Đâ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)
77

$ 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

Thẩm quyền giải quyết

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
  • con trăn

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)
79 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

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    # ...
70 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

Exception

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 ConstantDescriptionBlockingIOErrorEWOULDBLOCKResource temporarily unavailable. 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)
83.
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
84. 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. ConnectionRefusedErrorECONNREFUSEDConnection refused. 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 
Connected by ('127.0.0.1', 64623)
6 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)
86 đạ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 muốn địa chỉ đó ở 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 
Connected by ('127.0.0.1', 64623)
6 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)
86

Địa chỉ FamilyProtocolAddress TupleDescription

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
6IPv4
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
7
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 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)
93 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)
94.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01 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)
86IPv6
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
18
$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 is a string with a hostname like
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 or an IPv6 address like
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
00.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01 is an integer.
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
02 and
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
03 represent the
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
04 and
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
05 members in the C struct
# echo-client.py

import socket

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

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

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

Note the excerpt below from Python’s socket module documentation regarding the

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 value of the address tuple

“For IPv4 addresses, two special forms are accepted instead of a host address. the empty string represents

# echo-client.py

import socket

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

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

print(f"Received {data!r}")
08, and the string
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
09 represents
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
10. This behavior is not compatible with IPv6, therefore, you may want to avoid these if you intend to support IPv6 with your Python programs. ” (Source)

See Python’s for more information

This tutorial uses IPv4 sockets, but if your network supports it, try testing and using IPv6 if possible. One way to support this easily is by using the function . It translates the

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)
8 and
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
01 arguments into a sequence of five-tuples that contains all of the necessary arguments for creating a socket connected to that service.
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
13 will understand and interpret passed-in IPv6 addresses and hostnames that resolve to IPv6 addresses, in addition to IPv4

The following example returns address information for a TCP connection to

# echo-client.py

import socket

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

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

print(f"Received {data!r}")
14 on port
# echo-client.py

import socket

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

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

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

>>>

$ python echo-server.py
6

Results may differ on your system if IPv6 isn’t enabled. The values returned above can be used by passing them to

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 and
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
17. There’s a client and server example in the of Python’s socket module documentation

Using Hostnames

For context, this section applies mostly to using hostnames with

$ python echo-server.py
9 and
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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, or
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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, when you intend to use the loopback interface, “localhost. ” However, it also applies any time you’re using a hostname and there’s an expectation of it resolving to a certain address and having a special meaning to your application that affects its behavior or assumptions. This is in contrast to the typical scenario of a client using a hostname to connect to a server that’s resolved by DNS, like www. example. 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)
79 của Python

“If you use a hostname in the host portion of IPv4/v6 socket address, the program may show a non-deterministic behavior, as Python uses the first address returned from the DNS resolution. The socket address will be resolved differently into an actual IPv4/v6 address, depending on the results from DNS resolution and/or the host configuration. For deterministic behavior use a numeric address in host portion. " (Nguồn)

The standard convention for the name “localhost” is for it to resolve to

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69, the loopback interface. This will more than likely be the case for you on your system, but maybe not. It depends on how your system is configured for name resolution. As with all things IT, there are always exceptions, and there are no guarantees that using the name “localhost” will connect to the loopback interface

For example, on Linux, see

# echo-client.py

import socket

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

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

print(f"Received {data!r}")
24, the Name Service Switch configuration file. Một nơi khác để kiểm tra macOS và Linux là tệp
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
25. On Windows, see
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
26. The
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
27 file contains a static table of name-to-address mappings in a simple text format. DNS is another piece of the puzzle altogether

Interestingly enough, as of June 2018, there’s an RFC draft Let ‘localhost’ be localhost that discusses the conventions, assumptions, and security around using the name “localhost. ”

What’s important to understand is that when you use hostnames in your application, the returned addresses could literally be anything. Don’t make assumptions regarding a name if you have a security-sensitive application. Depending on your application and environment, this may or may not be a concern for you

Note. Security precautions and best practices still apply, even if your application isn’t explicitly security-sensitive. If your application accesses the network, it should be secured and maintained. This means, at a minimum

  • System software updates and security patches are applied regularly, including Python. Are you using any third-party libraries? If so, make sure those are checked and updated too

  • If possible, use a dedicated or host-based firewall to restrict connections to trusted systems only

  • What DNS servers are configured? Do you trust them and their administrators?

  • Make sure that request data is sanitized and validated as much as possible prior to calling other code that processes it. Use fuzz tests for this and run them regularly

Regardless of whether or not you’re using hostnames, if your application needs to support secure connections through encryption and authentication, then you’ll probably want to look into using TLS. This is its own separate topic and beyond the scope of this tutorial. See Python’s ssl module documentation to get started. This is the same protocol that your web browser uses to connect securely to web sites

With interfaces, IP addresses, and name resolution to consider, there are many variables. What should you do? Here are some recommendations that you can use if you don’t have a network application review process

ApplicationUsageRecommendationServerloopback interfaceUse an IP address, such as

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69. Serverethernet interfaceUse an IP address, such as
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
07. To support more than one interface, use an empty string for all interfaces/addresses. See the security note above. Clientloopback interfaceUse an IP address, such as
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
00 or
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
69. Clientethernet interfaceUse an IP address for consistency and non-reliance on name resolution. For the typical case, use a hostname. See the security note above

For clients or servers, if you need to authenticate the host that you’re connecting to, look into using TLS

Blocking Calls

A socket function or method that temporarily suspends your application is a blocking call. For example,

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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, and
# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 block, meaning they don’t return immediately. Blocking calls have to wait on system calls (I/O) to complete before they can return a value. So you, the caller, are blocked until they’re done or a timeout or other error occurs

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. If you do this, then you’ll need to at least refactor or redesign your application to handle the socket operation when it’s ready

Because the call returns immediately, data may not be ready. The callee is waiting on the network and hasn’t had time to complete its work. If this is the case, then the current status is the

# echo-server.py

# ...

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    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 value
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
38. Non-blocking mode is supported with

By default, sockets are always created in blocking mode. See for a description of the three modes

Đó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. In general, it’s not. Ở 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

When designing and writing your application and its application-layer protocol, it’s a good idea to go ahead and work out how you expect connections to be closed. Đô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

Byte Endianness

See Wikipedia’s article on endianness for details on how different CPUs store byte orderings in memory. Khi diễn giải các byte riêng lẻ, đây không phải là vấn đề. However, when you’re handling multiple bytes that are read and processed as a single value, for example a 4-byte integer, the byte order needs to be reversed if you’re communicating with a machine that uses a different endianness

Byte order is also important for text strings that are represented as multi-byte sequences, like Unicode. Unless you’re always using true, strict ASCII and control the client and server implementations, you’re probably better off using Unicode with an encoding like UTF-8 or one that supports a byte order mark (BOM)

It’s important to explicitly define the encoding used in your application-layer protocol. You can do this by mandating that all text is UTF-8 or using a “content-encoding” header that specifies the encoding. This prevents your application from having to detect the encoding, which you should avoid if possible

This becomes problematic when there is data involved that’s stored in files or a database and there’s no metadata available that specifies its encoding. When the data is transferred to another endpoint, it’ll have to try to detect the encoding. For a discussion, see Wikipedia’s Unicode article, which references

“However RFC 3629, the UTF-8 standard, recommends that byte order marks be forbidden in protocols using UTF-8, but discusses the cases where this may not be possible. In addition, the large restriction on possible patterns in UTF-8 (for instance there cannot be any lone bytes with the high bit set) means that it should be possible to distinguish UTF-8 from other character encodings without relying on the BOM. ” (Source)

The takeaway from this is to always store the encoding used for data that’s handled by your application if it can vary. In other words, try to somehow store the encoding as metadata if it’s not always UTF-8 or some other encoding with a BOM. Then you can send that encoding in a header along with the data to tell the receiver what it is

The byte ordering used in TCP/IP is and is referred to as network order. Network order is used to represent integers in lower layers of the protocol stack, like IP addresses and port numbers. Python’s socket module includes functions that convert integers to and from network and host byte order

FunctionDescription

# echo-client.py

import socket

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

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

print(f"Received {data!r}")
39Convert 32-bit positive integers from network to host byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 4-byte swap operation.
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
40Convert 16-bit positive integers from network to host byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 2-byte swap operation.
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
41Convert 32-bit positive integers from host to network byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 4-byte swap operation.
# echo-client.py

import socket

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

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

print(f"Received {data!r}")
42Convert 16-bit positive integers from host to network byte order. On machines where the host byte order is the same as network byte order, this is a no-op; otherwise, it performs a 2-byte swap operation

You can also use the struct module to pack and unpack binary data using format strings

$ python echo-server.py
7

Conclusion

You covered a lot of ground in this tutorial. Networking and sockets are large subjects. If you’re new to networking or sockets, don’t be discouraged by all of the terms and acronyms

There are a lot of pieces to become familiar with in order to understand how everything works together. However, just like Python, it will start to make more sense as you get to know the individual pieces and spend more time with them

In this tutorial, you

  • Looked at the low-level socket API in Python’s
    # echo-server.py
    
    # ...
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)
    
    79 module and saw how it can be used to create client-server applications
  • Built a client and server that can handle multiple connections using a
    # echo-client.py
    
    import socket
    
    HOST = "127.0.0.1"  # The server's hostname or IP address
    PORT = 65432  # The port used by the server
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(b"Hello, world")
        data = s.recv(1024)
    
    print(f"Received {data!r}")
    
    44 object
  • Created your own custom class and used it as an application-layer protocol to exchange messages and data between endpoints

From here, you can use your custom class and build upon it to learn and help make creating your own socket applications easier and faster

To review the examples, you can click the link below

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

Congratulations on making it to the end. You are now well on your way to using sockets in your own applications. Best of luck on your sockets development journey

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

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team

Liên kết ổ cắm Python

Send Me Python Tricks »

About Nathan Jennings

Liên kết ổ cắm Python
Liên kết ổ cắm Python

Nathan is a member of the Real Python tutorial team who started his programmer career with C a long time ago, but eventually found Python. From web applications and data collection to networking and network security, he enjoys all things Pythonic

» More about Nathan


Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are

Liên kết ổ cắm Python

Aldren

Liên kết ổ cắm Python

Brad

Liên kết ổ cắm Python

Geir Arne

Liên kết ổ cắm Python

Ian

Liên kết ổ cắm Python

Jim

Liên kết ổ cắm Python

Joanna

Liên kết ổ cắm Python

Kate

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas

Level Up Your Python Skills »

Bạn nghĩ sao?

Rate this article

Tweet Share Share Email

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know

Commenting Tips. 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. and get answers to common questions in our support portal

What is socket bind in Python?

A server has a bind() method which binds it to a specific IP and port so that it can listen to incoming requests on that IP and port . A server has a listen() method which puts the server into listen mode. This allows the server to listen to incoming connections.

What is bind () socket?

Hàm bind() liên kết một tên cục bộ duy nhất với ổ cắm bằng ổ cắm mô tả . Sau khi gọi socket(), một bộ mô tả không có tên liên kết với nó. Tuy nhiên, nó thuộc về một họ địa chỉ cụ thể như được chỉ định khi socket() được gọi. Định dạng chính xác của tên phụ thuộc vào họ địa chỉ.

What is the difference between bind and connect in Python socket?

bind() associates the socket with its local address [that's why server side binds, so that clients can use that address to connect to server. ] connect() is used to connect to a remote [server] address, that's why is client side, connect [read as. connect to server] is used.

What is the difference between bind and listen Python?

. bind() is used to associate the socket with the server address. Calling listen() puts the socket into server mode, and accept() waits for an incoming connection . listen() is what differentiates a server socket from a client.