Python-网络编程之socket

软件开发架构

开发软件,必须要开发一套客户端与服务端

客户端:使用服务

服务端:提供服务

C/S 架构

c即client:客户端

S即Server:服务端

优点:软件使用稳定,节省网络资源

缺点:1、下载客户端 2、每次更新需要更新客户端

C/S 架构的软件:例如qq

B/S 架构

B即Browser:浏览器(浏览器也是软件)

S即Server:服务端

优点:1、以浏览器充当客户端,无需下载客户端 2、无需更新

缺点:网络资源消耗大

B/S 架构例如:在浏览器输入 xxx域名

网络编程

网络编程的发展史

所有先进数都源自于军事,希望通过远程获取数据,所以出现了网络编程

要实现远程通信必须具备:

1、 物理连接介 --> 网卡

2、 互联网协议 --> OSI七层协议

互联网协议

互联网协议又称为网络七层协议,OSI七层协议,OSI是一个世界标准组织

OSI 七层协议:

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理链路层

物理链路层

基于电流信号发送二进制数据

数据链路层

以太网协议,专门处理基于电流信号发送的二进制的数据

以太网协议规定好电流信号数据的分组方式,每一台连接网线的电脑都必须有一块网卡,网卡由不同厂商生产,每块网卡都有世界上唯一的12位的编号

交换机

数据链路层的一个应用,它可以将多态电脑连接到一起

基于以太网协议发送广播、单播 数据,但有一点不好会产生广播风暴,不能跨局域网通信

互联网

互联网的出现 可以让局域网之间通信

网络层

基于IP工作

IP地址

用于标识唯一的一台计算机的地址

传输层

TCP/UDP 协议,基于端口工作

端口号 标识了电脑上某一个软件,端口号范围:0-65535

我们在开发中常用软件的默认端口号:

MySql:3306

Mongodb:27017

Django:8000

Tomcat:8080

Flask:5000

Redis:6379

若 客户端与服务端想要通信,必须要建立连接,产生双向通道

一条通道是客户端往服务端发送消息

另一条是服务端往客户端发送消息

TCP工作原理

TCP三次握手

张三首先向李四招手(syn),李四看到张三向自己招手后,向对方点了点头挤出了一个微笑(ack)。张三看到李四微笑后确认了李四成功辨认出了自己(进入estalished状态)。

但是李四还有点狐疑,向四周看了一看,有没有可能张三是在看别人呢,他也需要确认一下。

所以李四也向张三招了招手(syn),张三看到李四向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑(ack),李四看到对方的微笑后确认了张三就是在向自己打招呼(进入established状态)。

于是两人加快步伐,走到了一起,相互拥抱

我们看到这个过程中一共是四个动作,张三招手--李四点头微笑--李四招手--张三点头微笑。

其中李四连续进行了2个动作,先是点头微笑(回复对方),然后再次招手(寻求确认),实际上可以将这两个动作合一,招手的同时点头和微笑(syn+ack)。

于是四个动作就简化成了三个动作,张三招手--李四点头微笑并招手--张三点头微笑。这就是三次握手的本质,中间的一次动作是两个动作的合并。

我们看到有两个中间状态,syn_sent和syn_rcvd,这两个状态叫着「半打开」状态,就是向对方招手了,但是还没来得及看到对方的点头微笑。

syn_sent是主动打开方的「半打开」状态,syn_rcvd是被动打开方的「半打开」状态。客户端是主动打开方,服务器是被动打开方。

TCP 数据传输就是两个人隔空对话,差了一点距离,所以需要对方反复确认听见了自己的话。

张三喊了一句话(data),李四听见了之后要向张三回复自己听见了(ack)。

如果张三喊了一句,半天没听到李四回复,张三就认为自己的话被大风吹走了,李四没听见,所以需要重新喊话,这就是tcp重传。

也有可能是李四听到了张三的话,但是李四向张三的回复被大风吹走了,以至于张三没听见李四的回复。

张三并不能判断究竟是自己的话被大风吹走了还是李四的回复被大风吹走了,张三也不用管,重传一下就是。

既然会重传,李四就有可能同一句话听见了两次,这就是「去重」。「重传」和「去重」工作操作系统的网络内核模块都已经帮我们处理好了,用户层是不用关心的。

张三可以向李四喊话,同样李四也可以向张三喊话,因为tcp链接是「双工的」,双方都可以主动发起数据传输。不过无论是哪方喊话,都需要收到对方的确认才能认为对方收到了自己的喊话。

张三可能是个高射炮,一说连说了八句话,这时候李四可以不用一句一句回复,而是连续听了这八句话之后,一起向对方回复说前面你说的八句话我都听见了,这就是批量ack。

但是张三也不能一次性说了太多话,李四的脑子短时间可能无法消化太多,两人之间需要有协商好的合适的发送和接受速率,这个就是「TCP窗口大小」。

网络环境的数据交互同人类之间的对话还要复杂一些,它存在数据包乱序的现象。

同一个来源发出来的不同数据包在「网际路由」上可能会走过不同的路径,最终达到同一个地方时,顺序就不一样了。

操作系统的网络内核模块会负责对数据包进行排序,到用户层时顺序就已经完全一致了。

TCP 四次挥手

TCP断开链接的过程和建立链接的过程比较类似,只不过中间的两部并不总是会合成一步走,所以它分成了4个动作,张三挥手(fin)——李四伤感地微笑(ack)——李四挥手(fin)——张三伤感地微笑(ack)。

之所以中间的两个动作没有合并,是因为tcp存在「半关闭」状态,也就是单向关闭。

张三已经挥了手,可是人还没有走,只是不再说话,但是耳朵还是可以继续听,李四呢继续喊话。等待李四累了,也不再说话了,超张三挥了挥手,张三伤感地微笑了一下,才彻底结束了。

上面有一个非常特殊的状态time_wait,它是主动关闭的一方在回复完对方的挥手后进入的一个长期状态,这个状态标准的持续时间是4分钟,4分钟后才会进入到closed状态,释放套接字资源。不过在具体实现上这个时间是可以调整的。

它就好比主动分手方要承担的责任,是你提出的要分手,你得付出代价。这个后果就是持续4分钟的time_wait状态,不能释放套接字资源(端口),就好比守寡期,这段时间内套接字资源(端口)不得回收利用。

它的作用是重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文。

同时在这段时间内,该链接在对话期间于网际路由上产生的残留报文(因为路径过于崎岖,数据报文走的时间太长,重传的报文都收到了,原始报文还在路上)传过来时,都会被立即丢弃掉。

4分钟的时间足以使得这些残留报文彻底消逝。不然当新的端口被重复利用时,这些残留报文可能会干扰新的链接。

4分钟就是2个MSL,每个MSL是2分钟。MSL就是maximium segment lifetime——最长报文寿命。这个时间是由官方RFC协议规定的。至于为什么是2个MSL而不是1个MSL,我还没有看到一个非常满意的解释。

四次挥手也并不总是四次挥手,中间的两个动作有时候是可以合并一起进行的,这个时候就成了三次挥手,主动关闭方就会从fin_wait_1状态直接进入到time_wait状态,跳过了fin_wait_2状态。

应用层

应用层包含了表示层和会话层,常见的有http、ftp

Socket

什么是Socket

Socket是一个模块,可以写一套C/S 架构的套接字

为什么要使用Socket

Socket套接字 封装好了各层协议的工作,可以节省开发成本

如何使用

那么我们先写服务端,启动也是先启动服务端

# 服务端

# coding=utf-8

import socket
s = socket.socket()     # 导入socket模块产生一个s对象
s.bind(
    ("127.0.0.1",8888)      # 设置ip+port,绑定套接字
)
s.listen(5)                 # 开始监听(5) 表示可以服务 5+1=6 个客户端,一个正在服务,另外5个在等候服务
while True:
    conn, addr = s.accept() # 接收客户端连接
    while True:
        try:    
            data = conn.recv(1024).decode("utf-8")      # 接收客户端信息
            print(data)                 # 打印客户端信息

            if len(data) == 0:          # 如果发送为0,跳过
                continue

            if data == "q":             # 如果客户端发送q退出,那么退出服务
                break
            conn.send(data.encode("utf-8")) # 服务端发行消息
        except Exception as e:
            break
    conn.close()
s.close()        #关闭服务器套接字(可选)
# 客户端

# coding=utf-8
import socket       # 导入socket模块

c = socket.socket()     
c.connect(
    ("127.0.0.1",8888)      # 连接服务端
)

while True:
    send_msg = input("client:>>>")
    c.send(send_msg.encode("utf-8"))        # 发送消息

    if send_msg == "q":
        break
    data = c.recv(1024).decode("utf-8")     # 接收消息
    print(data)


c.close()

猜你喜欢

转载自www.cnblogs.com/qinyujie/p/11692840.html