Skip to content

Instantly share code, notes, and snippets.

@aaachuan
Last active October 10, 2019 02:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaachuan/f667e706d2023d185dcd1b6e57be9cc4 to your computer and use it in GitHub Desktop.
Save aaachuan/f667e706d2023d185dcd1b6e57be9cc4 to your computer and use it in GitHub Desktop.
Python Socket编程

Socket With Python

Socket是众多IPC的一种,在其它特定的场景下,其它方式的IPC性能可能会更佳, 但是对于跨平台这点,Socket绝对是唯一的选择。

Socket最初出现在1971年的ARPANET,源自RFC 147,而如今的Socket实现,绝大多数 都来源于Berkeley sockets(1983)。Socket最常见的就是C/S应用上,当然,也有用于同一 主机间进程通信的——Unix domain sockets

在Python里有两个和Socket有关的主要模块:socketsocketserver,其中,socket模块提供底层网络接口,即标准的BSD Socket API, socketserver提供了网络服务的框架,可以简化对应的socket开发。Socket API可以在官方文档上查阅得到,主要的Socket API函数有以下几个:

  • socket()
  • bind()
  • listen()
  • accept()
  • connect()
  • connect_ex()
  • send()
  • recv()
  • close()

以TCP Socket为例:

socket()

import socket后,使用socket.scoket()创建一个默认类型为socket.SOCK_STREAM的socket,并默认使用TCP协议,完整的写法应该是socket.socket(socket.AF_INET, socket.SOCK_STREAM)AF_INET表示IPV4,相对应的有AF_UNIX(表示UNIX系统本机进程通信)和 AF_INET6(表示IPV6);SOCK_STREAM代表基于TCP的流式socket,而对应的SOCK_DGRAM则代表 基于UDP的数据报式socket。

bind()

接下来,服务端的socket绑定相应的地址端口——bind((HOST,PORT)),在bind的参数中,括号内的形式取决于socket的地址族,socket.AF_INETIPv4将返回(HOST,PORT)形式的二元组。

下面这个问题有助于理解为什么服务端绑定地址端口:

In socket programming, why a client is not bind to an address?

HOST说明及需要注意的是:

  • A pair (host, port) is used for the AF_INET address family, where host is a string representing either a hostname in Internet domain notation like 'daring.cwi.nl' or an IPv4 address like '100.50.200.5', and port is an integer.

    • For IPv4 addresses, two special forms are accepted instead of a host address: '' represents INADDR_ANY, which is used to bind to all interfaces, and the string '' represents INADDR_BROADCAST. This behavior is not compatible with IPv6, therefore, you may want to avoid these if you intend to support IPv6 with your Python programs.

If you use a hostname in the host portion of IPv4/v6 socket address, the program may show a nondeterministic 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.

listen()

完成地址端口的关联,listen([backlog])函数让服务端可以接收连接请求,backlog参数从Python 3.5开始可选,它指定在拒绝新的连接之前系统将允许使用的未接受的连接数量。

如果服务器需要同时接收很多连接请求,增加 backlog 参数的值可以加大等待链接请求队列的长度,最大长度取决于操作系统。比如在 Linux 下,参考will-increasing-net-core-somaxconn-make-a-difference

accept()

accept()函数用来接收客户端的一个连接,返回值是一对(conn, address),值得注意的是,conn是一个在连接上可以send和receive数据的新socket对象,address是一个由主机、端口号组成的 IPv4/v6 连接的元组。也就是说这时有两个socket对象,刚才那个监听socket是用来接收新的连接请求。 而现在这个socket对象将用来和客户端通信。

这时候,服务端已经做好准备工作:

TCP Socket Flow

#!/usr/bin/env python3

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("Connected by", addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

accept()获取客户端socket连接对象conn后,使用一个无限while循环来阻塞调用conn.recv(),无论客户端传过来什么数据都会使用conn.sendall()打印出来。如果conn.recv()方法返回一个空byte对象(b''),然后客户端关闭连接,循环结束,with语句和conn一起使用时,通信结束的时候会自动关闭socket连接。——socket-programming-in-python

connect()

#!/usr/bin/env python3

import socket

HOST = '127.0.0.1'  # 服务器的主机名或者 IP 地址
PORT = 65432        # 服务器使用的端口

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('Received', repr(data))

客户端也需要创建一个socket对象,调用connect()连接到服务端,并开始三次握手。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment