预热知识

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模块来实现,通常分为以下步骤:

  1. 创建socket对象
  2. 绑定本地地址+端口
  3. 监听本地端口
  4. 等待链接(阻塞的)
  5. 应答(非必须)、关闭客户端链接(非必须)
  6. 关闭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,通常包含以下步骤:

  1. 创建socket对象
  2. 连接socket server地址
  3. 数据交互
  4. 断开连接

代码如下:

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的更多相关文章

  1. Python菜鸟之路:前端HTML基础

    前面的章节中,Python的基本知识已经差不多介绍完了.本节介绍HTML相关的知识.需要着重声明的是,前端知识是非常非常重要的知识,以我实际项目经验来看,一个项目的瓶颈在设计和前端.设计就先不说了,前 ...

  2. Python菜鸟之路:Django 路由补充1:FBV和CBV - 补充2:url默认参数

    一.FBV和CBV 在Python菜鸟之路:Django 路由.模板.Model(ORM)一节中,已经介绍了几种路由的写法及对应关系,那种写法可以称之为FBV: function base view ...

  3. python 自动化之路 day 19 Django基础[二]

    Django - 路由系统 url.py - 视图函数 views.py - 数据库操作 models.py - 模板引擎渲染 - HttpReponse(字符串) - render(request, ...

  4. Python菜鸟之路:Django Admin后台管理功能使用

    前言 用过Django框架的童鞋肯定都知道,在创建完Django项目后,每个app下,都会有一个urls.py文件,里边会有如下几行: from django.contrib import admin ...

  5. Python菜鸟之路:Django 路由、模板、Model(ORM)

    Django路由系统 Django的路由系统让Django可以根据URI进行匹配,进而发送至特定的函数去处理用户请求.有点类似nginx的location功能. Django的路由关系分为三种:普通关 ...

  6. 我的Python学习之路 Python的输入输出与基本数据类型

    *** python中的变量不需要事先声明再使用,而可以直接来一个变量名,后面一个赋值,接着一个数据值,如 hw = "hello python",相当于Python能智能的根据你 ...

  7. 我的Python学习之路 Python的初识与准备工作

    注:文笔不好,不喜勿喷,当个段子看看就好 一.初识Python 第一次听到Python是在2016年大概暑假 时候(即将大三),因为对黑客技术的蜜汁热爱(虽然自己并不会),在玄魂大大的公众微信号中看到 ...

  8. Python菜鸟之路:Python基础-Socket编程-2

    在上节socket编程中,我们介绍了一些TCP/IP方面的必备知识,以及如何通过Python实现一个简单的socket服务端和客户端,并用它来解决“粘包”的问题.本章介绍网络编程中的几个概念:多线程. ...

  9. Python菜鸟之路:Python基础-操作缓存memcache、redis

    一.搭建memcached和redis 略,自己去百度吧 二.操作Mmecached 1. 安装API python -m pip install python-memcached 2. 启动memc ...

随机推荐

  1. 最简单简洁高效的Json数据解析

    一.无图无真相 二.主要代码 1.导入jar包 拷贝fastjson.jar包到projectlibs包下 2.封装工具类JsonUtil.java package com.example.parse ...

  2. Solr 配置文件之schema.xml

    schema.xml这个配置文件的根本目的是为了通过配置告诉Solr怎样建立索引. solr的数据结构例如以下: document:一个文档.一条记录 field:域.属性 solr通过搜索某个或某些 ...

  3. fiddler实现后端接口 mock(不需要修改开发代码)

    转载:http://blog.csdn.net/huazhongkejidaxuezpp/article/details/50435552 步骤   1.  获取 接口 定义(接口返回的json串) ...

  4. Rails 状态码

    Response Class HTTP StatusCode Symbol Informational 100 :continue Success 200 :ok Redirection 300 :m ...

  5. liunx下安装第三方Python(PIP安装)

    wget https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz $ tar zvxf pip-6.0.8.tar.gz $ cd ...

  6. Beautiful Soup 4.4.0 基本使用方法

    Beautiful Soup 4.4.0 基本使用方法Beautiful Soup 安装 pip install  beautifulsoup4 标准库有html.parser解析器但速度不是很快一般 ...

  7. go语言25个关键字总结

    var和const :变量和常量的声明var varName type 或者 varName : = valuepackage and import: 导入func: 用于定义函数和方法return ...

  8. OS之os.fork()

    有两种方式来实现并发性, 一种方式是让每个“任务"或“进程”在单独的内在空间中工作,每个都有自已的工作内存区域.不过,虽然进程可在单独的内存空间中执行,但除非这些进程在单独的处理器上执行,否 ...

  9. 初探J2EE

    还记得在技术交流会上八期给我们讲的J2EE,当时就是云里来屋里去.留在自己脑子中的仅仅有两个字"规范",其他的真是一无全部. 可是如今学了后,又在脑子里留下了两个字"规范 ...

  10. kvo&kvc

    Key Value Coding Key Value Coding是cocoa的一个标准组成部分,它能让我们可以通过name(key)的方式访问property, 不必调用明确的property ac ...