Python网络编程之简单交互

为单用户服务

  • server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 绑定IP及端口号
s.bind(ip_port)

# step 3: 监听绑定的端口
s.listen(5)

# 等待客户端的连接(阻塞函数)
# step 4: 接受客户端的连接
conn, addr = s.accept()
# conn对象里封装了连接过来的这个客户端的通信线路信息
# 后期跟这个客户端的通信与交互都需要在conn这条通信线路上进行

while True:

# step 5: 接收消息(在conn通道没有被关闭的情况下是阻塞的函数,一旦conn被客户端关闭,该函数将不会阻塞)
recv_data = conn.recv(1024)

# 如果conn通道被客户端主动关闭,recv函数将不再阻塞,recv_data将接收到空字符串
# 通过判断recv_data为空字符串来退出服务端的连接
if len(recv_data) == 0: break

# step 6: 发送消息
send_data = recv_data.upper()
conn.send(send_data)

# step 7: 断开连接
conn.close()

这个服务端在与客户端交互(收发消息)的部分使用了循环,没次接收消息,处理消息,发送消息之后就进入到下一次循环等待接收消息。

这里需要注意的一点就是conn,服务端的conn对象对应了客户端的s对象,都代表了两端之间建立的连接通道,一旦客户端主动关闭连接对象s,在服务端对应的conn对象将立即失效,随即退出循关闭服务端持有的连接对象。

此时conn.recv()函数不再是一个阻塞的状态,它将返回空值。我们需要对接收这个空值的对象(变量)做出相应的处理,关闭掉服务端持有的连接对象

  • client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 连接服务端
s.connect(ip_port)

while True:

# step 3: 发送消息
send_data = input("> ").strip()

# 如果客户端输入了exit,退出循环,主动close掉与服务端的连接
if send_data == "exit": break

# 如果什么也没有输入,重新循环接收输入
if len(send_data) == 0: continue

# 这里注意,和服务端不同的是,服务端找到对端是通过conn对象,而客户端是s对象
# 在Python3.x中,socket对象发送对象必须是字节类型(2.7中可以是字符串)
s.send(bytes(send_data, encoding='utf-8'))

# step 4: 收消息
recv_data = s.recv(1024)
print(recv_data, "--->", type(recv_data), str(recv_data, encoding='utf-8'))

# step 5: 断开连接
s.close()

这个版本实现了服务端为单个用户提供服务的场景。

从上面的说明中也可以看出,服务端在接收到客户端主动关闭的操作后(特征为:conn.recv返回值为空),相继关闭服务端持有的连接对象,接着代码就停止了。

这里需要特别注意的就是处理客户端输入为空的情况,因为服务端判断客户端是否主动断开,主要依据的就是conn.recv方法的返回值是否为空,所以我们要屏蔽掉用户输入为空的情况

为多用户服务(排队)

  • server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 绑定IP及端口号
s.bind(ip_port)

# step 3: 监听绑定的端口
s.listen(5)

# 把接收客户端连接请求的操作循环起来就可以接受多个用户的请求啦
# 注意:同一时间只能处理一个客户端的请求,其他连接上的用户会排队等待
# 最后支持多少个用户排队等待,是由listen中的参数决定的(连接池)
while True:

# 等待客户端的连接(阻塞函数)
# step 4: 接受客户端的连接
conn, addr = s.accept()
# conn对象里封装了连接过来的这个客户端的通信线路信息
# 后期跟这个客户端的通信与交互都需要在conn这条通信线路上进行

while True:

# step 5: 接收消息(在conn通道没有被关闭的情况下是阻塞的函数,一旦conn被客户端关闭,该函数将不会阻塞)
recv_data = conn.recv(1024)

# 如果conn通道被客户端主动关闭,recv函数将不再阻塞,recv_data将接收到空字符串
# 通过判断recv_data为空字符串来退出服务端的连接
if len(recv_data) == 0: break

# step 6: 发送消息
send_data = recv_data.upper()
conn.send(send_data)

# step 7: 断开连接
conn.close()

服务端的代码只增加了一个while循环,上一个代码版本中,服务端唯一的循环放在了处理某一客户端的请求上。此次加上的循环,放在了接收用户连接请求上,可以在关闭上一个连接之后,循环的处理下一个连接请求

  • client.py 客户端的代码没有任何修改

此时,连续的运行两个客户端,发现两个客户端都可以连接到服务端,但是一个可断端发出请求后,在第一个客户端不关闭连接的情况下,第二个客户端发出的请求一直卡在终端中。只有第一个客户端关闭连接,触发服务端关闭对第一个客户端的连接之后,才会去接收第二个客户端发来的信息

这里特别提示一下,当两个客户端相继连接上服务端后,看似服务端对两个连接都接受了请求,但是,只有第一个连接过来的客户端才进入到了服务端代码中的内层while循环体,此时即使第一个客户端没有发送信息,第二个客户端先给服务端发送信息,终端也是会阻塞住的