甄子丹老婆照片,重庆石柱周小燕,话说夏娃mv
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)
proto=0 请忽略,特殊用途
fileno=None 请忽略,特殊用途
1.简单套接字
客户端和服务端:两个主要功能,1、建立链接 2、数据通讯
服务端程序会产生两个套接字socket,一个用于三次握手建立链接,另一个用于收发消息数据通讯;
客户端产生一个套接字socket,既可以用于建立链接后,再用于收发消息数据通讯。
client.py
1 import socket 2 3 #1.买手机 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 print(phone) 6 7 #2.拨号 8 phone.connect(('127.0.0.1',8081)) 9 #端口范围0-65535,0-1024给操作系统用的,若一直无法连接上server,则会一直停留在这一步 10 11 #3.发收消息 12 phone.send('hello'.encode('utf-8')) 13 data = phone.recv(1024) 14 print(data) 15 16 #4.关闭 17 phone.close()
service.py
1 import socket 2 3 #1.买手机 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 print(phone) 6 7 #2.绑定手机卡 8 phone.bind(('127.0.0.1',8081)) #端口范围0-65535,0-1024给操作系统用的 9 10 #3.开机 11 phone.listen(5) # 参数表示最大监听数 12 13 #4.等电话链接 14 print('starting...') 15 conn,client = phone.accept() #返回一个新的套接字conn用于通讯,client为发起通讯链接的客户端的ip和端口号 16 print(conn,client) 17 # print('===>') 18 19 #5.收,发消息 20 data = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 21 print('客户端的数据',data) 22 conn.send(data.upper()) 23 24 #6.挂电话 25 conn.close() 26 27 #7.关机 28 phone.close()
client.py
1 import socket 2 3 #1.买手机 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 6 print(phone) 7 8 #2.拨号 9 phone.connect(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的 10 11 while True: 12 msg = input('>>:').strip() 13 if not msg:continue 14 phone.send(msg.encode('utf-8')) #phone.send(b'') 15 print('has send') #判断能否发空 16 data = phone.recv(1024) 17 print(data.decode('utf-8')) 18 19 #4.关闭 20 phone.close()
service.py
1 import socket 2 3 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 print(phone) 7 phone.bind(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的 8 phone.listen(5) #最大链接挂起数 9 10 print('starting...') 11 conn,client = phone.accept() #监听 12 # print('===>') 13 14 #监听到到后,进行通讯循环 15 # while True: 16 # data = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 17 # #conn tcp协议三次握手的成果,双向链接 18 # if not data:break #适用与linux操作,当client单方面终止链接时,service端会出现死循环 19 # print('客户端的数据',data) 20 # conn.send(data.upper()) 21 22 while True: 23 try: 24 data = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 25 #conn tcp协议三次握手的成果,双向链接 26 27 print('客户端的数据',data) 28 conn.send(data.upper()) 29 except ConnectionResetError: 30 31 break 32 33 conn.close() 34 phone.close()
有时重启服务端时会遇到报错:
由于重启时系统还没来得及回收端口,因此会提示端口已被占用。
这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
解决方法:加入一条socket配置,重用ip和端口。
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind ip和端口 前加。
或者:
实验之前要全部关闭掉所用占用端口的程序,用以下指令
linux:pkill -9 python
windows:taskkill python
之前代码运行可知,client端关闭后,service端也会关闭,但此刻我们想client端关闭后,service端应该能在接收新的client端的链接请求,因此,在建
立链接的部分加入循环。
client.py
1 import socket 2 3 #1.买手机 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 6 print(phone) 7 8 #2.拨号 9 phone.connect(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的 10 11 while True: 12 msg = input('>>:').strip() 13 if not msg:continue 14 phone.send(msg.encode('utf-8')) #phone.send(b'') 15 print('has send') #判断能否发空 16 data = phone.recv(1024) 17 print(data.decode('utf-8')) 18 19 #4.关闭 20 phone.close()
service.py
1 import socket 2 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 5 print(phone) 6 phone.bind(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的 7 phone.listen(5) #最大链接挂起数 8 9 print('starting...') 10 11 while True: 12 ''' 13 用于监听多次client端的链接,但一次链接发起结束后, 14 可继续监听下一次client端的连接 15 ''' 16 conn,client = phone.accept() 17 print(client) 18 while True: 19 try: 20 data = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 21 #conn tcp协议三次握手的成果,双向链接 22 if not data: break 23 print('客户端的数据',data) 24 conn.send(data.upper()) 25 except ConnectionResetError: 26 break 27 conn.close()
client.py
1 import socket,subprocess 2 3 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 print(phone) 7 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 8 phone.listen(5) #最大链接挂起数 9 10 print('starting...') 11 while True: 12 conn,client = phone.accept() #监听 13 14 while True: #通讯循环 15 try: 16 #1、收命令 17 cmd = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 18 #conn tcp协议三次握手的成果,双向链接 19 if not cmd: break 20 #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端 21 obj = subprocess.Popen(cmd.decode('utf-8'),shell=True, 22 # 指令由client端发送过来是以utf-8解码为bytes发送过来的,因此处应该以utf-8来编码, 23 # 因此此处的命令编码应该与client端的一致 24 stdout=subprocess.PIPE, 25 stderr=subprocess.PIPE) 26 print(obj) 27 stdout = obj.stdout.read() 28 stderr = obj.stderr.read() #s收发都是bytes格式 29 30 #3、把命令的结果返回给客户端 31 conn.send(stdout+stderr) #申请一块新的内存空间存放stdout+stderr,会占内存,效率会低 32 except ConnectionResetError: 33 break 34 conn.close() 35 36 phone.close()
service.py
1 import socket 2 3 #1.买手机 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 6 print(phone) 7 8 #2.拨号 9 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 10 11 while True: 12 msg = input('>>:').strip() 13 if not msg:continue 14 phone.send(msg.encode('utf-8')) 15 # 注意:信息由utf-8解码为bytes格式发送到service端,因此service端也必须把bytes格式以utf-8来编码, 16 17 data = phone.recv(1024) #返回值可能超过1024bytes, 18 print(data.decode('gbk')) 19 # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8 20 21 #4.关闭 22 phone.close()
此处注意两个小问题:
1.service端的命令的编码应该与client端的解码模式对应,client端以utf-8解码指令为bytes,则service端必须以utf-8来编码;
2.service端的把命令结果发送给client端,client则需要将命令结果进行编码,若serice端在windows上,则以GBK进行编码,若在linux上则以utf-8进行编码。
须知:只有TCP有粘包现象,UDP永远不会粘包
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
两种情况下会发生粘包:
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据段很小,会合到一起,产生粘包)。TCP使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
client.py
1 import socket 2 3 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 client.connect(('127.0.0.1',9903)) 5 6 #TCP使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据, 7 # 合并成一个大的数据块,然后进行封包。从而在发送端造成粘包。 8 client.send('hello'.encode('utf-8')) 9 client.send('world'.encode('utf-8'))
service.py
1 import socket 2 3 service = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 service.bind(('127.0.0.1',9903)) 5 service.listen(5) 6 7 conn,addr = service.accept() 8 9 res1 = conn.recv(1024) 10 print('第一次',res1.decode()) 11 12 res2 = conn.recv(1024) 13 print('第二次',res2.decode())
输出结果:
第一次 helloworld 第二次
发送端由于TCP 优化算法造成粘包
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
client.py
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 import socket 4 BUFSIZE=1024 5 ip_port=('127.0.0.1',8080) 6 7 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 res=s.connect_ex(ip_port) 9 10 11 s.send('hello feng'.encode('utf-8')) 12 13 客户端
service.py
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 from socket import * 4 ip_port=('127.0.0.1',8080) 5 6 tcp_socket_server=socket(AF_INET,SOCK_STREAM) 7 tcp_socket_server.bind(ip_port) 8 tcp_socket_server.listen(5) 9 10 11 conn,addr=tcp_socket_server.accept() 12 13 14 data1=conn.recv(2) #一次没有收完整 15 data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 16 17 print('----->',data1.decode('utf-8')) 18 print('----->',data2.decode('utf-8')) 19 20 conn.close() 21 22 服务端
输出结果
-----> he -----> llo feng
接收端由于没能一次将发送端一次发送的数据全部接受,导致粘包
拆包的发生情况
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
补充问题一:为何tcp是可靠传输,udp是不可靠传输
tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的。
而udp发送数据,对端是不会返回确认信息的,因此不可靠。
补充问题二:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失。
send 和 recv:
1.不管是recv还是send都不是直接接收对方的数据,而是操作自己的操作系统内存-->不是一个send对应一个recv
2.recv阶段,耗时分析:
wait data 耗时非常长
copy data 耗时短
send耗时分析:
copy data
粘包问题的原因在于接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
第一步:先拿到数据的长度
第二步:接收真实的数据
先来介绍下struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes类型
但注意类型如数字是有范围的,超出范围就会报错
>>> struct.pack('i',1111111111111)
。。。。。。。。。
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
1 import struct 2 3 res = struct.pack('i', 1235) 4 print(res,type(res), len(res)) 5 6 obj = struct.unpack('i', res) 7 print(obj)
输出结果
b'\xd3\x04\x00\x00' <class 'bytes'> 4 (1235,)
在数据发送端将数据长度打包,发送给接收端,解包获取实际数据的长度。
简单版本报头自制
client.py
1 import socket, struct 2 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 print(phone) 5 6 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 7 8 while True: 9 #1、发命令 10 msg = input('>>:').strip() 11 if not msg:continue 12 phone.send(msg.encode('utf-8')) 13 14 #2、拿到命令的结果,并打印 15 16 #第一步:先收报头 17 header = phone.recv(4) 18 #第二步:从报头中解析出对真实数据的描述信息 19 total_size = struct.unpack('i',header)[0] 20 21 #第三部:接收真实数据 22 recv_size = 0 23 recv_data = b'' 24 while recv_size < total_size: 25 data = phone.recv(1024) 26 recv_data += data 27 recv_size += len(data) 28 29 print(data.decode('gbk')) 30 # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8 31 32 phone.close()
service.py
1 import socket,subprocess,struct 2 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 5 phone.listen(5) #最大链接挂起数 6 7 print('starting...') 8 while True: 9 conn,client = phone.accept() #监听 10 11 while True: #通讯循环 12 try: 13 #1、收命令 14 cmd = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 15 #conn tcp协议三次握手的成果,双向链接 16 if not cmd: break 17 #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端 18 obj = subprocess.Popen(cmd.decode('utf-8'),shell=True, 19 stdout=subprocess.PIPE, 20 stderr=subprocess.PIPE) 21 print(obj) 22 stdout = obj.stdout.read() 23 stderr = obj.stderr.read() #s收发都是bytes格式 24 25 #3、把命令的结果返回给客户端 26 #第一步:制作固定长度的报头 27 total_size = len(stdout)+len(stderr) 28 header = struct.pack('i', total_size) 29 30 #第二步:把报头(固定长度)发送给客户端 31 conn.send(header) 32 33 #第三步:再发送真实数据 34 conn.send(stdout) 35 conn.send(stderr) 36 37 except ConnectionResetError: 38 break 39 conn.close() 40 41 phone.close()
高阶报头自制
以上讲解了简单报头的自制,但有缺陷:
1、报头存有的信息少。
2、struct模块打包的int数字有范围,普通指令的结果长度虽然不会超过这个范围,但是上传下载文件时就很有可能会超过此范围,因此下面介绍同样使用struct模块来自制跟高端的报头。
以字典的形式制作报头,字典中可以存文件名、文件md5值、文件大小等,再将字典序列化,将序列化的字符串长度通过struct pack,这样既可让报头存有足够的信息,又不会超出struct模块打包的int的数字范围。
client.py
1 import socket, struct, json 2 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 print(phone) 5 6 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 7 8 while True: 9 #1、发命令 10 msg = input('>>:').strip() 11 if not msg:continue 12 phone.send(msg.encode('utf-8')) 13 14 #2、拿到命令的结果,并打印 15 16 #第一步:先收报头长度 17 obj = phone.recv(4) 18 header_size = struct.unpack('i',obj)[0] 19 # 第二步:再收报头 20 header = phone.recv(header_size) 21 22 #第三步:从报头中解析出对真实数据的描述信息 23 header_dic = json.loads(header) 24 total_size = header_dic['total_size'] 25 26 #第三部:接收真实数据 27 recv_size = 0 28 recv_data = b'' 29 while recv_size < total_size: 30 data = phone.recv(1024) 31 recv_data += data 32 recv_size += len(data) 33 34 print(data.decode('gbk')) 35 # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8 36 37 phone.close()
service.py
1 import socket, subprocess, struct, json 2 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的 5 phone.listen(5) #最大链接挂起数 6 7 print('starting...') 8 while True: 9 conn,client = phone.accept() #监听 10 11 while True: #通讯循环 12 try: 13 #1、收命令 14 cmd = conn.recv(1024) # 单位:bytes, 1024代表最大接收1024个bytes 15 #conn tcp协议三次握手的成果,双向链接 16 if not cmd: break 17 #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端 18 obj = subprocess.Popen(cmd.decode('utf-8'),shell=True, 19 stdout=subprocess.PIPE, 20 stderr=subprocess.PIPE) 21 print(obj) 22 stdout = obj.stdout.read() 23 stderr = obj.stderr.read() #s收发都是bytes格式 24 25 #3、把命令的结果返回给客户端 26 #第一步:制作报头 27 28 header_dic = { 29 'filename':'a.txt', 30 'md5':'xxfdxxx', 31 'total_size': len(stdout)+len(stderr) 32 } 33 header_json = json.dumps(header_dic) 34 header_types = header_json.encode('utf-8') 35 36 #第二步:把报头(固定长度)发送给客户端 37 conn.send(struct.pack('i',len(header_types))) 38 39 #第三步:再发送报头、 40 conn.send(header_types) 41 42 #第四步:再发送真实数据 43 conn.send(stdout) 44 conn.send(stderr) 45 46 except ConnectionResetError: 47 break 48 conn.close() 49 50 phone.close()
udp 不需要经过3次握手和4次挥手,不需要提前建立连接,直接发数据就行。
udp协议虽然不会产生粘包,但 udp协议不安全,tcp协议会在发数据前发个信息,在接收端回复确认可以接收数据后,才会发送数据,发送数据 后还要等待接收端回复已接收后才会继续发送,因此tcp协议是稳定安全的。
client.py
1 import socket 2 # 建立套接字对象 3 client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 4 #client.connect(('127.0.0.0',8080)) #udp没有链接 5 6 7 while True: 8 msg = input('>>:').strip() 9 client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) #udp没有链接,发送信息必须指定ip和端口 10 11 data,server_addr = client.recvfrom(1024) 12 print(data,server_addr) 13 14 client.close()
>>:hah b'HAH' ('127.0.0.1', 8080) >>:yy b'YY' ('127.0.0.1', 8080) >>:
service.py
1 import socket 2 # 建立套接字对象 3 server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 4 server.bind(('127.0.0.1',8080)) 5 6 #server.listen() #监听最大链接数,udp没有链接 7 #server.accept() #建立链接,udp无链接 8 9 while True: 10 data,client = server.recvfrom(1024) 11 print(data,client) #返回数据和数据发送端的ip和端口 12 server.sendto(data.upper(),client) #udp没有链接,发送信息必须指定ip和端口 13 14 server.close()
b'hah' ('127.0.0.1', 59001) b'yy' ('127.0.0.1', 59001)
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
win10如何启用管理员账户 win10禁用/启用管理员账户的方法
win10怎么查看驱动是否异常 Win10检测驱动程序是否正常的方法
Windows10系统任务栏无效图标怎么删除 通过注册表一键快速删除
Win10系统自动重启怎么办 Win10系统自动重启的关闭方法
怎么减少win10笔记本功耗 详谈笔记本硬件功耗大的原因和解决办法
网友评论