SocketServer trăn

Ổ cắm và API ổ cắm được sử dụng để gửi tin nhắn qua mạng. Họ cung cấp một hình thức giao tiếp giữa các quá trình [IPC]. 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. The obvious example is the Internet, which you connect to via your ISP

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

  • A simple socket server and client
  • An improved version that handles multiple connections simultaneously
  • A server-client application that functions like a full-fledged socket application, complete with its own custom header and content

By the end of this tutorial, you’ll understand how to use the main functions and methods in Python’s socket module to write your own client-server applications. You’ll know how to use a custom class to send messages and data between endpoints, which you can build upon and utilize for your own applications

The examples in this tutorial require Python 3. 6 or above, and have been tested using Python 3. 10. To get the most out of this tutorial, it’s best to download the source code and have it on hand for reference while reading

Get Source Code. Click here to get the source code you’ll use for the examples in this tutorial

Networking and sockets are large subjects. Literal volumes have been written about them. If you’re new to sockets or networking, it’s completely normal if you feel overwhelmed with all of the terms and pieces

Don’t be discouraged though. This tutorial is for you. As with anything Python-related, you can learn a little bit at a time. Bookmark this article and come back when you’re ready for the next section

Background

Sockets have a long history. Their use in 1971 and later became an API in the Berkeley Software Distribution [BSD] operating system released in 1983 called Berkeley sockets

When the Internet took off in the 1990s with the World Wide Web, so did network programming. Web servers and browsers weren’t the only applications taking advantage of newly connected networks and using sockets. Client-server applications of all types and sizes came into widespread use

Today, although the underlying protocols used by the socket API have evolved over the years, and new ones have developed, the low-level API has remained the same

The most common type of socket applications are client-server applications, where one side acts as the server and waits for connections from clients. This is the type of application that you’ll be creating in this tutorial. More specifically, you’ll focus on the socket API for Internet sockets, sometimes called Berkeley or BSD sockets. There are also Unix domain sockets, which can only be used to communicate between processes on the same host

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

Socket API Overview

Python’s socket module provides an interface to the Berkeley sockets API. This is the module that you’ll use in this tutorial

The primary socket API functions and methods in this module are

  • $ 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 provides a convenient and consistent API that maps directly to system calls, their C counterparts. In the next section, you’ll learn how these are used together

As part of its standard library, Python also has classes that make using these low-level socket functions easier. Although it’s not covered in this tutorial, you can check out the socketserver module, a framework for network servers. There are also many modules available that implement higher-level Internet protocols like HTTP and SMTP. For an overview, see Internet Protocols and Support

TCP Sockets

You’re going to create a socket object using

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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, specifying the socket type as
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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. When you do that, the default protocol that’s used is the Transmission Control Protocol [TCP]. Đây là một mặc định tốt và có thể là những gì bạn muốn

Why should you use TCP? The Transmission Control Protocol [TCP]

  • Is reliable. Packets dropped in the network are detected and retransmitted by the sender
  • Has in-order data delivery. Data is read by your application in the order it was written by the sender

In contrast, User Datagram Protocol [UDP] sockets created with

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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 aren’t reliable, and data read by the receiver can be out-of-order from the sender’s writes

Why is this important? Networks are a best-effort delivery system. There’s no guarantee that your data will reach its destination or that you’ll receive what’s been sent to you

Network devices, such as routers and switches, have finite bandwidth available and come with their own inherent system limitations. They have CPUs, memory, buses, and interface packet buffers, just like your clients and servers. TCP relieves you from having to worry about packet loss, out-of-order data arrival, and other pitfalls that invariably happen when you’re communicating across a network

To better understand this, check out the sequence of socket API calls and data flow for TCP

TCP Socket Flow [Image source]

The left-hand column represents the server. On the right-hand side is the client

Starting in the top left-hand column, note the API calls that the server makes to set up a “listening” socket

  • $ 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 sử dụng lệnh 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]

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

Ở 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 một. 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[].

The arguments passed to are used to specify the and socket type.

$ 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. ” [Source]

Bạn sẽ tìm hiểu thêm về điều này sau, trong. For now, just understand that when using a hostname, you could see different results depending on what’s returned from the name resolution process. These results could be anything. The first time you run your application, you might get the address

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
07. The next time, you get a different address,
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
08. The third time, you could get
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
09, and so on

In the server 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]
0 enables a server to accept connections. It makes the server a “listening” socket

# echo-server.py

# ...

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

The method has a

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
12 parameter. It specifies the number of unaccepted connections that the system will allow before refusing new connections. Starting in Python 3. 5, it’s optional. If not specified, a default
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
12 value is chosen

If your server receives a lot of connection requests simultaneously, increasing the

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
12 value may help by setting the maximum length of the queue for pending connections. The maximum value is system dependent. For example, on Linux, see
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
15

The method execution and waits for an incoming connection. When a client connects, it returns a new socket object representing the connection and a tuple holding the address of the client. The tuple will contain

$ python echo-server.py 
Connected by ['127.0.0.1', 64623]
7 for IPv4 connections or
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
18 for IPv6. See in the reference section for details on the tuple values

One thing that’s imperative to understand is that you now have a new socket object 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[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
1. This is important because it’s the socket that you’ll use to communicate with the client. It’s distinct from the listening socket that the server is using to accept new connections

# echo-server.py

# ...

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

After

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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 provides the client socket object
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
21, an infinite
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
22 loop is used to loop over to
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
23. This reads whatever data the client sends and echoes it back using
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
24

If

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
23 returns an empty object,
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
27, that signals that the client closed the connection and the loop is terminated. The
$ python echo-client.py 
Received b'Hello, world'
9 statement is used with
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
21 to automatically close the socket at the end of the block

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

Echo Client

Now let’s look at the client

# 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}"]

In comparison to the server, the client is pretty simple. It creates a socket object, uses to connect to the server and calls

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
31 to send its message. Lastly, it calls
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
32 to read the server’s reply and then prints it

Running the Echo Client and Server

In this section, you’ll run the client and server to see how they behave and inspect what’s happening

Note. If you’re having trouble getting the examples or your own code to run from the command line, read How Do I Make My Own Command-Line Commands Using Python? or How to Run Your Python Scripts. If you’re on Windows, check the Python Windows FAQ

Open a terminal or command prompt, navigate to the directory that contains your scripts, ensure that you have Python 3. 6 or above installed and on your path, then run the server

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
34 được trả về từ
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. Trong ví dụ này,
$ python echo-server.py 
Connected by ['127.0.0.1', 64623]
6 đã được sử dụng [IPv4] trong cuộc gọi tới
$ python echo-server.py
8. Bạn có thể thấy điều này trong cột
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
47.
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. Bạn có thể sẽ thấy nhiều đầu ra hơn, tùy thuộc vào hệ thống mà bạn đang chạy nó. Những điều cần chú ý là các cột

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. Trên đây là quá trình echo server

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
37 và
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
55 có rất nhiều tùy chọn và khác nhau tùy thuộc vào hệ điều hành mà bạn đang chạy chúng. Kiểm tra trang
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
63 hoặc tài liệu cho cả hai. Họ chắc chắn đáng để dành một chút thời gian và tìm hiểu. Bạn sẽ được thưởng. Trên macOS và Linux, hãy sử dụng
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
64 và
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
65. Đối với Windows, 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[].
66

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

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
67. Thêm quy tắc tường lửa cho phép máy khách kết nối với cổng TCP

Có một danh sách phổ biến trong phần tham khảo

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

Communication Breakdown

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc địa chỉ IPv6
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69], dữ liệu không bao giờ rời khỏi máy chủ hoặc chạm vào mạng bên ngoài. Trong sơ đồ trên, giao diện loopback được chứa bên trong máy chủ. Điều này thể hiện bản chất bên trong của giao diện loopback và cho thấy rằng các kết nối và dữ liệu truyền qua nó là cục bộ của máy chủ. Đây là lý do tại sao bạn cũng sẽ nghe thấy giao diện loopback và địa chỉ IP
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69 được gọi là “localhost. ”

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

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69 trong các ứng dụng của mình, địa chỉ đó có thể bị ràng buộc với giao diện Ethernet được kết nối với mạng bên ngoài. Đây là cổng vào các máy chủ khác bên ngoài vương quốc “localhost” của bạn

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

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
32, có thể nó sẽ chỉ trả về một byte,
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
75 từ
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

Đối số

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
77 của
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
05 được sử dụng ở trên là lượng dữ liệu tối đa được nhận cùng một lúc. Điều đó không có nghĩa là
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5 sẽ trả về
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
05 byte

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

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
4 nhiều lần nếu cần để gửi tất cả dữ liệu

“Các ứng dụng có trách nhiệm kiểm tra xem tất cả dữ liệu đã được gửi chưa; . ”

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

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
84 được trả lại khi thành công. ”

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

  • Làm thế nào để bạn xử lý nhiều kết nối đồng thời?
  • Bạn cần gọi điện cho
    # echo-server.py
    
    # ...
    
    with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
        s.bind[[HOST, PORT]]
        s.listen[]
        conn, addr = s.accept[]
        with conn:
            print[f"Connected by {addr}"]
            while True:
                data = conn.recv[1024]
                if not data:
                    break
                conn.sendall[data]
    
    4 và
    # echo-server.py
    
    # ...
    
    with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
        s.bind[[HOST, PORT]]
        s.listen[]
        conn, addr = s.accept[]
        with conn:
            print[f"Connected by {addr}"]
            while True:
                data = conn.recv[1024]
                if not data:
                    break
                conn.sendall[data]
    
    5 cho đến khi tất cả dữ liệu được gửi hoặc nhận

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
87 đã được đưa vào thư viện chuẩn trong Python 3. 4. Sự lựa chọn truyền thống là sử dụng chủ đề

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

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

Phương pháp

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 cho phép bạn kiểm tra việc hoàn thành I/O trên nhiều ổ cắm. Vì vậy, bạn có thể gọi
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 để xem ổ cắm nào có sẵn I/O để đọc và/hoặc ghi. Nhưng đây là Python, vì vậy có nhiều hơn nữa. 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

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

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

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

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

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

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

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

Nếu bạn sử dụng nhiều quy trình, hệ điều hành có thể lên lịch mã Python của bạn để chạy song song trên nhiều bộ xử lý hoặc lõi mà không cần GIL. Để có ý tưởng và cảm hứng, hãy xem buổi nói chuyện về PyCon John Reese - Tư duy bên ngoài GIL với AsyncIO và Đa xử lý - PyCon 2018

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 để xử lý đồng thời nhiều kết nối và gọi
# echo-server.py

# ...

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

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5 nhiều lần nếu cần

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

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
99 được tạo từ mô-đun bộ chọn

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

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

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

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
00 để định cấu hình ổ cắm ở chế độ không chặn. Các cuộc gọi đến ổ cắm này sẽ không còn. Khi nó được sử dụng với
# echo-server.py

# ...

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
02 đăng ký ổ cắm sẽ được giám sát với
# echo-server.py

# ...

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

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
04

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
05. Nó được trả lại khi
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 trả về. Bạn sẽ sử dụng
# echo-server.py

# ...

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

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

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

cho đến khi có ổ cắm sẵn sàng cho I/O. Nó trả về một danh sách các bộ dữ liệu, một bộ cho mỗi ổ cắm. 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 is the socket object, and
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
10 is an event mask of the operations that are ready

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

If

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
16 is not
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 a client socket that’s already been accepted, and you need to service it.
# 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

Remember, this is the main objective in this version of the server because you don’t want it to . If it blocks, then the entire server is stalled until it returns. That means other sockets are left waiting even though the server isn’t actively working. This is the dreaded “hang” state that you don’t want your server to be in

Next, you create an object to hold the data that you want included along with the socket using a . Because you want to know when the client connection is ready for reading and writing, both of those events are set with the operator

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

The

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
29 mask, socket, and data objects are then passed to
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
02

Now take a look at

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
21 to see how a client connection is handled when it’s ready

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

This is the heart of the simple multi-connection server.

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
09 is the
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
12 returned from
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 that contains the socket object [
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
13] and data object.
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
10 contains the events that are ready

If the socket is ready for reading, then

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
37 will evaluate to
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
38, so
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
39 is called. Any data that’s read is appended to
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
40 so that it can be sent later

Note the

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
41 block to check if no data is received

# 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. Số này sau đó có thể được sử dụng trên bộ đệm
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
47 để loại bỏ các byte đã gửi

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

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

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
48. Nó rất giống với máy chủ, nhưng thay vì lắng nghe kết nối, nó bắt đầu bằng cách bắt đầu kết nối qua
# 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. 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]
3 ban đầu trả về một chỉ báo lỗi,
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
56, thay vì đưa ra một ngoại lệ có thể cản trở kết nối đang diễn ra. Sau khi kết nối hoàn tất, ổ cắm đã sẵn sàng để đọc và ghi và được trả về bởi
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. the server expects the client to close its side of the connection when it’s done sending messages. 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ủ, hãy chuyển 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 vời. 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, ổ cắm không chặn và

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
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. Máy chủ và bộ định tuyến được khởi động lại, cổng chuyển đổi bị hỏng, cáp bị hỏng, cáp bị rút phích cắm, bạn đặt tên cho nó. Bạn nên chuẩn bị cho những lỗi này và các lỗi khác, xử lý chúng trong mã của bạn

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

It’s up to you to define and keep track of where the message boundaries are. As far as the TCP socket is concerned, it’s just sending and receiving raw bytes to and from the network. It knows nothing about what those raw bytes mean

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

In other words, the length and format that you choose for these messages define the semantics and behavior of your application. This is directly related to what you learned in the previous paragraph regarding reading bytes from the socket. When you’re reading bytes with

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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, you need to keep up with how many bytes were read, and figure out where the message boundaries are

How can you do this? One way is to always send fixed-length messages. 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

However, using fixed-length messages is inefficient for small messages where you’d need to use padding to fill them out. Also, you’re still left with the problem of what to do about data that doesn’t fit into one message

In this tutorial, you’ll learn a generic approach, one that’s used by many protocols, including HTTP. You’ll prefix messages with a header that includes the content length as well as any other fields you need. By doing this, you’ll only need to keep up with the header. Once you’ve read the header, you can process it to determine the length of the message’s content. With the content length, you can then read that number of bytes to consume it

You’ll implement this by creating a custom class that can send and receive messages that contain text or binary data. You can improve and extend this class for your own applications. The most important thing is that you’ll be able to see an example of how this is done

Before you get started, there’s something you need to know regarding sockets and bytes. As you learned earlier, when sending and receiving data via sockets, you’re sending and receiving raw bytes

If you receive data and want to use it in a context where it’s interpreted as multiple bytes, for example a 4-byte integer, you’ll need to take into account that it could be in a format that’s not native to your machine’s CPU. The client or server on the other end could have a CPU that uses a different byte order than your own. If this is the case, then you’ll need to convert it to your host’s native byte order before using it

This byte order is referred to as a CPU’s endianness. See in the reference section for details. You’ll avoid this issue by taking advantage of Unicode for your message header and using the encoding UTF-8. Since UTF-8 uses an 8-bit encoding, there are no byte ordering issues

You can find an explanation in Python’s documentation. Note that this applies to the text header only. You’ll use an explicit type and encoding defined in the header for the content that’s being sent, the message payload. This will allow you to transfer any data that you’d like [text or binary], in any format

You can easily determine the byte order of your machine by using

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
77. For example, you could see something like this

# 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. For the actual content in the message, the message payload, you’ll still have to swap the byte order manually if needed

This will depend on your application and whether or not it needs to process multi-byte binary data from a machine with a different endianness. You can help your client or server implement binary support by adding additional headers and using them to pass parameters, similar to HTTP

Don’t worry if this doesn’t make sense yet. In the next section, you’ll see how all of this works and fits together

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

Application Protocol Header

Now you’ll fully define the protocol header. The protocol header is

  • Variable-length text
  • Unicode with the encoding UTF-8
  • A Python dictionary serialized using JSON

The required headers, or sub-headers, in the protocol header’s dictionary are as follows

NameDescription

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
78The byte order of the machine [uses
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
77]. This may not be required for your application.
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
80The length of the content in bytes.
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
81The type of content in the payload, for example,
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
82 or
# 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]]
    # ...
84The encoding used by the content, for example,
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
85 for Unicode text or
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
86 for binary data

These headers inform the receiver about the content in the payload of the message. This allows you to send arbitrary data while providing enough information so that the content can be decoded and interpreted correctly by the receiver. Because the headers are in a dictionary, it’s easy to add additional headers by inserting key-value pairs as needed

Sending an Application Message

There’s still a bit of a problem. You have a variable-length header, which is nice and flexible, but how do you know the length of the header when reading it with

# echo-server.py

# ...

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

When you previously learned about using

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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 and message boundaries, you also learned that fixed-length headers can be inefficient. That’s true, but you’re going to use a small, 2-byte, fixed-length header to prefix the JSON header that contains its length

You can think of this as a hybrid approach to sending messages. In effect, you’re bootstrapping the message receive process by sending the length of the header first. This makes it easy for your receiver to deconstruct the message

To give you a better idea of the message format, check out a message in its entirety

A message starts with a fixed-length header of two bytes, which is an integer in network byte order. This is the length of the next header, the variable-length JSON header. Once you’ve read two bytes with

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    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, then you know you can process the two bytes as an integer and then read that number of bytes before decoding the UTF-8 JSON header

The contains a dictionary of additional headers. One of those is

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
80, which is the number of bytes of the message’s content [not including the JSON header]. Once you’ve called
# echo-server.py

# ...

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

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
80 bytes, then you’ve reached a message boundary, meaning you’ve read an entire message

Application Message Class

Finally, the payoff. In this section, you’ll study the

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
93 class and see how it’s used with
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
88 when read and write events happen on the socket

This example application reflects what types of messages a client and server could reasonably use. You’re far beyond toy echo clients and servers at this point

To keep things simple and still demonstrate how things would work in a real application, this example uses an application protocol that implements a basic search feature. The client sends a search request and the server does a lookup for a match. 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

After reading the following sections, running the examples, and experimenting with the code, you’ll see how things work. You can then use the

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
93 class as a starting point and modify it for your own use

The application is not that far off from the

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
68 client and server example. The event loop code stays the same in
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
97 and
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
98. What you’re going to do is move the message code into a class named
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
93 and add methods to support reading, writing, and processing of the headers and content. This is a great example for using a class

As you learned before and you’ll see below, working with sockets involves keeping state. By using a class, you keep all of the state, data, and code bundled together in an organized unit. An instance of the class is created for each socket in the client and server when a connection is started or accepted

The class is mostly the same for both the client and the server for the wrapper and utility methods. They start with an underscore, 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[]
    # ...
00. These methods simplify working with the class. 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

The server’s

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
93 class works in essentially the same way as the client’s and vice-versa. 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ụ 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. Nó chỉ có thể làm hai việc. gọi
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    # ...
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. Nếu một phương thức khác phụ thuộc vào các biến trạng thái có một giá trị nhất định, thì chúng sẽ chỉ được gọi từ

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    # ...
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. Đ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à 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. 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 phải 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

The client version of

# 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 is similar

# 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

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    # ...
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

Get Source Code. Click here to get the source code you’ll use for the examples in this tutorial

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. Bởi vì tiêu đề JSON được định nghĩa 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. The result is saved 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[]
    # ...
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 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[]
    # ...
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 tới

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
6 theo phương thức
# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    # ...
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

Get Source Code. Click here to get the source code you’ll use for the examples in this tutorial

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à thêm vào bộ đệm gửi, sau đó được xem và gửi qua

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    # ...
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 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]
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 có 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 nắm 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

Bởi vì yêu cầu

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    # ...
81 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 mảnh 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ủ [______23_______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 [____52_______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

One of them is

# echo-server.py

# ...

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

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

Đâ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 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ỉ IPv6 như
# 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 là một số nguyê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}"]
02 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}"]
03 đại diện cho các thành viê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}"]
04 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}"]
05 trong cấu trúc C
# 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

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

$ python echo-server.py 
Connected by ['127.0.0.1', 64623]
8 của bộ địa chỉ

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

# echo-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 và chuỗi
# echo-client.py

import socket

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

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

print[f"Received {data!r}"]
09 đại diện cho
# 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. Hành vi này không tương thích với IPv6, do đó, bạn có thể muốn tránh những điều này nếu bạn có ý định hỗ trợ IPv6 với các chương trình Python của mình. " [Nguồn]

Xem Python để biết thêm thông tin

Hướng dẫn này sử dụng ổ cắm IPv4, nhưng nếu mạng của bạn hỗ trợ, hãy thử kiểm tra và sử dụng IPv6 nếu có thể. Một cách để hỗ trợ điều này một cách dễ dàng là sử dụng chức năng. Nó dịch các đối 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 thành một chuỗi gồm năm bộ chứa tất cả các đối số cần thiết để tạo một ổ cắm được kết nối với dịch vụ đó.
# echo-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 sẽ hiểu và diễn giải các địa chỉ IPv6 được chuyển vào và tên máy chủ phân giải thành địa chỉ IPv6, ngoài IPv4

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

# echo-client.py

import socket

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

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

print[f"Received {data!r}"]
14 trên cổng
# echo-client.py

import socket

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

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

print[f"Received {data!r}"]
15

>>>

$ python echo-server.py
6

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
7 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}"]
17. Có một ví dụ về máy khách và máy chủ trong tài liệu mô-đun ổ cắm của Python

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

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

$ python echo-server.py
9 và
# echo-server.py

# ...

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

# ...

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

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

# echo-server.py

# ...

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

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

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69, giao diện loopback. Điều này nhiều khả năng sẽ xảy ra với bạn trên hệ thống của mình, nhưng có thể không. Nó phụ thuộc vào cách hệ thống của bạn được cấu hình để phân giải tên. Như với tất cả mọi thứ về CNTT, luôn có những ngoại lệ và không có gì đảm bảo rằng việc sử dụng tên “localhost” sẽ kết nối với giao diện loopback

Ví dụ: trên Linux, xem

# echo-client.py

import socket

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

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

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

import socket

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

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

print[f"Received {data!r}"]
25. Trên Windows, xem
# echo-client.py

import socket

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

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

print[f"Received {data!r}"]
26. 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}"]
27 chứa một bảng tĩnh ánh xạ tên-địa chỉ ở định dạng văn bản đơn giản. DNS hoàn toàn là một phần khác của câu đố

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

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

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

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

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

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

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

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

Với các giao diện, địa chỉ IP và độ phân giải tên cần xem xét, có nhiều biến số. Những gì bạn nên làm?

Ứng dụngSử dụngĐề xuấtGiao diện vòng lặp máy chủSử dụng địa chỉ IP, chẳng hạn như

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69. Giao diện mạng máy chủSử dụng địa chỉ IP, chẳng hạn như
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
07. Để hỗ trợ nhiều giao diện, hãy sử dụng một chuỗi trống cho tất cả các giao diện/địa chỉ. Xem lưu ý bảo mật ở trên. Giao diện clientloopback Sử dụng địa chỉ IP, chẳng hạn như
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
00 hoặc
with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    pass  # Use the socket object without calling s.close[].
69. Giao diện clientethernetSử dụng địa chỉ IP để đảm bảo tính nhất quán và không phụ thuộc vào độ phân giải tên. Đối với trường hợp điển hình, hãy sử dụng tên máy chủ. Xem lưu ý bảo mật ở trên

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

chặn cuộc gọi

A socket function or method that temporarily suspends your application is a blocking call. Ví dụ: khối

# echo-server.py

# ...

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

# ...

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

# ...

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

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
5, nghĩa là chúng không quay lại ngay lập tức. Cuộc gọi chặn phải đợi cuộc gọi hệ thống [I/O] hoàn tất trước khi chúng có thể trả về giá trị. Vì vậy, bạn, người gọi, bị chặn cho đến khi họ thực hiện xong hoặc hết thời gian chờ hoặc xảy ra lỗi khác

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

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

# echo-server.py

# ...

with socket.socket[socket.AF_INET, socket.SOCK_STREAM] as s:
    s.bind[[HOST, PORT]]
    s.listen[]
    conn, addr = s.accept[]
    with conn:
        print[f"Connected by {addr}"]
        while True:
            data = conn.recv[1024]
            if not data:
                break
            conn.sendall[data]
81
# 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. Chế độ không chặn được hỗ trợ với

Theo mặc định, ổ cắm luôn được tạo ở chế độ chặn. Xem mô tả về ba chế độ

Đóng kết nối

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

Cách tiếp cận này không nhất thiết được khuyến nghị, nhưng ví dụ: HTTP sử dụng tiêu đề có tên "Kết nối" được sử dụng để chuẩn hóa cách các ứng dụng nên đóng hoặc duy trì các kết nối mở. Để biết chi tiết, xem

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

Độ bền byte

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

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

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

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

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

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

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

Chức năngMô tả

# echo-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}"]
39Chuyển đổi số nguyên dương 32 bit từ mạng sang thứ tự byte máy chủ. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh cấm; .
# echo-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}"]
40Chuyển đổi số nguyên dương 16 bit từ mạng sang thứ tự byte máy chủ. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh cấm; .
# echo-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}"]
41Chuyển đổi số nguyên dương 32 bit từ máy chủ sang thứ tự byte mạng. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh cấm; .
# echo-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}"]
42Chuyển đổi số nguyên dương 16 bit từ máy chủ sang thứ tự byte mạng. Trên các máy có thứ tự byte máy chủ giống với thứ tự byte mạng, đây là lệnh cấm;

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

$ python echo-server.py
7

Phần kết luận

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

Get Source Code. Click here to get the source code you’ll use for the examples in this tutorial

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

Mark as Completed

🐍 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

Send Me Python Tricks »

About Nathan Jennings

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

Aldren

Brad

Geir Arne

Ian

Jim

Joanna

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 »

What Do You Think?

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. The most useful comments are those written with the goal of learning from or helping out other students. and get answers to common questions in our support portal

What is SocketServer in Python?

The SocketServer module is a framework for creating network servers . It defines classes for handling synchronous network requests [the server request handler blocks until the request is completed] over TCP, UDP, Unix streams, and Unix datagrams.

What is a socket server?

Sockets are commonly used for client and server interaction . Typical system configuration places the server on one machine, with the clients on other machines. The clients connect to the server, exchange information, and then disconnect. A socket has a typical flow of events.

How to install socket server?

Installation​ .
ổ cắm cài đặt $ npm. io. sao chép
$ npm install socket.io@ Copy..
$ npm cài đặt --save-tùy chọn bộ đệmutil utf-8-xác thực. sao chép
$ npm cài đặt eiows. sao chép

Lớp SocketServer là gì?

Lớp ServerSocket được được sử dụng để cung cấp triển khai phía máy chủ độc lập với hệ thống của kết nối Socket máy khách/máy chủ . Hàm tạo cho ServerSocket đưa ra một ngoại lệ nếu nó không thể lắng nghe trên cổng đã chỉ định [ví dụ: cổng đã được sử dụng]. Nó được sử dụng rộng rãi nên các ứng dụng của java.

Chủ Đề