一、基于TCP协议的socket通信

以打电话为理解方式进行TCP的通信。

Server端代码:

  1. import socket
  2. phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #购买电话卡,AF_INET服务器之间网络通信,socket.SOCK_STREAM,流式协议,就是TCP协议
  3. phone.bind(('127.0.0.1', 8080)) #选择电话号码,绑定IP地址
  4. phone.listen(5) #开机,监控5个请求,最多能进入6个,第7个会报错
  5. conn, addr = phone.accept() #等待接听电话,阻塞状态,获取IP和端口
  6. print(conn, addr) #打印IP和端口
  7. client_data = conn.recv(1024) #交流过程
  8. print(client_data) #打印接收到的数据
  9. conn.send(client_data.upper()) #接收到的英文字母变成大写并回传数据
  10. conn.close()
  11. phone.close()

Client端代码:

  1. import socket
  2. phone = socket.socket()
  3. phone.connect(('127.0.0.1', 8080))
  4. msg = input('>>>').strip()
  5. phone.send(msg.encode('utf-8'))
  6. server_data = phone.recv(1024) #限制最大接收字节
  7. print(server_data)
  8. phone.close()

先运行Server端,再运行Client端,代码运行结果:

  1. ------------------------------------Client端运行结果---------------------------------------
  2. >>>Others laugh at me for being mad. I laugh at others for not being able to see through. #>>>符号右侧为输入内容
  3. b'OTHERS LAUGH AT ME FOR BEING MAD. I LAUGH AT OTHERS FOR NOT BEING ABLE TO SEE THROUGH.' #为服务端返回内容
  4. ------------------------------------------------------------------------------------------
  5.  
  6. ------------------------------------Server端运行结果----------------------------------------
  7. <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 64096)> ('127.0.0.1', 64096)
  8. b'Others laugh at me for being mad. I laugh at others for not being able to see through.' #接收到客户端的内容
  9. -------------------------------------------------------------------------------------------

二、单循环模式

Server端代码:

  1. import socket
  2. phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #使用TCP协议创建网络通信
  3. phone.bind(('127.0.0.1', 8080)) #绑定IP地址和端口
  4. phone.listen(5) #设置最大连接数,但是同一时间只能处理一个请求
    while 1: #使服务端能够一直接受数据
  5. conn, addr = phone.accept() #获取连接到服务器的IP和端口
  6. try:
  7. client_data = conn.recv(1024) #接收1024字节的数据
  8. print(client_data) #打印数据(数字为bytes类型)
  9. conn.send(client_data + b'-yes-') #源数据加上-yes-并返回给客户端
  10. except Exception:
  11. break
  12. conn.close()
  13. phone.close()

Client端代码:

  1. import socket
  2. phone = socket.socket()
  3. phone.connect(('127.0.0.1', 8080))
  4.  
  5. while 1:
  6. msg = input('>>>').strip() #可输入要传输的字符
  7. if msg.upper() == 'Q': #如果输出q则退出
  8. break
  9. elif not msg: #如果msg接到到的内容为空,则结束本次循环
  10. continue
  11. phone.send(msg.encode('utf-8')) #使用utf-8的编码进行发送数据
  12. server_data = phone.recv(1024) #最多只能接受1024字节,只问题会产生粘包
  13. print(server_data.decode('utf-8')) #打印解码后的数据
  14. phone.close()

先运行Server端,再运行Client端,代码运行结果:

  1. ------------------------------------Client端运行结果---------------------------------------
  2. >>>laugh #输入要传到S端的内容
  3. laugh-yes- #打印出来的是S端返回的内容
  4. >>>crazy
  5. crazy-yes-
  6. >>>q
  7.  
  8. Process finished with exit code 0
  9. -------------------------------------------------------------------------------------------
  1. ------------------------------------Server端运行结果----------------------------------------
  1. b'laugh' #收到C端发送来的内容
    b'crazy'
  1. -------------------------------------------------------------------------------------------

三、远程执行命令

3.1 远程执行命令之粘包现象的产生,示例如下:

Server端代码:

  1. import socket
  2. import subprocess #远程执行命令需要使用到subprocess模块
  3. phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  4. phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket
  5. phone.bind(('127.0.0.1', 8080))
  6. phone.listen(5)
  7. while 1:
  8. conn, addr = phone.accept()
  9. try:
  10. cmd = conn.recv(1024)
  11. obj = subprocess.Popen(cmd.decode('utf-8'), #要执行的命令或可执行文件的路径。一个由字符串组成的序列(通常是列表),列表的第一个元素是可执行程序的路径,\
  12. #剩下的是传给这个程序的参数,如果没有要传给这个程序的参数,args 参数可以仅仅是一个字符串。
  13. shell=True, #Linux下相当于执行/bin/bash,Windows下相当于执行cmd.exe
  14. stdout=subprocess.PIPE, #指定子进程的标准输出
  15. stderr=subprocess.PIPE, #指定子进程的标准错误输出
  16. )
  17. ret = obj.stdout.read() #把标准的输出结果赋值给ret变量
  18. ret1 = obj.stderr.read() #把标准的错误输出结果赋值给ret1变量
  19. conn.send(ret + ret1)
  20. except Exception:
  21. break
  22. conn.close()
  23. phone.close()

Client端代码:

  1. import socket
  2. phone = socket.socket()
  3. phone.connect(('127.0.0.1', 8080))
  4. while 1:
  5. msg = input('>>>').strip()
  6. if msg.upper() == 'Q': #输出q则退出程序
  7. break
  8. elif not msg:
  9. continue #解决的是输入为空程序会夯住的问题。
  10. phone.send(msg.encode('utf-8')) #将接收到的数据进行编码后发送到服务端
  11. server_data = phone.recv(1024) #每次只接收1024个字节
  12. print(server_data.decode('gbk')) #打印接收数据
  13. phone.close()

先运行Server端,再运行Client端,代码运行结果:

  1. ------------------------------------Client端运行结果---------------------------------------
  2. >>>ipconfig /all #查看机器所有的IP地址
  3.  
  4. Windows IP 配置
  5.  
  6. 主机名 . . . . . . . . . . . . . : WINDOWS-CP2ICQS
  7. DNS 后缀 . . . . . . . . . . . :
  8. 节点类型 . . . . . . . . . . . . : 混合
  9. IP 路由已启用 . . . . . . . . . . :
  10. WINS 代理已启用 . . . . . . . . . :
  11.  
  12. 以太网适配器 本地连接:
  13.  
  14. 连接特定的 DNS 后缀 . . . . . . . :
  15. 描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
  16. 物理地址. . . . . . . . . . . . . : ---D1-E1-8B
  17. DHCP 已启用 . . . . . . . . . . . :
  18. 自动配置已启用. . . . . . . . . . :
  19. 本地链接 IPv6 地址. . . . . . . . : fe80::91d8:554a:b7fb:f16e%(首选)
  20. IPv4 地址 . . . . . . . . . . . . : 192.168.77.77(首选)
  21. 子网掩码 . . . . . . . . . . . . : 255.255.255.0
  22. 默认网关. . . . . . . . . . . . . : 192.168.77.1
  23. DHCPv6 IAID . . . . . . . . . . . :
  24. DHCPv6 客户端 DUID . . . . . . . : -----DE-4B-----D1-E1-8B
  25. DNS 服务器 . . . . . . . . . . . : 114.114.114.114
  26. TCPIP 上的 NetBIOS . . . . . . . : 已启用
  27.  
  28. 隧道适 #到此时数据明显没有接收全,因为每次只接收1024个字节
  29. >>>dir #直到发送下一个命令的时候,才把剩余的字节接收完成,这就是粘包现象之一
  30. 配器 isatap.{D3F0159A-0A26--9B2A-92BE10466E18}: #dir命令并未列出当前Windows目录的内容
  31.  
  32. 媒体状态 . . . . . . . . . . . . : 媒体已断开
  33. 连接特定的 DNS 后缀 . . . . . . . :
  34. 描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
  35. 物理地址. . . . . . . . . . . . . : -------E0
  36. DHCP 已启用 . . . . . . . . . . . :
  37. 自动配置已启用. . . . . . . . . . :
  38.  
  39. 隧道适配器 Teredo Tunneling Pseudo-Interface:
  40.  
  41. 媒体状态 . . . . . . . . . . . . : 媒体已断开
  42. 连接特定的 DNS 后缀 . . . . . . . :
  43. 描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
  44. 物理地址. . . . . . . . . . . . . : -------E0
  45. DHCP 已启用 . . . . . . . . . . . :
  46. 自动配置已启用. . . . . . . . . . :
  47.  
  48. >>>q
  49.  
  50. Process finished with exit code
  51. -------------------------------------------------------------------------------------------

3.2 什么是粘包:

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

3.3 以下两种情况发发生粘包(只有tcp协议会产生粘包):

1,接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
2,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)

四、解决粘包的方法

4.1 使用struct模块

该模块可以把一个类型,如数字转换成固定长度的bytes

  1. import struct
  2. ret = struct.pack('i', ) #将一个int型数字转化成等长度的bytes类型。
  3. print(ret, type(ret), len(ret))
  4. ----------------------------------------运行结果-------------------------------------------
  5. b'2\xcc\x02\x00' <class 'bytes'>
  6. ------------------------------------------------------------------------------------------
  7.  
  8. ret1 = struct.unpack('i', ret)[] #通过unpack反解回来
  9. print(ret1, type(ret1))
  10. ----------------------------------------运行结果-------------------------------------------
  11. <class 'int'>
  12. ------------------------------------------------------------------------------------------
  13.  
  14. ret = struct.pack('l', ) # 但是通过struct 处理不能处理太大
  15. print(ret, type(ret), len(ret)) # 报错
  16. ----------------------------------------运行结果-------------------------------------------
  17. File "D:/PycharmProjects/python/pack.py", line , in <module>
  18. ret = struct.pack('l', )
  19. struct.error: argument out of range
  1. ------------------------------------------------------------------------------------------

4.2 方案代码如下:

  1. 整个流程的大致解释:
  2. 我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
  3. 我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下
  4.  
  5. 发送时:
  6. 先发报头长度
  7. 再编码报头内容然后发送
  8. 最后发真实内容
  9.  
  10. 接收时:
  11. 先收报头长度,用struct取出来
  12. 根据取出的长度收取报头内容,然后解码,反序列化
  13. 从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容

server端代码:

  1. import socket
  2. import subprocess
  3. import struct
  4. import json
  5.  
  6. phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  7. phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  8. phone.bind(('127.0.0.1', 8080))
  9. phone.listen(5)
  10. conn, addr = phone.accept()
  11. while 1:
  12. try:
  13. cmd = conn.recv(1024)
  14. obj = subprocess.Popen(cmd.decode('utf-8'),
  15. shell=True,
  16. stdout=subprocess.PIPE,
  17. stderr=subprocess.PIPE,
  18. )
  19. ret = obj.stdout.read() #1.获取执行命令返回的结果,计算结果的总大小
  20. ret1 = obj.stderr.read()
  21. total_size = len(ret + ret1)
  22. head_dict = { #2.构建header字典
  23. 'filename': 'file.txt',
  24. 'md5': '',
  25. 'total_size': total_size,
  26. }
  27. dict_json = json.dumps(head_dict) #3.将字典转换json格式
  28. dict_bytes = dict_json.encode('utf-8') #4.将字典转化成bytes
  29. dict_len = len(dict_bytes) #5.计算字典bytes类型的大小
  30. head_dict_len = struct.pack('i', dict_len) #6.将bytes类型的 字典的长度转化成固定长度的bytes
  31. conn.send(head_dict_len) #7.发送字典的总大小
  32. conn.send(dict_bytes) #8.发送 bytes类型的字典
  33. conn.send(ret) #9.发送数据部分。
  34. conn.send(ret1)
  35. except Exception:
  36. break
  37. conn.close()
  38. phone.close()

Client端代码:

  1. import socket
  2. import struct
  3. import json
  4.  
  5. phone = socket.socket()
  6. phone.connect(('127.0.0.1', 8080))
  7. while 1:
  8. msg = input('>>>').strip()
  9. if msg.upper() == 'Q':
  10. break
  11. elif not msg:
  12. continue
  13. phone.send(msg.encode('utf-8'))
  14. dict_size = struct.unpack('i', phone.recv(4))[0] #1.接收字典的大小。
  15. header_dict_json = phone.recv(dict_size).decode('utf-8') #2.获取报头字典json格式。
  16. header_dict = json.loads(header_dict_json) #3.通过json 反解成 字典
  17. data_size = 0 #4.接收数据部分。
  18. res = b''
  19. while data_size < header_dict['total_size']:
  20. data = phone.recv(1024)
  21. res = res + data
  22. data_size = data_size + len(data)
  23. print(res.decode('gbk'))
  24. phone.close()

先运行Server端,再运行Client端,代码运行结果:

  1. >>>ipconfig /all
  2.  
  3. Windows IP 配置
  4.  
  5. 主机名 . . . . . . . . . . . . . : WINDOWS-CP2ICQS
  6. DNS 后缀 . . . . . . . . . . . :
  7. 节点类型 . . . . . . . . . . . . : 混合
  8. IP 路由已启用 . . . . . . . . . . :
  9. WINS 代理已启用 . . . . . . . . . :
  10.  
  11. 以太网适配器 本地连接:
  12.  
  13. 连接特定的 DNS 后缀 . . . . . . . :
  14. 描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
  15. 物理地址. . . . . . . . . . . . . : 20-89-84-D1-E1-8B
  16. DHCP 已启用 . . . . . . . . . . . :
  17. 自动配置已启用. . . . . . . . . . :
  18. 本地链接 IPv6 地址. . . . . . . . : fe80::91d8:554a:b7fb:f16e%13(首选)
  19. IPv4 地址 . . . . . . . . . . . . : 192.168.77.77(首选)
  20. 子网掩码 . . . . . . . . . . . . : 255.255.255.0
  21. 默认网关. . . . . . . . . . . . . : 192.168.77.1
  22. DHCPv6 IAID . . . . . . . . . . . : 287345028
  23. DHCPv6 客户端 DUID . . . . . . . : 00-01-00-01-21-DE-4B-33-20-89-84-D1-E1-8B
  24. DNS 服务器 . . . . . . . . . . . : 114.114.114.114
  25. TCPIP 上的 NetBIOS . . . . . . . : 已启用
  26.  
  27. 隧道适配器 isatap.{D3F0159A-0A26-4494-9B2A-92BE10466E18}:
  28.  
  29. 媒体状态 . . . . . . . . . . . . : 媒体已断开
  30. 连接特定的 DNS 后缀 . . . . . . . :
  31. 描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
  32. 物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
  33. DHCP 已启用 . . . . . . . . . . . :
  34. 自动配置已启用. . . . . . . . . . :
  35.  
  36. 隧道适配器 Teredo Tunneling Pseudo-Interface:
  37.  
  38. 媒体状态 . . . . . . . . . . . . : 媒体已断开
  39. 连接特定的 DNS 后缀 . . . . . . . :
  40. 描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
  41. 物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
  42. DHCP 已启用 . . . . . . . . . . . :
  43. 自动配置已启用. . . . . . . . . . :
  44.  
  45. >>>dir
  46. 驱动器 D 中的卷没有标签。
  47. 卷的序列号是 FCBB-EB88
  48.  
  49. D:\PycharmProjects\python\01.py 的目录
  50.  
  51. 2018/12/17 17:00 <DIR> .
  52. 2018/12/17 17:00 <DIR> ..
  53. 2018/12/17 16:11 581 su.py
  54. 2018/12/17 15:57 1,011 客户.py
  55. 2018/12/17 17:00 1,802 服务.py
  56. 3 个文件 3,394 字节
  57. 2 个目录 43,045,126,144 可用字节
  58.  
  59. >>>q
  60.  
  61. Process finished with exit code 0

Python socket网络模块的更多相关文章

  1. Python Socket 编程——聊天室示例程序

    上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...

  2. Python Socket 网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

  3. python socket发送魔法包网络唤醒开机.py

    python socket发送魔法包网络唤醒开机.py 现在的电脑应该都普遍支持有线网络的WOL了,支持无线网络唤醒的电脑,可能比较少. """ python socke ...

  4. Python socket编程之二:【struct.pack】&【struct.unpack】

    import struct """通过 socket 的 send 和 recv 只能传输 str 格式的数据""" "" ...

  5. Python Socket,How to Create Socket Server? - 网络编程实例

    文章出自:Python socket – network programming tutorial by Silver Moon 原创译文,如有版权问题请联系删除. Network programin ...

  6. Python Socket,How to Create Socket Cilent? - 网络编程实例

    文章出自:Python socket – network programming tutorial by Silver Moon 原创译文,如有版权问题请联系删除. Network programin ...

  7. Python Socket通信原理

    [Python之旅]第五篇(一):Python Socket通信原理   python Socket 通信理论 socket例子 摘要:  只要和网络服务涉及的,就离不开Socket以及Socket编 ...

  8. Python Socket单线程+阻塞模式

    Python之旅]第五篇(二):Python Socket单线程+阻塞模式 python Socket单线程 Socket阻塞模式 串行发送 摘要:  前面第五篇(一)中的一个Socket例子其实就是 ...

  9. python socket之tcp服务器与客户端demo

    python socket之tcp服务器与客户端demo 作者:vpoet mails:vpoet_sir@163.com server: # -*- coding: cp936 -*- ''' 建立 ...

随机推荐

  1. mvc4中viewbag viewdata 和 tempdata的区别

    ViewBag 不再是字典的键值对结构,而是 dynamic 动态类型,它会在程序运行的时候动态解析. eg: ViewBag.NumberObjs = new string[] { "on ...

  2. WOSign API

    [HttpGet] public ActionResult WoSign() { // System.IO.FileStream fs = System.IO.File.OpenRead(System ...

  3. PHPStorm自定义主题配置

    1.下载喜欢的主题 官方下载地址:下载 2.将.icls主题文件放到PHPStorm的配置中 windows下主题位置:C:\Users\Administrator\.PhpStorm2017.3\c ...

  4. JDK(一)JDK集合框架

    JDK中的集合框架分为两大类:Collection和Map.Collection以一组Object的形式保存元素,Map以Key-Value对的形式保存元素. 上图列出的类并不完整,只列举了平时比较常 ...

  5. echo图片延迟加载js

    插件描述:和 Lazy Load 一样,Echo.js 也是一个用于图像延迟加载 JavaScript.不同的是 Lazy Load 是基于 jQuery 的插件,而 Echo.js 不依赖于 jQu ...

  6. 客户端对象模型之列表数据导出到Excel

    1,废话不多话,直接上代码,留着以后做类似功能时Copy一下!有需要的朋友也可以参考一下. <!DOCTYPE html> <html xmlns="http://www. ...

  7. JNDI数据源(在Tomcat下配置JNDI多数据源实例)

    一,添加数据库驱动包加入classpath. 这里我用到了oracle和mysql.所以由两个jar包:ojdbc14.jar和mysql-connector-java-5.1.13-bin.jar. ...

  8. js获取今天,明天,本周五,下周五日期的函数

    代码比较简单,随便写写 /** * a连接快速选择日期函数 */ function timeChooseSimple(key, me) { //today,tomorrow,thisWeek,next ...

  9. NodeJ node.js基础

    因为是Node服务器端的,怎样实现前台和后台请求以及回应 URL(由什么组成的 ),传输的内容:表单数据  文件数据 [图片.压缩包.各种后缀文件] URL的组成 URL由三部分组成: 协议类型 , ...

  10. Xquery的初步学习(一次Lab作业的总结)

    Task 1: Open countries.xml, compose the following XQueries: 1. Return the area of Mongolia. 2. Retur ...