实现一款能够进行数据交互的程序。
他们互通信息就得通过网络传输数据,那就肯定会涉及 OSI 七层协议的操作,而每次传输数据都要对OSI 七层协议进行操作,就会重复很多相似的工作,这时候就出现了 socket 模块,封装了OSI 七层协议的操作代码,我们在传输数据时,就可以通过socket 实例化的对象以点的形式方便快捷调用操作方法。
下面,我就用 socket 模块实现一个最简易的套接字编程
注意:先有服务端启动,客户端才能够成功连接服务端,实现数据互通
服务端
import socket # 实例化一个套接字对象 server = socket.socket() # 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识 server.bind(('192.168.11.134', 8080)) # 设置半连接池,最多容量(等待连接数)为5 server.listen(5) # 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口) sock, address = server.accept() # 接收数据 data = sock.recv(1024) print(data.decode('utf8')) # 发送数据 sock.send('elijah is very good'.encode('utf8')) # 关闭与客户端连接的socket对象 sock.close() # 关闭服务端自己的socket对象 server.close()
客户端
import socket # 实例化客户端的socket对象 client = socket.socket() # 客户端根据 IP+端口 精准连接服务端 client.connect(('192.168.11.134', 8080)) # 给服务端发送数据 client.send('i am client'.encode('utf8')) # 接收服务端回传的数据 data = client.recv(1024) print(data.decode('utf8')) # 关闭客户端 client.close()
给简易版本升一下级,让服务端与客户端可以输入自定义的信息,并且不会一传输完数据就结束进程,让服务端一直处于监听状态,随时可以与客户端进行连接。
服务端
import socket # 实例化一个套接字对象 server = socket.socket() # 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识 server.bind(('192.168.11.134', 8080)) # 设置半连接池,最多容量(等待连接数)为5 server.listen(5) # 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口) sock, address = server.accept() while True: # 接收数据 client_data = sock.recv(1024) print(client_data.decode('utf8')) # 发送数据 server_data = input('输入您的回复>>>: ') sock.send(server_data.encode('utf8'))
客户端
import socket # 实例化客户端的socket对象 client = socket.socket() # 客户端根据 IP+端口 精准连接服务端 client.connect(('192.168.11.134', 8080)) while True: # 给服务端发送数据 client_data = input('输入您的信息>>>: ') client.send(client_data.encode('utf8')) # 接收服务端回传的数据 data = client.recv(1024) print(data.decode('utf8'))
实现你一句我一句的通信:
1、让服务端一直处于监听状态,当服务端回复空消息,则断开与当前客户端的连接,回到监听状态 2、客户端发送的消息不可以为空(避免两者都处于revc等待状态) 3、服务端添加兼容性代码(mac linux) 4、服务端重启频繁报端口占用错误 from socket import SOL_SOCKET, SO_REUSEADDR server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加 5、客户端异常关闭服务端报错的问题 异常捕获 6.服务端链接循环 7.半连接池 设置可以等待的客户端数量(超出数量会报错)
服务端
import socket # 实例化一个套接字对象 server = socket.socket() # 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识 server.bind(('192.168.11.134', 8080)) # 设置半连接池,最多容量(等待连接数)为5 server.listen(5) while True: # 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口) sock, address = server.accept() while True: # 处理客户端异常断开错误 try: # 接收数据 client_data = sock.recv(1024) print(client_data.decode('utf8')) # 发送数据 server_data = input('输入您的回复>>>: ') if len(server_data) == 0: break sock.send(server_data.encode('utf8')) except Exception as e: print(e) break
客户端
import socket # 实例化客户端的socket对象 client = socket.socket() # 客户端根据 IP+端口 精准连接服务端 client.connect(('192.168.11.134', 8080)) while True: # 给服务端发送数据 client_msg = input('输入您的信息>>>: ') if len(client_msg) == 0: continue client.send(client_msg.encode('utf8')) # 接收服务端回传的数据 data = client.recv(1024) print(data.decode('utf8'))
由于TCP协议也是一个流式协议,数据是会像流水一样进行传输
当客户端向服务端发起命令请求,服务端返回很大容量的数据,而客户端一次只接收1024字节,显然接收不完,剩余的数据不会丢失,而是会堵在传输通道中,等客户端下一次发起申请命令时,剩余的数据就会涌入,导致答非所问的问题。
TCP协议有一个特性
当发送的数据量比较少,多次发送,且发送时间间隔比较短 那么TCP会自动把这些数据全部打包成一个数据包接收 sock.send('litle') sock.send('ll') sock.send('a') client.recv() client.recv() client.recv() ---》 litlella
报头用于标识即将到来的数据具体信息
比如,数据的具体大小,这样,接收方就可以根据这个数据大小的具体信息,决定接收多大的数据
client.recv(4343534)
简易版本报头
import socket import subprocess import json import struct server = socket.socket() server.bind(('127.0.0.1', 8080)) server.listen(5) while True: sock, address = server.accept() while True: data = sock.recv(1024) # 接收cmd命令 command_cmd = data.decode('utf8') sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) res = sub.stdout.read() + sub.stderr.read() # 结果可能很大 # 1.制作报头 data_first = struct.pack('i', len(res)) # 2.发送报头 sock.send(data_first) # 3.发送真实数据 sock.send(res) import socket import struct client = socket.socket() # 买手机 client.connect(('127.0.0.1', 8080)) # 拨号 while True: msg = input('请输入cmd命令>>>:').strip() if len(msg) == 0: continue client.send(msg.encode('utf8')) # 1.先接收固定长度为4的报头数据 recv_first = client.recv(4) # 2.解析报头 real_length = struct.unpack('i',recv_first)[0] # 3.接收真实数据 real_data = client.recv(real_length) print(real_data.decode('gbk'))
可以把报头制作成字典的形式,这样,除了可以接收数据具体大小信息,还可以接收更多其它的数据信息,并且strut 打包时 'i' 模式也不会数据过大而报错
在阅读源码的时候 1.变量名后面跟冒号 表示的意思是该变量名需要指代的数据类型 2.函数后更横杆加大于号表示的意思是该函数的返回值类型