TCP通信

1. 前言

socket模块是Python网络编程中非常基础的模块,使用该模块,可以创建TCPUDP的服务端或客户端,从而实现ClientServer之间的通信。

默认情况下,创建的都是阻塞式socket。用户可以借助一些协程框架,进行异步通信,从而提升效率。

2. 创建TCP服务端

2.1. 1. 创建socket对象

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

socket.socket是一个类,构造函数定义如下:

def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0)

family一般翻译为协议族,常见的取值有AF_INET(因特网)、AF_INET6(IPv6)、AF_UNIX(Unix域)等。最为常见的就是AF_INET了,TCPUDP就是使用的这个。

type表示使用何种协议,SOCK_STREAM表示TCPSOCK_DGRAM表示UDPSOCK_RAW表示原始套接字

proto表示使用的协议,如:TCPUDP等。需要注意的是,typeproto必须匹配才行。协议数值具体定义如下:

#define IPPROTO_IP 0 /* dummy for IP */
#define IPPROTO_ICMP 1 /* control message protocol */
#define IPPROTO_IGMP 2 /* internet group management protocol */
#define IPPROTO_GGP 3 /* gateway^2 (deprecated) */
#define IPPROTO_TCP 6 /* tcp */
#define IPPROTO_PUP 12 /* pup */
#define IPPROTO_UDP 17 /* user datagram protocol */
#define IPPROTO_IDP 22 /* xns idp */
#define IPPROTO_ND 77 /* UNOFFICIAL net disk proto */
#define IPPROTO_RAW 255 /* raw IP packet */
#define IPPROTO_MAX 256

对于创建TCP协议的socket,也可以直接使用以下代码:

s = socket.socket()

2.2. 2. 绑定监听地址

s.bind(('0.0.0.0', 80))

以上代码可以将socket绑定在全局地址的80端口。这样,局域网中的其它电脑也可以访问该地址;如果不希望将服务端暴露出去,可以使用127.0.0.1回环地址。 如果将端口指定为0,系统会使用一个随机的可用端口。

2.3. 3. 开始监听

s.listen(1)

使用listen函数开始监听该地址,传入的参数表示允许同时建立连接的客户端数量。这是因为建立TCP连接需要三次握手,而服务端需要限制同时可以进行连接的最大并发数。

2.4. 4. 接受连接

conn, addr = s.accept()

调用accept函数后,会一直阻塞到有新连接到达,返回值中conn是新建立的连接的socket对象,addr是客户端的(ip, port)二元组。

3. 创建客户端并与服务端建立连接

3.1. 1. 创建socket对象

这步与创建服务端的方法完全一致

3.2. 2. 连接服务端

s.connect(('127.0.0.1', 80))

连接成功会直接返回;如果连接失败会抛出异常。

访问外网服务时,有时会因为网络波动导致连接失败,这种情况可以通过捕获异常后重试来解决。

4. 服务端与客户端收发数据

4.1. 1. 接收数据

buff = conn.recv(4096)

使用recv函数可以获取通信对端发送过来的数据,参数为缓冲区大小,表示本次接收到的数据最大长度不会超过该阀值。buff为实际接收到的数据字节数组。如果buff长度为0,表示连接已经被对方关闭。

默认情况下,该调用是阻塞方式,会一直等待到有数据获取连接关闭才会返回。

可以使用conn.settimeout(timeout)设置超时时间,如果timeout时间到,会抛出socket.timeout异常。

在接收数据过程中,如果连接中断,也会抛出异常。因此,一般都需要对recv调用加入异常捕获逻辑。

4.2. 2. 发送数据

conn.send(buff)

该调用默认也是阻塞调用,但是由于一般情况下,发送数据都比较快,因此不如recv调用感觉那么明显。

4.3. 3. 关闭连接

conn.close()

为了避免资源泄漏。需要及时关闭连接。

5. 简单的例子

5.1. 服务端代码

def create_server(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('0.0.0.0', port))
    s.listen(1)
    while True:
        conn, addr = s.accept()
        print('Recv connection from %s:%d' % addr)
        while True:
            try:
                data = conn.recv(4096)
            except socket.error as e:
                print('Connection %s:%d closed: %s' % (addr[0], addr[1], e))
                break

            if not data:
                print('Connection %s:%d closed' % addr)
                break

            try:
                conn.send(data)
            except socket.error as e:
                print('Send data to %s:%d failed: %s' % (addr[0], addr[1], e))
                break

        conn.close()

5.2. 客户端代码

def create_client(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', port))

    s.send('Hello python!!!') # add try
    print('Recv: ' + s.recv(4096)) # add try
    s.close()

5.3. 结果显示如下

服务端:

Recv connection from 127.0.0.1:54993
Connection 127.0.0.1:54993 closed

客户端:

Recv: Hello python!!!
drunkdream.cn 版权所有 粤ICP备17153329号 all right reserved,powered by Gitbook该文件修订时间: 2021-01-07 18:27:03

results matching ""

    No results matching ""