网络编程是计算机之间的数据交互。数据传输的大致过程是,计算机A与计算机B通信。计算机A从本机的应用层,传输层,网络层,数据链路层和物理链路层发出信息,通过网络,传输到计算机B,通过计算机B的物理链路层,数据链路层,网络层,传输层到达应用层的应用程序。这也说明,通信本质上应用程序之间的数据交互。
数据从应用层到物理链路层有很多协议,想要发出信息,就必须遵守这些协议的规范。套接字socket模块就帮助开发者解决这中问题。
套接字有两种其中,一种是文件类型的套接字:AF_UNIX,一种是网络类型的套接字:AF_INET。文件类型的套接字可以应用在Linux系统,Linux系统一切皆文件。使用python经行网络编程,使用更多的是AF_INET网络类型的套接字。
TCP通信
socket通信
通信代码如下图。整个通信先开启服务端,服务端运行到接受客服端链接时暂停,等待客户端发来的信号开始执行之后的程序。这时启动客户端,客户端发送信息,服务端接受到后从对象中取出数据,最大1024个字节,然后打印hello回复信息。关闭通信。客服端也一样,收到信息。进行打印,关闭通信。其中服务端有一个连接池的概念,这个链接池限制链接客户端的个数。
# 服务端
import socket # 调用socket模块
server = socket.socket() # 创建一个套接字对象
server.bind(('192.168.12.183', 8080)) # 服务端自己的IP和端口 IPv4即可,端口自己配置,在8000以上最好
server.listen(5) # 监听链接, 称便连接池
conn, addr = server.accept() # 接受客服端链接,也可视为使双向连接通道建立完成。返回的conn是对象客服端套接字对象,addr是地址
data = conn.recv(1024) # 数据接受
print(data) # 打印接收数据
conn.send(b'word!') # 回复信息
conn.close() # 断开通道
server.close() # 关闭通信
>>>b'hello'
#客户端
import socket
client = socket.socket()
client.connect(('192.168.12.183', 8080)) # 链接服务端,服务端IP与端口
client.send(b'hello') # 发送数据
data = client.recv(1024) # 数据接受
print(data) # 打应数据
client.close() # 关闭通道
>>>b'word!'
在通信的时候一般都是可以经行循环通信。在上面的基础上可以改进此通信。使服务端可以随时经行通信。
# 服务端
import socket
server = socket.socket()
server.bind(('192.168.12.183', 8080)) # 服务端自己的IP和端口
server.listen(5)
conn, addr = server.accept() # 建立通道 在此基础上可以经行数据传输
while True:
data = conn.recv(1024)
print(data)
conn.send(b'word!')
# 客户端
import socket
client = socket.socket()
client.connect(('192.168.12.183', 8080))
while True:
msg = input('>>>').strip()
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
连接循环。对输入和接受的数据进行逻辑处理是程序更加健壮。
# 服务端
import socket
server = socket.socket()
server.bind(('192.168.12.183', 8080)) # 服务端自己的IP和端口
server.listen(5)
while True:
conn, addr = server.accept() # 建立通道 在此基础上可以经行数据传输
while True:
try:
data = conn.recv(1024)
print(data)
if len(data) == 0: break # 客服端异常退出后会循环打印b'',这个时候需要做个判断
conn.send(b'word!')
except ConnectionResetError:
break
conn.close()
# 客户端
import socket
client = socket.socket()
client.connect(('192.168.12.183', 8080))
while True:
try:
msg = input('>>>').strip()
if not msg: continue
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
except ConnectionResetError as e:
print(e)
break
在使用TCP通信时,但多个数据包连续(时间间隔比较短时会出现黏包)发送时,接受方会将多个包同时接受以先后顺序,队列的方式放在一起。接收方会无法将多个文件经行分离。这是一种黏包现象。

# 服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
while True:
conn, addr = server.accept()
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(b'hello')
conn.send(b'hello')
conn.send(b'hello')
conn.send(b'hello')
conn.send(b'hello')
>>>aaa
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8081))
while True:
msg = input('>>>').encode('utf-8')
client.send(msg)
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
b'hello'
b'hellohellohello'
b'hello'
解决黏包现象可以用报头的方式解决这种问题。利用报头固定大小,携带数据大小的属性。告诉接收者发送到数据有多大,按照大小接受。

# 客户端
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
try:
com = input('>>>').strip()
if len(com) == 0: continue
client.send(com.encode('utf-8'))
header = client.recv(4) # 先收报头
dict_l = struct.unpack('i',header)[0] # 解压报头获取携带字典大小数据
j_dict = client.recv(dict_l) # 按字典大小接收字典
dict_m = json.loads(j_dict.decode('utf-8')) # 还原字典
a = 0
msg = b''
if a < dict_m['file_msg']: # dict_m['file_msg'] 获取字典中携带要发送数据包的大小
data = client.recv(1024)
msg += data
a += len(data)
print(msg)
except BaseException:
break
# 服务端
import socket
import json
import struct
import subprocess
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept()
while True:
try :
com = conn.recv(1024)
if len(com) == 0: break
print(com)
obj = subprocess.Popen(com.decode('utf-8'),shell =True,
stdout = subprocess.PIPE,
stderr= subprocess.PIPE)
res = obj.stdout.read() + obj.stderr.read() # 要发送信息
dict_s = {'name':'命令信息', 'file_msg': len(res)} # 将要发送信息放入字典中
json_d = json.dumps(dict_s) # 将字典序列化
header = struct.pack('i',json_d) # 将序列化的字典大小信息放入报头
conn.send(header) # 先发报头
conn.send(json_d.encode('utf-8')) # 在发序列化后的字典
conn.send(res) # 最后发要发送到信息
except BaseException:
break
conn.close()
UDP通信
UDP通信,自带报头,不存在粘包现象。与TCP协议下通信相比,UDP协议下通信不需要建立双向通道,直接向对方地址发送。支持一对一,和一对多,可进行并发通信。
UDP通信的有四个特点。客户端允许发送,不会粘包,支持并发,客户端不存在也不会出错。

# 客户端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
client_addr = ('127.0.0.1', 8080) # 服务端地址端口
while True:
msg = input('>>>')
server.sendto(msg.encode('utf-8'), client_addr) # 发数据包
data, addr = server.recvfrom(1024) # 收数据包
print(data)
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080)) # 服务端自己的地址和端口
while True:
data, addr = server.recvfrom(1024) # 收数据包,返回数据包和客户端地址
print(data)
msg = 'hello'
server.sendto(msg.encode('utf-8'), addr) # 发数据,按返回的客户端地址发送
socketserver模块。使TCP协议下通信和UDP协议下通信一样灵活。

# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))
# 服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler): # 设计一个类,继承socketserver模块下BaseRequestHandler这个类
def handle(self):
while True:
try:
data = self.request.recv(1024) # 接收数据
print(data.decode('utf-8'))
self.request.send(b'ok') # 发送数据
except ConnectionResetError as e:
print(e)
break
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.01', 8080), MyServer) # 创建对象
server.serve_forever() # 启动对象
