基于UDP的socket

面向无连接的不可靠数据传输,可以没有服务器端,只不过没有服务器端,发送的数据会被直接丢弃,并不能到达服务器端

  1. #客户端
  2. import socket
  3. ip_port=('127.0.0.1',8080)
  4. BUFSIZE=1024
  5. sock_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #SOCK_DGRAM就是UDP
  6. while True:
  7. msg=input('>>').strip()
  8. if not msg:continue
  9. sock_client.sendto(msg.encode('utf-8'),ip_port) #UDP用的是sendto发送数据

UDP服务端+客户端

  1. #服务端
  2. import socket
  3. ip_port=('127.0.0.1',8080)
  4. BUFSIZE=1024
  5. sock_server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
  6. sock_server.bind(ip_port)
  7. #对比TCP,缺少listen侦听地址,缺少accept等待连接的代码
  8. while True:
  9. msg,addr=sock_server.recvfrom(BUFSIZE) #UDP接收数据使用recvfrom接收
  10. print('recv:',msg,addr)
  11. sock_server.sendto(msg.upper(),addr)
  12.  
  13. #客户端
  14. import socket
  15. ip_port=('127.0.0.1',8080)
  16. BUFSIZE=1024
  17. sock_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
  18. while True:
  19. msg=input('>>').strip()
  20. if not msg:continue
  21. sock_client.sendto(msg.encode('utf-8'),ip_port)
  22. # back_msg,addr=sock_client.recvfrom(BUFSIZE) #一般UDP用于广播,不会接收数据,如果没有服务端,启用该行代码会出错
  23. # print(back_msg.decode('utf-8'),addr)

由于UDP是面向无连接的(实际上有链接,不然通过什么去传数据去取数据),可以使用多个客户端连接服务端,但这并不是并发访问。

注意:

1. 发消息,都是将数据发送到己端的发送缓冲中,收消息都是从己端的缓冲区中收

   tcp:send发消息,recv收消息

   udp:sendto发消息,recvfrom收消息

2. tcp是基于数据流的,而udp是基于数据报的:

  send(bytes_data):发送数据流,数据流bytes_data若为空,自己这段的缓冲区也为空,操作系统不会控制tcp协议发空包

  sendinto(bytes_data,ip_port):发送数据报,bytes_data为空,还有ip_port,所有即便是发送空的bytes_data,数据报其实也不是空的,自己这端的缓冲区收到内容,操作系统就会控制udp协议发包。

3.1 tcp协议

(1)如果收消息缓冲区里的数据为空,那么recv就会阻塞(阻塞很简单,就是一直在等着收)

(2)只不过tcp协议的客户端send一个空数据就是真的空数据,客户端即使有无穷个send空,也跟没有一个样。

(3)tcp基于链接通信

  • 基于链接,则需要listen(backlog),指定半连接池的大小
  • 基于链接,必须先运行的服务端,然后客户端发起链接请求
  • 对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
  • 对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)

3.2 udp协议

(1)如果如果收消息缓冲区里的数据为“空”,recvfrom也会阻塞

(2)只不过udp协议的客户端sendinto一个空数据并不是真的空数据(包含:空数据+地址信息,得到的报仍然不会为空),所以客户端只要有一个sendinto(不管是否发送空数据,都不是真的空数据),服务端就可以recvfrom到数据。

(3)udp无链接

  • 无链接,因而无需listen(backlog),更加没有什么连接池之说了
  • 无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失
  • recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
  • 只有sendinto发送数据没有recvfrom收数据,数据丢失

粘包

对昨天ssh的客户端代码做点手脚

  1. import socket
  2. import subprocess
  3. ssh_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #生成socket实例对象
  4. ssh_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重用地址,防止占用
  5. ssh_server.bind(('127.0.0.1',8080))
  6. ssh_server.listen(5)
  7. print('server run...')
  8. while True:
  9. conn,client_addr=ssh_server.accept() #循环等待连接
  10. print('客户端: ',client_addr)
  11. while True: #通讯循环
  12. try:
  13. cmd=conn.recv(1024) #收消息
  14. res=subprocess.Popen(cmd.decode('utf-8'), #执行命令
  15. shell=True,
  16. stdout=subprocess.PIPE,
  17. stderr=subprocess.PIPE)
  18. stdout=res.stdout.read() #标准输出
  19. stderr=res.stderr.read() #标准输入
  20. std=stdout+stderr
  21. conn.sendall(std)
  22.  
  23. except Exception:
  24. break
  25. conn.close() #断连接,进入下一次连接等待
  26. ssh_server.close() #关闭程序

服务端不动代码

  1. #客户端动手脚
  2. import socket
  3. ssh_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  4. ssh_client.connect(('127.0.0.1',8080))
  5. while True: #通讯循环
  6. cmd=input('>>: ').strip()
  7. if not cmd:continue
  8. ssh_client.send(cmd.encode('utf-8'))
  9. cmd_res = ssh_client.recv(100) #动手脚位置,将一次接收的数据大小改为100字节
  10. print(cmd_res.decode('gbk')) #windows
  11. # print(cmd_res.decode('utf-8')) #linux
  12. ssh_client.close()

运行服务端后,执行客户端测试:

  1. >>: dir
  2. 驱动器 C 中的卷没有标签。
  3. 卷的序列号是 5E42-F448
  4.  
  5. C:\Users\Mr.chai\Desktop\PythonProject\笔\
  6. >>: pwd
  7. 2017.7.10\套接字_test 的目录
  8.  
  9. 2017/07/11 16:58 <DIR> .
  10. 2017/07/11 16:58 <DIR>
  11. >>: pwd
  12. ..
  13. 2017/07/10 11:04 0 __init__.py
  14. 2017/07/11 16:58 711 客户
  15. >>: pwd
  16. 端.py
  17. 2017/07/11 16:03 1,992 服务端.py
  18. 3 个文件 2,703 字节
  19.  
  20. >>: pwd
  21. 2 个目录 42,335,735,808 可用字节
  22. 'pwd' 不是内部或外部命令,也不是可运行的程序
  23. 或批处
  24. >>:

对比没动手脚之前:

  1. >>: dir
  2. 驱动器 C 中的卷没有标签。
  3. 卷的序列号是 5E42-F448
  4.  
  5. C:\Users\Mr.chai\Desktop\PythonProject\笔\2017.7.10\套接字_test 的目录
  6.  
  7. 2017/07/11 17:02 <DIR> .
  8. 2017/07/11 17:02 <DIR> ..
  9. 2017/07/10 11:04 0 __init__.py
  10. 2017/07/11 17:02 712 客户端.py
  11. 2017/07/11 16:03 1,992 服务端.py
  12. 3 个文件 2,704 字节
  13. 2 个目录 42,335,076,352 可用字节
  14.  
  15. >>: pwd
  16. 'pwd' 不是内部或外部命令,也不是可运行的程序
  17. 或批处理文件。
  18.  
  19. >>:

What happened?

发生了什么事?原因是这个样子。。

首先是socket数据传送和数据接收的原理:

  1. 发送端可以是一KK地发送数据,而接收端的应用程序可以两KK地提走数据,当然也有可能一次提走3K6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
  2.  
  3. 例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
  4.  
  5. 所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
  6.  
  7. 此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
  8.  
  9. TCPtransport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  10. UDPuser datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  11. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
  12. udprecvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
  13.  
  14. tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

原理

粘包只会出现在TCP里

UDP在windows下会提示:

  OSError: [WinError 10040] 一个在数据报套接字上发送的消息大于内部消息缓冲区或其他一些网络限制,或该用户用于接收数据报的缓冲区比数据报小

而在linux下会出现丢数据的情况:

  1. >>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  2. AAAAAAAAAA ('192.168.1.10', 8080)
  3. >>

问题出现在接收方,这是因为接收方不知道返回的消息之间的界限,不知道一次性提取多少字节的数据所造成的,第一次dir返回的消息远远大于100个字节,而懂了手脚后变成了一次只能从缓存中取100字节,再次取的时候会继续取缓存中没取完的数据

出现粘包的情况:

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合到一起,产生粘包)

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

  1. send(字节流)和recv(1024)及sendall
  2. recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
  3. send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

send(字节流)和recv(1024)及sendall

解决粘包的lowB方法

粘包的根源是接收端不知道发送端将要传送的字节流的长度,那么接收端提前把自己要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

服务端

  1. import socket
  2. import subprocess
  3. ip_addr=('127.0.0.1',8088)
  4. BUFSIZE=1024
  5. s_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  6. s_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
  7. s_server.bind(ip_addr)
  8. s_server.listen(5)
  9. print('run server...')
  10.  
  11. while True:
  12. conn,addr=s_server.accept()
  13. print('客户端地址:',addr)
  14. while True:
  15. try:
  16. client_res=conn.recv(BUFSIZE)
  17. if len(client_res.decode('utf-8')) == 0:continue
  18. res=subprocess.Popen(client_res.decode('utf-8'),
  19. shell=True,
  20. stdout=subprocess.PIPE,
  21. stderr=subprocess.PIPE)
  22. stdout=res.stdout.read()
  23. stderr=res.stderr.read()
  24. std_bytes=stdout+stderr #标准输出和标准错误组合
  25. std_size=len(std_bytes) #计算总长度
  26. conn.send(str(std_size).encode('utf-8')) #将总长度发给客户端,客户端收到该消息返回一个状态
  27. status=conn.recv(BUFSIZE).decode('utf-8') #将返回来的状态赋值
  28. if status: #如果该状态成立,那么开始发送所有数据
  29. conn.send(std_bytes)
  30. except Exception:
  31. break
  32. conn.close()
  33. s_server.close()

客户端

  1. import socket
  2. ip_addr=('127.0.0.1',8088)
  3. BUFSIZE=100
  4. s_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  5. s_client.connect(ip_addr)
  6. while True:
  7. cmd=input('>>').strip()
  8. if not cmd:continue
  9. s_client.send(cmd.encode('utf-8'))
  10.  
  11. std_size=int(s_client.recv(BUFSIZE).decode('utf-8')) #将接收的数据总长度转换成数字
  12. s_client.send('True'.encode('utf-8')) #返回给服务器端一个状态True
  13. res=b''
  14. get_size=0
  15. while get_size < std_size:
  16. if (std_size-get_size) < 100: #如果总长度比下载的长度小于定义的100,那么就取数据的最小值,否则按100取值
  17. res+=s_client.recv(std_size-get_size)
  18. else:
  19. res+=s_client.recv(BUFSIZE)
  20. get_size+=BUFSIZE #每取一次值加100,最后一次的值肯定大于总长度
  21. print(res.decode('gbk'))
  22. s_client.close()

解决粘包的高大上方法-自定义数据头

该方法是基于上面方法的改良,即在传输数据之前,在服务器端定一个固定长度数据头部,该数据头部封装了一系列关于该数据的信息,如数据的总长度,或者传输文件数据的用户信息、时间信息等等,客户端取得整个数据的时候,先取固定长度的数据头部读取信息,按照头部信息接收数据

待补充

Python开发基础-Day24socket套接字基础2的更多相关文章

  1. Python开发基础-Day23try异常处理、socket套接字基础1

    异常处理 错误 程序里的错误一般分为两种: 1.语法错误,这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正 2.逻辑错误,人为造成的错误,如数据类型错误.调用方法错误等,这些解 ...

  2. python基础之try异常处理、socket套接字基础part1

    异常处理 错误 程序里的错误一般分为两种: 1.语法错误,这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正 2.逻辑错误,人为造成的错误,如数据类型错误.调用方法错误等,这些解 ...

  3. Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令.

    Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令. 一丶socket套接字 什么是socket套接字: ​ ​  ​ 专业理解: socket是应用层与TCP/IP ...

  4. Python网络编程——处理套接字错误

    在网络应用中,经常会遇到这种情况:一方尝试连接,但另一方由于网络媒介失效或者其他原因无法响应. Python的Socket库提供了一个方法,能通过socket.error异常优雅地处理套接字错误. 1 ...

  5. Python中利用原始套接字进行网络编程的示例

    Python中利用原始套接字进行网络编程的示例 在实验中需要自己构造单独的HTTP数据报文,而使用SOCK_STREAM进行发送数据包,需要进行完整的TCP交互. 因此想使用原始套接字进行编程,直接构 ...

  6. python基础(29):网络编程(软件开发架构、网络基础、套接字初使用)

    1. 软件开发架构 我们了解的程序之间通讯的应用可分为两种: 第一种是应用类:qq.微信.百度网盘.腾讯视频这一类是属于需要安装的桌面应用. 第二种是web类:比如百度.知乎.博客园等使用浏览器访问就 ...

  7. python基础之socket套接字基础part2

    基于UDP的socket 面向无连接的不可靠数据传输,可以没有服务器端,只不过没有服务器端,发送的数据会被直接丢弃,并不能到达服务器端 1 #客户端 2 import socket 3 ip_port ...

  8. [linux basic基础]----套接字

    套接字是一种通信机制,凭借这种机制client/server系统的开发者既可以在本地机器上进行,也可以跨网络进行. 1,服务器应用程序用系统调用socket来创建一个套接字,他是系统分配给服务器进程的 ...

  9. 异步套接字基础:select函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

    参考:[原创]技术系列之 网络模型(三)多路复用模型 select函数 select函数: 系统提供select函数来实现多路复用输入/输出模型.原型: #include <sys/time.h ...

随机推荐

  1. Maven-Optional Dependencies & Dependency Exclusion

    本文讨论可选依赖和排除依赖.  帮助用户理解它们是什么, 如何使用, 它们如何工作, 以及什么时候使用它们最合适. 本文也将解释为什么排除是基于单个依赖的, 而非POM级别的. Optional De ...

  2. 「6月雅礼集训 2017 Day8」infection

    [题目大意] 有$n$个人,每个人有一个初始位置$x_i$和一个速度$v_i$,你需要选择若干个人来感染一个傻逼病毒. 当两个人相遇(可以是正面和背面),傻逼病毒会传染,求经过无限大时间后,传染完所有 ...

  3. [Unity]用PropertyDrawer自定义struct/class的外观

    一般来说,当我们要扩展编辑器时,我们会从Editor类继承,为自己的MonoBehaviour实现不同的外观. 但是如果有一个struct/class,在许多地方被使用,Unity默认的外观又不够好看 ...

  4. 联系博主 Contact

    李莫 / Ray OI 蒟蒻一只 / A Player of Olympiad in Informatics QQ:740929894 邮箱 / Email :rayking2017@outlook. ...

  5. MSSQL 数据库性能优化

    优化数据库的注意事项: 1.关键字段建立索引. 2.使用存储过程,它使SQL变得更加灵活和高效. 3.备份数据库和清除垃圾数据. 4.SQL语句语法的优化. 5.清理删除日志. SQL语句优化的基本原 ...

  6. Sqlmap使用教程

    sqlmap也是渗透中常用的一个注入工具,其实在注入工具方面,一个sqlmap就足够用了,只要你用的熟,秒杀各种工具,只是一个便捷性问题,sql注入另一方面就是手工党了,这个就另当别论了. 今天把我一 ...

  7. hdu 1272 小希的迷宫(并查集+最小生成树+队列)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1272 小希的迷宫 Time Limit: 2000/1000 MS (Java/Others)     ...

  8. spring boot web 入门

    ① 新建一个maven项目. ② pom中parent设为 spring-boot-starter-parent .建议使用最新的 RELEASE 版本.否则可能需要设置 <repositori ...

  9. Bagging和Boosting 概念及区别(转)

    Bagging和Boosting都是将已有的分类或回归算法通过一定方式组合起来,形成一个性能更加强大的分类器,更准确的说这是一种分类算法的组装方法.即将弱分类器组装成强分类器的方法. 首先介绍Boot ...

  10. 安装FFMpeg CentOS 7

    https://linuxadmin.io/install-ffmpeg-on-centos-7/