TCP通信
1. 前言
socket模块是Python
网络编程中非常基础的模块,使用该模块,可以创建TCP
和UDP
的服务端或客户端,从而实现Client
和Server
之间的通信。
默认情况下,创建的都是阻塞式
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
了,TCP
和UDP
就是使用的这个。
type
表示使用何种协议,SOCK_STREAM
表示TCP
,SOCK_DGRAM
表示UDP
,SOCK_RAW
表示原始套接字
。
proto
表示使用的协议,如:TCP
、UDP
等。需要注意的是,type
和proto
必须匹配才行。协议数值具体定义如下:
#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!!!