Python菜鸟之路:Python基础-Socket基础-1
预热知识
OSI 七层模型
谈到TCP/IP,就不得不说OSI七层模型,OSI 是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架,图示如下:
各个层次的详细说明,可以阅读百度词条http://baike.baidu.com/link?url=Agh556r2NM_rG7Yi4eV3UrxpkTBmpVU4eRrZSBpUf3HTF1xFcwkoh5AgfAyo5YqIbDosIuhhxh4v-dE4zLgC0ZqQwEn_5JGJl325CZYWYui6xdLMtrDc9b4JuXGQ_SLSjf6X4z6nzAt1uUAdrpwcbTUo_nciq4SoSPgBS__ZatfudORRkwseD_cO5CkRaqH2
TCP/IP 四层模型
应用层:应用程序间沟通的层,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。
传输层:在此层中,它提供了节点间的数据传送服务,如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。
网络层:负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收),如网际协议(IP)。
链路层:也叫网络接口层。对实际的网络媒体的管理,定义如何使用实际网络(如Ethernet、Serial Line等)来传送数据。
数据包、数据帧
“包”(Packet)是TCP/IP协议通信传输中的数据单位,一般也称“数据包”。但是TCP/IP协议是工作在OSI模型第三层(网络层)、第四层(传输层)上的,而帧是工作在第二层(数据链路层)。上一层的内容由下一层的内容来传输,所以在局域网中,“包”是包含在“帧”里的。
“帧”数据由两部分组成:帧头和帧数据。帧头包括接收方主机物理地址的定位以及其它网络信息。帧数据区含有一个数据体。为确保计算机能够解释数据帧中的数据,这两台计算机使用一种公用的通讯协议。互联网使用的通讯协议简称IP,即互联网协议。IP数据体由两部分组成:数据体头部和数据体的数据区。数据体头部包括IP源地址和IP目标地址,以及其它信息。数据体的数据区包括用户数据协议(UDP),传输控制协议(TCP),还有数据包的其他信息。这些数据包都含有附加的进程信息以及实际数据。
MTU
最大传输单元(Maximum Transmission Unit,MTU)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等)。如果IP层有一个数据包要传,而且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分成托干片,让每一片都不超过MTU。 如下是几种常见的MTU:
TCP标志位
在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.其中,对于我们日常的分析有用的就是前面的五个字段。它们的含义是:SYN表示建立连接,FIN表示关闭连接,ACK表示响应,PSH表示有 DATA数据传输,RST表示连接重置。其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同时为1,它表示的就是建立连接之后的响应,如果只是单个的一个SYN,它表示的只是建立连接。TCP的几次握手就是通过这样的ACK表现出来的。但SYN与FIN是不会同时为1的,因为前者表示的是建立连接,而后者表示的是断开连接。RST一般是在FIN之后才会出现为1的情况,表示的是连接重置。一般地,当出现FIN包或RST包时,我们便认为客户端与服务器端断开了连接;而当出现SYN和SYN+ACK包时,我们认为客户端与服务器建立了一个连接。PSH为1的情况,一般只出现在 DATA内容不为0的包中,也就是说PSH为1表示的是有真正的TCP数据包内容被传递。TCP的连接建立和连接关闭,都是通过请求-响应的模式完成的。
TCP三次握手四次断开
本地的进程间通信(IPC)可以通过以下方式:
linux下进程间通信的几种主要手段简介:
- 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
- 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
- 远程过程调用
- 通过/proc 下的某些目录
而要实现网络中进程间通信,首先要解决的是如何标识唯一进程:网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
Socket编程基础
什么是socket?
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。因此可以理解为Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket流程
一个完整的socket server的建立
在Python中,socket建立可以使用内置的socket模块来实现,通常分为以下步骤:
- 创建socket对象
- 绑定本地地址+端口
- 监听本地端口
- 等待链接(阻塞的)
- 应答(非必须)、关闭客户端链接(非必须)
- 关闭socket
代码如下:
import socket
# 创建socket对象
s = socket.socket()
ip_port = ('127.0.0.1', 9999)
# 绑定本地IP+端口
s.bind(ip_port)
# 监听本地地址
s.listen(5)
# 等待客户端请求
conn, addr = s.accept()
# 接收客户端请求或数据
recv_data = conn.recv(1024)
# 应答客户端(非必须)
conn.send(send_data)
# 关闭客户端链接
conn.close()
# 关闭socket
s.close()
一个socket client的建立
client去连接socket server,通常包含以下步骤:
- 创建socket对象
- 连接socket server地址
- 数据交互
- 断开连接
代码如下:
import socket
# 创建socket对象
s = socket.socket()
ip_port = ('127.0.0.1', 9999) # 连接socket server,该过程connect 不阻塞
s.connect(ip_port) # 数据交互(发)
s.send(bytes('请求内容'), encoding='utf8')
# 数据交互(收)
s.recv(1024)
# 断开连接
s.close()
Socket模块的用法
s=socket.socket() # socket.socket()创建socket s.bind() # 绑定地址到套接字
s.listen() # 开始TCP监听
s.accept() # 被动接受TCP客户端连接,等待连接的到来
s.connect() # 主动初始化TCP服务器连接
s.connect_ex() # connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
s.recv() # 接收TCP数据
s.send() # 发送TCP数据
s.sendall() # 完整发送TCP数据
s.recvfrom() # 接收UDP数据
s.sendto() # 发送UDP数据
s.getpeername() # 连接到当前套接字的远端的地址(TCP连接)
s.getsockname() # 当前套接字的地址
s.getsockopt() # 返回指定套接字的参数
s.setsockopt() # 设置指定套接字的参数
s.close() # 关闭套接字
s.setblocking() # 设置套接字的阻塞与非阻塞模式
s.settimeout() # 设置阻塞套接字操作的超时时间
s.gettimeout() # 得到阻塞套接字操作的超时时间
s.filen0() # 套接字的文件描述符
s.makefile() # 创建一个与该套接字关联的文件对象 socket.AF_UNIX # 只能够用于单一的Unix系统进程间通信
socket.AF_INET # 服务器之间网络通信
socket.AF_INET6 # IPv6 socket.SOCK_STREAM # 流式socket , for TCP
socket.SOCK_DGRAM # 数据报式socket , for UDP
socket.SOCK_RAW # 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM # 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET # 可靠的连续数据包服务 socket的方法
粘包
什么是粘包?
指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
造成粘包的原因?
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。(http://zgc168.iteye.com/blog/1880620)
小结:
1 发送端需要等缓冲区满才发送出去,造成粘包
2 接收方不及时接收缓冲区的包,造成多个包接收
什么时候不需要考虑粘包?
如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题
如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
如何解决粘包?
在头加一个数据长度之类的包,以确保接收。
代码案例
client端代码,核心在24-28行。
import socket
import os ,json
ip_port=('192.168.11.150',8009)
#买手机
s=socket.socket()
#拨号
s.connect(ip_port)
#发送消息
welcome_msg = s.recv(1024)
print("from server:",welcome_msg.decode())
while True:
send_data=input(">>: ").strip()
if len(send_data) == 0:continue cmd_list = send_data.split()
if len(cmd_list) <2:continue
task_type = cmd_list[0]
if task_type == 'put':
abs_filepath = cmd_list[1]
if os.path.isfile(abs_filepath):
file_size = os.stat(abs_filepath).st_size
filename = abs_filepath.split("\\")[-1]
print('file:%s size:%s' %(abs_filepath,file_size))
msg_data = {"action":"put",
"filename":filename,
"file_size":file_size}
# 在发送数据之前,先发送本次发送的数据包信息,关键字是 file_size
s.send( bytes(json.dumps(msg_data),encoding="utf-8") )
server_confirmation_msg = s.recv(1024)
confirm_data = json.loads(server_confirmation_msg.decode())
if confirm_data['status'] ==200: print("start sending file ",filename)
f = open(abs_filepath,'rb')
for line in f:
s.send(line) print("send file done ") else:
print("\033[31;1mfile [%s] is not exist\033[0m" % abs_filepath)
continue
else:
print("doesn't support task type",task_type)
continue
#s.send(bytes(send_data,encoding='utf8'))
#收消息
recv_data=s.recv(1024)
print(str(recv_data,encoding='utf8'))
#挂电话
s.close()
server端代码,关键点在28行。
import socketserver,json
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
self.request.sendall(bytes('欢迎致电 10086,请输入1xxx,0转人工服务.',encoding="utf-8"))
while True:
data = self.request.recv(1024)
if len(data) == 0:break
print("data", data)
print("[%s] says:%s" % (self.client_address,data.decode() )) task_data = json.loads( data.decode() )
task_action = task_data.get("action")
if hasattr(self, "task_%s"%task_action):
func = getattr(self,"task_%s" %task_action)
func(task_data)
else:
print("task action is not supported",task_action) def task_put(self,*args,**kwargs):
print("---put",args,kwargs)
filename = args[0].get('filename')
filesize = args[0].get('file_size')
server_response = {"status":200}
self.request.send(bytes( json.dumps(server_response), encoding='utf-8' ))
f = open(filename,'wb')
recv_size = 0
# 接收到客户端发来的数据包文件大小,然后进行循环接收,直至数据包刚好接收完毕
while recv_size < filesize:
data = self.request.recv(4096)
f.write(data)
recv_size += len(data)
print('filesize: %s recvsize:%s' % (filesize,recv_size))
print("file recv success")
f.close() if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('0.0.0.0',8009),MyServer)
server.serve_forever()
Python菜鸟之路:Python基础-Socket基础-1的更多相关文章
- Python菜鸟之路:前端HTML基础
前面的章节中,Python的基本知识已经差不多介绍完了.本节介绍HTML相关的知识.需要着重声明的是,前端知识是非常非常重要的知识,以我实际项目经验来看,一个项目的瓶颈在设计和前端.设计就先不说了,前 ...
- Python菜鸟之路:Django 路由补充1:FBV和CBV - 补充2:url默认参数
一.FBV和CBV 在Python菜鸟之路:Django 路由.模板.Model(ORM)一节中,已经介绍了几种路由的写法及对应关系,那种写法可以称之为FBV: function base view ...
- python 自动化之路 day 19 Django基础[二]
Django - 路由系统 url.py - 视图函数 views.py - 数据库操作 models.py - 模板引擎渲染 - HttpReponse(字符串) - render(request, ...
- Python菜鸟之路:Django Admin后台管理功能使用
前言 用过Django框架的童鞋肯定都知道,在创建完Django项目后,每个app下,都会有一个urls.py文件,里边会有如下几行: from django.contrib import admin ...
- Python菜鸟之路:Django 路由、模板、Model(ORM)
Django路由系统 Django的路由系统让Django可以根据URI进行匹配,进而发送至特定的函数去处理用户请求.有点类似nginx的location功能. Django的路由关系分为三种:普通关 ...
- 我的Python学习之路 Python的输入输出与基本数据类型
*** python中的变量不需要事先声明再使用,而可以直接来一个变量名,后面一个赋值,接着一个数据值,如 hw = "hello python",相当于Python能智能的根据你 ...
- 我的Python学习之路 Python的初识与准备工作
注:文笔不好,不喜勿喷,当个段子看看就好 一.初识Python 第一次听到Python是在2016年大概暑假 时候(即将大三),因为对黑客技术的蜜汁热爱(虽然自己并不会),在玄魂大大的公众微信号中看到 ...
- Python菜鸟之路:Python基础-Socket编程-2
在上节socket编程中,我们介绍了一些TCP/IP方面的必备知识,以及如何通过Python实现一个简单的socket服务端和客户端,并用它来解决“粘包”的问题.本章介绍网络编程中的几个概念:多线程. ...
- Python菜鸟之路:Python基础-操作缓存memcache、redis
一.搭建memcached和redis 略,自己去百度吧 二.操作Mmecached 1. 安装API python -m pip install python-memcached 2. 启动memc ...
随机推荐
- 转: 初识Agile/CMMI/Scrum
转:http://www.cnblogs.com/maxwell/p/5093917.html 一.背景介绍 在朋友(aehyok)的建议下,初步去了解Visual Studio Online,简称V ...
- 比特币Bitcoin-qt客户端加密前后如何导入导出私钥?
一.Bitcoin-qt客户端加密后 如需要导出某一地址对应的私钥,需要先调用 walletpassphrase 密码 解锁持续时间(秒), 如:walletpassphrase h123456789 ...
- 转: 使用 Velocity 模板引擎快速生成代码
from:https://www.ibm.com/developerworks/cn/java/j-lo-velocity1/ 评注: 1. velocity 的基本语法 2. 生成代码的用法.
- elasticsearch 插入数据
1.单条插入(推荐设定主键id防止重复) public static String addIndex(String index,String type,HashMap<String, Objec ...
- [Functional Programming] Compose Simple State ADT Transitions into One Complex Transaction
State is a lazy datatype and as such we can combine many simple transitions into one very complex on ...
- ContentProvider之通过ContentResolver获取图像、视频、音频举例
MediaStore中定义了一系列的数据表格,通过ContentResolver提供的查询接口,我们能够得到各种须要的媒体信息.通过下面两个URI能够扫描设备外部和内部的媒体文件.Android系统提 ...
- AngularJS, Ember.js, Backbone这类新框架与 jQuery的重要区别在哪里?
jQuery主要是用来操作DOM的,如果单单说jQuery的话就是这样一个功能,它的插件也比较多,大家也都各自专注一个功能,可以说jQuery体系是跟着前端页面从静态到动态崛起的一个产物,他的作用就是 ...
- C#如何改变字符串编码
public string UTF8ToGB2312(string str) { try { Encod ...
- 微信小程序 - 关于下拉刷新
// 拉取数据 fetchData: function() { wx.request({ url: 'http://v.juhe.cn/toutiao/index', data: { type: '' ...
- GROUP BY和HAVING 以及mysql中常用的日期函数
一.mysql中的GROUP BY和HAVINGGROUP BY常见的是和聚合函数(SUM,MIN,MAX,COUNT)搭配使用. 比如:SELECT category,SUM(money) AS ` ...