python之socket(套接字)
Posted changzhendong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python之socket(套接字)相关的知识,希望对你有一定的参考价值。
一、客户端/服务器架构
1.1基本概念
客户端/服务器架构即:C/S架构,包括:
(1)硬件C/S架构(打印机)
(2)软件C/S架构(Web服务器)
1.2举例
生活中的C/S架构:饭店为S端,所有食客C端
二、TCP/udp/osi七层
2.1TCP/UDP协议
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
2.1tcp的三次握手
所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
简单来说,就是
1、建立连接时,客户端发送SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认
2、服务器收到SYN包,必须确认客户的SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态
3、客户端收到服务器的SYN+ACK包,向服务器发送确认报ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手,客户端与服务器开始传送数据。
SYN攻击:
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:
#netstat -nap | grep SYN_RECV
2.2tcp的四次挥手
所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
为什么要建立三次握手四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
原因有二: 一、保证TCP协议的全双工连接能够可靠关闭 二、保证这次连接的重复数据段从网络中消失
先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。
再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。
2.3为什么Tcp连接的创立需要三次握手 ,而断开需要四次挥手
这是经典的三次握手,为啥是3次?
刚开始不明白,后来读了一些书,看了一些资料,了解了基于TCP协议传递信息流的通信方式。
发送方以确定SYN标志,同时生成一个ISN(初始序列号),也就是消息序号来发送信息(消息字节数n)。接收方如果收到了信息,会以ACK标志和下次需要对方传递的序号值发送给对方,ack标志告诉对方我已经收到了信息,传递序号ISN+n告诉对方下次从这个序号的地方开始发送。
两次消息的传递,意味着一次通信的完成。后面消息的序号都是基于ISN和传递消息的字节数逐渐累加计算得来。
TCP协议的通信方式规定是这样的。同时,基于tcp协议的双方是双全工的,也就是说通信双方都可以向对方发送消息,也都可以独立关闭自己一方的通信通道。
基于通信方式和双全工的特性,所以在tcp连接建立时
client需要将自己的ISN序号告知对方,同时需要对方的确定。
server也需要将自己的ISN序号告知对方,同时也要对方的确定。
在上图中,server将自己的ack和发出的syn标志的告知对方ISN的合并在一次传递中,这样子节省流量。所以三次握手很合理。
至于四次挥手,同样也是基于以上的原理。尤其是通信双方都可以独立关闭自己的通信通道,也就是半关闭。
client先发送FIN告知对方我已经完成数据发送了,server回复ack来确定我知道了。这样一个流程,就关闭了client的发送信息通道。但是还可以接收来自server方的数据。
server此时已经知道接收不到client的数据了,但是还可以给它发送数据。如果server也没有啥数据要发送给对方了,server也会以FIN标志位发送一个信息给client,client接到后,也会传递一个ack表示知道了。这样子,双方都完成了关闭。
2.4 osi七层协议
互联网的核心就是由一堆协议组成,协议就是标准,比如全世界人通信的标准是英语,如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了
网络中的计算机与终端间要想正确的传送信息和数据,必须在数据传输的顺序、数据的格式及内容等方面有一个约定或规则,这种约定或规则称做协议。
应用层:
与其它计算机进行通讯的一个应用,它是对应应用程序的通信服务的。例如,一个没有通信功能的字处理程序就不能执行通信的代码,从事字处理工作的程序员也不关心OSI的第7层。但是,如果添加了一个传输文件的选项,那么字处理器的程序就需要实现OSI的第7层。示例:TELNET,HTTP,FTP,NFS,SMTP等。
表示层:
这一层的主要功能是定义数据格式及加密。例如,FTP允许你选择以二进制或ASCII格式传输。如果选择二进制,那么发送方和接收方不改变文件的内容。如果选择ASCII格式,发送方将把文本从发送方的字符集转换成标准的ASCII后发送数据。在接收方将标准的ASCII转换成接收方计算机的字符集。示例:加密,ASCII等。
会话层:
它定义了如何开始、控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时可以通知应用,从而使表示层看到的数据是连续的,在某些情况下,如果表示层收到了所有的数据,则用数据代表表示层。示例:RPC,SQL等。
传输层:
这层的功能包括是否选择差错恢复协议还是无差错恢复协议,及在同一主机上对不同应用的数据流的输入进行复用,还包括对收到的顺序不对的数据包的重新排序功能。示例:TCP,UDP,SPX。
网络层:
这层对端到端的包传输进行定义,它定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。示例:IP,IPX等。
数据链路层:
它定义了在单个链路上如何传输数据。这些协议与被讨论的各种介质有关。示例:ATM,FDDI等
物理层:
OSI的物理层规范是有关传输介质的特性,这些规范通常也参考了其他组织制定的标准。连接头、帧、帧的使用、电流、编码及光调制等都属于各种物理层规范中的内容。物理层常用多个规范完成对所有细节的定义。示例:Rj45,802.3等。
三、socket(套接字)
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端。
3.1 socket的服务端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind((‘192.168.137.173‘,7680)) phone.listen(5) print(‘----->‘) conn,addr=phone.accept() msg=conn.recv(1024) print(‘客户端发来的消息是:‘,msg) conn.send(msg.upper()) conn.close()
3.2 socket的客户端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect((‘192.168.137.173‘,7680)) phone.send(‘hello‘.encode(‘utf-8‘)) data=phone.recv(1024) print(‘收到服务端发来的消息是:‘,data)
四、异常处理
python为每一种异常定制了一个类型,然后提供了一种特定的语法结构用来执行异常处理。
part1:基本语法 try: "被检测的代码块” except异常类型:
4.1异常处理
# age=input(‘---->>:‘) # int(age) # age=input(‘---->>:‘) # if age.isdigit(): # int(age) #主逻辑 # # elif age.isspace(): # print(‘--->用户输入的是空格‘) # # elif len(age)==0: # print(‘---->>用户输入为空‘) # # else: # print(‘其他的非法输入‘) #上述代码的不可取之处在于:如果有相同的代码,要重复写。如: #第二段代码 # age2=input(‘----->>:‘) # int(age2) #第三段代码 # age3=input(‘----->>:‘) # int(age3) #出现以上情况,就得反复写if判断语句来预防异常情况出现,这种方法不可取 #有人建议加and,如下; # age=input(‘---->>:‘) # age2=input(‘----->>:‘) # if age.isdigit() and age2.isdigit(): # int(age) #主逻辑 # int(age2) # elif age.isspace and age2.isspace(): # print(‘--->用户输入的是空格‘) # # elif len(age)==0: # print(‘---->>用户输入为空‘) # # else: # print(‘其他的非法输入‘) #这种方法的不可取之处在于:如果代码比较多,就得反复写and。 # def test(): # print(‘test running‘) # choice_dic= # ‘1‘:test # # # while True: # choice=input(‘--->>:‘).strip() # if not choice or choice not in choice_dic: # continue # choice_dic[choice]() #真正的异常处理 # try: # age=input(‘--->>:‘) # int(age) # age2=input(‘---->>‘) # int(age2) # # except ValueError as e: # print(e) # try: # age=input(‘--->>:‘) # int(age) # age2=input(‘---->>‘) # int(age2) # l=[1,‘asc‘,23] # l[100] # # except ValueError as e: # print(e) # except KeyError as e: # print(‘--->:‘,e) # except IndexError as e: # print(‘====>:‘,e) # # print(‘111‘) #万能异常处理 # try: # age=input(‘--->>:‘) # int(age) # age2=input(‘---->>‘) # int(age2) # l=[1,‘asc‘,23] # l[100] # # except Exception as e: # print(e) # # print(‘111‘) # while True: # try: # age = input(‘--->>:‘) # int(age) # age2 = input(‘---->>‘) # int(age2) # break # except Exception as e: # print("请重新输入:",e) # # print(‘111‘)
4.2异常的其他结构
s1=‘hello‘ try: int(s1) except ValueError as e: print(e) except KeyError as e: print(‘--->:‘,e) except IndexError as e: print(‘====>:‘,e) except Exception as e: print(e) else: print(‘try内代码块没有异常则执行‘) finally: print(‘无论异常与否,都执行该模块,通常是进行清理工作‘) print(111) print(22222)
4.3自定义异常
class czdException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg try: raise czdException(‘类型错误‘) except czdException as e: print(e)
4.4异常的几种标识
#异常的标识: # AttributeError:试图访问一个没有对象的树形,比如:foo.x,但是foo没有属性x。 # IOError:输入/输出异常,基本上式无法打开文件 # ImportError:无法引入模块或包,基本上是路径问题或名称错误 # IndexError:下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] # IndentationError:语法错误(的子类);代码没有正确对齐 # KeyError:试图访问字典里不存在的键 # KeyboardInterrupt:Ctrl+c被按下 # NameError:使用一个还未被赋予对象的变量 # SyntaxError:代码非法,代码不能编译。 # TypeError:传入对象类型与要求不符 # UnboundLocalError:试图访问一个还未被设置的局部变量,基本上是由于另一个同名的全局变量,导致你以为正在访问它 # ValueError:传入一个调用者不期望的值,即使值得类型是正确的。
注:二、TCP/udp/osi七层摘自:水一的博客
以上是关于python之socket(套接字)的主要内容,如果未能解决你的问题,请参考以下文章