Python菜鸟之路:Python基础-socket基础
Posted 程序员改变了世界、妹子改变了程序员!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python菜鸟之路:Python基础-socket基础相关的知识,希望对你有一定的参考价值。
预热知识
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模块的用法
1 s=socket.socket() # socket.socket()创建socket 2 3 s.bind() # 绑定地址到套接字 4 s.listen() # 开始TCP监听 5 s.accept() # 被动接受TCP客户端连接,等待连接的到来 6 s.connect() # 主动初始化TCP服务器连接 7 s.connect_ex() # connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 8 s.recv() # 接收TCP数据 9 s.send() # 发送TCP数据 10 s.sendall() # 完整发送TCP数据 11 s.recvfrom() # 接收UDP数据 12 s.sendto() # 发送UDP数据 13 s.getpeername() # 连接到当前套接字的远端的地址(TCP连接) 14 s.getsockname() # 当前套接字的地址 15 s.getsockopt() # 返回指定套接字的参数 16 s.setsockopt() # 设置指定套接字的参数 17 s.close() # 关闭套接字 18 s.setblocking() # 设置套接字的阻塞与非阻塞模式 19 s.settimeout() # 设置阻塞套接字操作的超时时间 20 s.gettimeout() # 得到阻塞套接字操作的超时时间 21 s.filen0() # 套接字的文件描述符 22 s.makefile() # 创建一个与该套接字关联的文件对象 23 24 socket.AF_UNIX # 只能够用于单一的Unix系统进程间通信 25 socket.AF_INET # 服务器之间网络通信 26 socket.AF_INET6 # IPv6 27 28 socket.SOCK_STREAM # 流式socket , for TCP 29 socket.SOCK_DGRAM # 数据报式socket , for UDP 30 socket.SOCK_RAW # 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 31 32 socket.SOCK_RDM # 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 33 34 socket.SOCK_SEQPACKET # 可靠的连续数据包服务 35 36 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行。
1 import socket 2 import os ,json 3 ip_port=(\'192.168.11.150\',8009) 4 #买手机 5 s=socket.socket() 6 #拨号 7 s.connect(ip_port) 8 #发送消息 9 welcome_msg = s.recv(1024) 10 print("from server:",welcome_msg.decode()) 11 while True: 12 send_data=input(">>: ").strip() 13 if len(send_data) == 0:continue 14 15 cmd_list = send_data.split() 16 if len(cmd_list) <2:continue 17 task_type = cmd_list[0] 18 if task_type == \'put\': 19 abs_filepath = cmd_list[1] 20 if os.path.isfile(abs_filepath): 21 file_size = os.stat(abs_filepath).st_size 22 filename = abs_filepath.split("\\\\")[-1] 23 print(\'file:%s size:%s\' %(abs_filepath,file_size)) 24 msg_data = {"action":"put", 25 "filename":filename, 26 "file_size":file_size} 27 # 在发送数据之前,先发送本次发送的数据包信息,关键字是 file_size 28 s.send( bytes(json.dumps(msg_data),encoding="utf-8") ) 29 server_confirmation_msg = s.recv(1024) 30 confirm_data = json.loads(server_confirmation_msg.decode()) 31 if confirm_data[\'status\'] ==200: 32 33 print("start sending file ",filename) 34 f = open(abs_filepath,\'rb\') 35 for line in f: 36 s.send(line) 37 38 print("send file done ") 39 40 else: 41 print("\\033[31;1mfile [%s] is not exist\\033[0m" % abs_filepath) 42 continue 43 else: 44 print("doesn\'t support task type",task_type) 45 continue 46 #s.send(bytes(send_data,encoding=\'utf8\')) 47 #收消息 48 recv_data=s.recv(1024) 49 print(str(recv_data,encoding=\'utf8\')) 50 #挂电话 51 s.close()
server端代码,关键点在28行。
1 import socketserver,json 2 class MyServer(socketserver.BaseRequestHandler): 3 def handle(self): 4 # print self.request,self.client_address,self.server 5 self.request.sendall(bytes(\'欢迎致电 10086,请输入1xxx,0转人工服务.\',encoding="utf-8")) 6 while True: 7 data = self.request.recv(1024) 8 if len(data) == 0:break 9 print("data", data) 10 print("[%s] says:%s" % (self.client_address,data.decode() )) 11 12 task_data = json.loads( data.decode() ) 13 task_action = task_data.get("action") 14 if hasattr(self, "task_%s"%task_action): 15 func = getattr(self,"task_%s" %task_action) 16 func(task_data) 17 else: 18 print("task action is not supported",task_action) 19 20 def task_put(self,*args,**kwargs): 21 print("---put",args,kwargs) 22 filename = args[0].get(\'filename\') 23 filesize = args[0].get(\'file_size\') 24 server_response = {"status":200} 25 self.request.send(bytes( json.dumps(server_response), encoding=\'utf-8\' )) 26 f = open(filename,\'wb\') 27 recv_size = 0
# 接收到客户端发来的数据包文件大小,然后进行循环接收,直至数据包刚好接收完毕 28 while recv_size < filesize: 29 data = self.request.recv(4096) 30 f.write(data) 31 recv_size += len(data) 32 print(\'filesize: %s recvsize:%s\' % (filesize,recv_size)) 33 print("file recv success") 34 f.close() 35 36 if __name__ == \'__main__\': 37 server = socketserver.ThreadingTCPServer((\'0.0.0.0\',8009),MyServer) 38 server.serve_forever()
以上是关于Python菜鸟之路:Python基础-socket基础的主要内容,如果未能解决你的问题,请参考以下文章