前情提要
一:套接字 socket()
1:三次握手
1:客户端像服务端链接, (第一次握手)
2:服务端收到请求,告诉客户端服务端收到了内容 (第二次握手1)
3:服务端像客户端连接,(第二次握手2) 这俩可以合在一起
4:客户端收到服务端请求,并告诉客户端服务端已经收到了内容 (第三次握手)
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK[1],并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接。[1] TCP三次握手的过程如下: 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。 三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。 tcp的三次握手
2:四次挥手
1:客户端告诉服务器说客户端要断开了,( 第一次挥手)
2:服务端告诉客户端说服务端收到了断开信息(第二次挥手) 客户端和服务端通信关闭
3:服务端像客户端通信,说服务端要断开了连接(第三次挥手)
4:客户端收到通知后,告诉客户端已经收到了断开连接的信息. 服务端与客户端通信关闭
建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。 (1) 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。 (2) 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。 注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。 (3) 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。 (4) 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。[1] 既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。 注意: (1) “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。[2] (2) 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。 (3) 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。 无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。[2] tcp的四次挥手
3:基本模型
二:简单的socket例子
服务端:
# sk =socket.socket() # sk.bind(('127.0.0.1',8888)) #服务器建立ip 和端口 # sk.listen() # 创建监听 # conn,addr =sk.accept() #阻塞,直到有一个客户端来连接我,三次握手 # print(addr) # while True: # send_msg =input('msg: ') # conn.send(send_msg.encode()) #转化成2进制 # msg =conn.recv(1024).decode() #最大接收1024,解码 # print(msg) # conn.close() # sk.close()
import socket # sk =socket.socket() #实例化对象 # # sk.connect(('127.0.0.1',8888)) #选择要连接的服务器,端口 # # sk.send(b'12313123') #像服务器传输你要发送的东西 # # ret =sk.recv(1024) #设置接收,和发送的大小 字节 # # print(ret) #打印接收到的内容 # # sk.close() #关闭客户端链接
三:带退出的socket例子
服务端:
# 带双方退出的版本 import time sk =socket.socket() sk.bind(('127.0.0.1',8887)) sk.listen() #建立监听 time1 =time.strftime("%Y-%m-%d %H:%M:%S") while 1: conn,addr =sk.accept() #建立阻塞 # conn.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 while 1: ret = conn.recv(1024).decode() print(ret) if ret == 'Q': break else: send_msg = input('msg :>>>'+time1).encode() # time1 = # conn.send(time1) conn.send(send_msg) if send_msg == 'Q': break conn.close() sk.close
客户端:
import time time1 =time.strftime("%Y-%m-%d %H:%M:%S") sk =socket.socket() sk.connect(("127.0.0.1",8887)) while 1 : send_msg = input('msg:>>>'+str(time1)).encode() sk.send(send_msg) if send_msg == 'Q'.encode(): break else: ret = sk.recv(1024).decode() print(ret) if ret == 'Q': break sk.close()
四:带时间的socket例子
服务端: # import time # sk = socket.socket() # sk.bind(('127.0.0.1',9000)) # sk.listen() # while True: # conn,addr = sk.accept() # fmt = conn.recv(1024) # str_time = time.strftime(fmt.decode()) # conn.send(str_time.encode()) # conn.close() # sk.close()
客户端: # import socket # sk = socket.socket() # # sk.connect(('127.0.0.1',9000)) # # sk.send(b'%m/%d %H:%M:%S') # msg = sk.recv(1024).decode() # print(msg) # sk.close()
五:粘包
服务端
import struct import socket sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() conn,addr = sk.accept() send_msg = input('>>>').encode() bytes_len = struct.pack('i',len(send_msg)) conn.send(bytes_len) conn.send(send_msg) # 粘包现象 conn.send(b'world') conn.close() sk.close() # 1.发送端的粘包 合包机制 + 缓存区 # 2.接收端的粘包 延迟接受 + 缓存区 # 3.流式传输 # 电流 高低电压 # 所以我们说 tcp协议是无边界的流式传输 # 4.拆包机制 # 粘包现象 # 接收端不知道发送端给我发送了多长的数据
import struct import socket sk = socket.socket() sk.connect(('127.0.0.1',9000)) bytes_len = sk.recv(4) msg_len = struct.unpack('i',bytes_len)[0] msg = sk.recv(msg_len) print(msg.decode()) msg2 = sk.recv(5) print(msg2) sk.close()
六: 如何解决粘包 sturck包
服务端:
# import socket # import struct # sk =socket.socket() # sk.bind(('127.0.0.1',8888)) # sk.listen() # conn,addr =sk.accept() #创建阻塞 # send_msg =input('>>>').encode() # bete_len =struct.pack('i',len(send_msg)) #文件长度标志 # conn.send(bete_len) # conn.send(send_msg) # conn.close() # sk.close()
客户端
# import socket # sk =socket.socket() # sk.connect(('127.0.0.1',8888)) # ret =sk.recv(1024).decode() # print(ret) # sk.close() # import struct # import socket # sk =socket.socket() # sk.connect(('127.0.0.1',8888)) # bete_len =sk.recv(4) #只是读取前4个值 # # print(bete_len) # msg_len =struct.unpack('i',bete_len)[0] # msg =sk.recv(msg_len) # print(msg.decode()) # # msg2 =sk.recv(5) # sk.close()
七 :struct 包的使用
struct.pack('i',len(bytes)) #i 是固定的 len() 里面放 存的内容
他会返回4个字节 , 客户端将这4个字节读取就可以得到真实字符串长度
import struct ret = struct.pack('i',560000) print(ret,len(ret)) ret1 = struct.pack('i',123) print(ret1,len(ret1)) ret2 = struct.pack('i',902730757) print(ret2,len(ret2)) res = struct.unpack('i',ret) print(res[0]) res = struct.unpack('i',ret1) print(res[0]) res = struct.unpack('i',ret2) print(res[0]) >>>>>>>>>>> b'\x80\x8b\x08\x00' 4 字节 b'{\x00\x00\x00' 4 字节 b'\x05\x94\xce5' 4 字节 560000 123 902730757
使用struct解决黏包
借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
发送时 | 接收时 |
先发送struct转换好的数据长度4字节 | 先接受4个字节使用struct转换成数字来获取要接收的数据长度 |
再发送数据 | 再按照长度接收数据 |
import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: conn,addr=phone.accept() while True: cmd=conn.recv(1024) if not cmd:break print('cmd: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() print(err) if err: back_msg=err else: back_msg=res.stdout.read() conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度 conn.sendall(back_msg) #在发真实的内容 conn.close() 服务端(自定制报头)
#_*_coding:utf-8_*_ import socket,time,struct s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if len(msg) == 0:continue if msg == 'quit':break s.send(msg.encode('utf-8')) l=s.recv(4) x=struct.unpack('i',l)[0] print(type(x),x) # print(struct.unpack('I',l)) r_s=0 data=b'' while r_s < x: r_d=s.recv(1024) data+=r_d r_s+=len(r_d) # print(data.decode('utf-8')) print(data.decode('gbk')) #windows默认gbk编码 客户端(自定制报头)
年与时驰,意与日去,遂成枯落,
多不接世,悲守穷庐,将复何及。