029_网络编程

楔子

  你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好。但是如果这两个程序之间想要传递一个数据,你要怎么做呢?

  这个问题以你现在的知识就可以解决了,我们可以创建一个文件,把a.py想要传递的内容写到文件中,然后b.py从这个文件中读取内容就可以了。

  但是当你的a.py和b.py分别在不同电脑上的时候,你要怎么办呢?

  类似的机制有计算机网盘,qq等等。我们可以在我们的电脑上和别人聊天,可以在自己的电脑上向网盘中上传、下载内容。这些都是两个程序在通信。

软件开发的架构

  我们了解的涉及到两个程序之间通讯的应用大致可以分为两种:

  第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用

  第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用

  这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~

1,C/S架构

  C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。

  这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

2,B/S架构

   B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。

  Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。

 网络基础

1,计算机网络的发展及基础网络概念

  1.1,早期 : 联机

  1.2,以太网 : 局域网与交换机

 

2,交换机 —— 解决 多台机器之前的通信问题

3,广播

  主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要),由于其不用路径选择,所以其网络成本可以很低廉。有线电视网就是典型的广播型网络,我们的电视机实际上是接受到所有频道的信号,但只将一个频道的信号还原成画面。在数据网络中也允许广播的存在,但其被限制在二层交换机的局域网范围内,禁止广播数据穿过路由器,防止广播数据影响大面积的主机。

4,ip地址与ip协议

  规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示

  3.1,ipv4

    4个点分十进制(4个8位2进制数):00000000.00000000.00000000.00000000

    范围   0.0.0.0——255.255.255.255

    保留字段 192.168.----

    127.0.0.1 本地的回环地址

  3.2,ipv6

    6个点分十进制(6个8位2进制数):

    范围    0.0.0.0.0.0 - 255.255.255.255.255.255

 5,mac地址

  head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址。

  mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)

6,arp协议 ——查询IP地址和MAC地址的对应关系

  地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。
  主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址。
  收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
  地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。相关协议有RARP、代理ARP。NDP用于在IPv6中代替地址解析协议。 
 
广域网与路由器

 

1,路由器

  路由器(Router),是连接因特网中各局域网、广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径,按前后顺序发送信号。 路由器是互联网络的枢纽,"交通警察"。目前路由器已经广泛应用于各行各业,各种不同档次的产品已成为实现各种骨干网内部连接、骨干网间互联和骨干网与互联网互联互通业务的主力军。路由和交换机之间的主要区别就是交换机发生在OSI参考模型第二层(数据链路层),而路由发生在第三层,即网络层。这一区别决定了路由和交换机在移动信息的过程中需使用不同的控制信息,所以说两者实现各自功能的方式是不同的。
  路由器(Router)又称网关设备(Gateway)是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网。当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成。因此,路由器具有判断网络地址和选择IP路径的功能,它能在多网络互联环境中,建立灵活的连接,可用完全不同的数据分组和介质访问方法连接各种子网,路由器只接受源站或其他路由器的信息,属网络层的一种互联设备。 

2,局域网

  如上图,一个圆圈内是一个局域网

  局域网(Local Area Network,LAN)是指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的日程安排、电子邮件和传真通信服务等功能。局域网是封闭型的,可以由办公室内的两台计算机组成,也可以由一个公司内的上千台计算机组成。  

3,子网掩码 

  所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。

  知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。 

比如,已知IP地址172.16.10.1和172.16.10.2的子网掩码都是255.255.255.0,请问它们是否在同一个子网络?两者与子网掩码分别进行AND运算,

172.16.10.1:10101100.00010000.00001010.000000001
255255.255.255.0:11111111.11111111.11111111.00000000
AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0

 

172.16.10.2:10101100.00010000.00001010.00000010
255255.255.255.0:11111111.11111111.11111111.00000000
AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0
结果都是172.16.10.0,因此它们在同一个子网络。
View Code

4,网段

# 交换机 —— 多台机器之前的通信问题
# 网关的概念 —— 局域网中的机器想要访问局域网外的机器,需要通过网关访问
# 网段—— ip地址 和 子网掩码 按位与  得到网段地址,即ip地址前三个数。
例如:
# 255.255.255.0   子网掩码
#192.168.13.253   ip地址的网段为:192.168.13.0
192.168.13.0 到 192.168.13.255 为一段

 

5,端口

  • 在计算机上 每一个需要网络通信的程序 都会开一个端口
  • 在同一时间只会有一个程序占用一个端口,不可能在同一时间 在同一个计算机上有两个程序 占用同一个端口
  • 端口的范围 : 0-65535
  • 一般情况下 8000 之后的端口,因为之前的要留给系统用的。

6,一个程序如何在网络上找到另一个程序?

  • ip -- 确定唯一一台机器
  • 端口 -- 确定唯一的一个程序
  • ip+端口 找到唯一的一台机器上的唯一的一个程序

 计算机网络协议

  互联网的核心就是由一堆协议组成,协议就是标准,比如全世界人通信的标准是英语,如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。

1,osi七层模型

  1.1,互联网协议按照功能不同分为  osi七层  或  tcp/ip五层  或  tcp/ip四层

  1.2,每层运行常见物理设备

  1.3,每层运行常见的协议

2,socket层  概念

  2.1,理解socket

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

  2.2,站在你的角度上看socket

    其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

  2.3,socket层

 3,套接字(socket)的发展史

  套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

  3.1,基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

3.2,基于网络类型的套接字家族

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

4.tcp协议和udp协议

  TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低、全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

  UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

  TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
  UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

现在Internet上流行的协议是TCP/IP协议,该协议中对低于1024的端口都有确切的定义,他们对应着Internet上一些常见的服务。这些常见的服务可以分为使用TCP端口(面向连接)和使用UDP端口(面向无连接)两种。 
说到TCP和UDP,首先要明白“连接”和“无连接”的含义,他们的关系可以用一个形象地比喻来说明,就是打电话和写信。两个人如果要通话,首先要建立连接——即打电话时的拨号,等待响应后——即接听电话后,才能相互传递信息,最后还要断开连接——即挂电话。写信就比较简单了,填写好收信人的地址后将信投入邮筒,收信人就可以收到了。从这个分析可以看出,建立连接可以在需要痛心地双方建立一个传递信息的通道,在发送方发送请求连接信息接收方响应后,由于是在接受方响应后才开始传递信息,而且是在一个通道中传送,因此接受方能比较完整地收到发送方发出的信息,即信息传递的可靠性比较高。但也正因为需要建立连接,使资源开销加大(在建立连接前必须等待接受方响应,传输信息过程中必须确认信息是否传到及断开连接时发出相应的信号等),独占一个通道,在断开连接钱不能建立另一个连接,即两人在通话过程中第三方不能打入电话。而无连接是一开始就发送信息(严格说来,这是没有开始、结束的),只是一次性的传递,是先不需要接受方的响应,因而在一定程度上也无法保证信息传递的可靠性了,就像写信一样,我们只是将信寄出去,却不能保证收信人一定可以收到。 
TCP是面向连接的,有比较高的可靠性, 一些要求比较高的服务一般使用这个协议,如FTP、Telnet、SMTP、HTTP、POP3等。
而UDP是面向无连接的,使用这个协议的常见服务有DNS、SNMP、QQ等。对于QQ必须另外说明一下,QQ2003以前是只使用UDP协议的,其服务器使用8000端口,侦听是否有信息传来,客户端使用4000端口,向外发送信息(这也就不难理解在一般的显IP的QQ版本中显示好友的IP地址信息中端口常为4000或其后续端口的原因了),即QQ程序既接受服务又提供服务,在以后的QQ版本中也支持使用TCP协议了。
更多

 

 

5,TCP协议

  当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。

  这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。

TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK[1],并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接。[1] 
TCP三次握手的过程如下:
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
tcp的三次握手
建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。
(1) 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
(2) 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。
注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
(3) 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
(4) 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。[1] 
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。
注意:
(1) “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。[2] 
(2) 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。
(3) 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。
无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。[2] 
tcp的四次挥手

6,UDP协议

  当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。

  当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。

套接字(socket)初使用

1,基于TCP协议的socket

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

  1.1,server端

import socket
sk = socket.socket()         # 创建服务器scoket(接口)对象
sk.bind(('127.0.0.1',8898))  # 给本接口绑定地址,端口   # bind 捆绑
sk.listen()                  # 监听是否有链接请求
conn, addr = sk.accept()     # 接受客户端链接,地址。创建链接对象conn
# print(conn,addr)
ret = conn.recv(1024)      # recvive接收 客户端send发送信息;参数设置接:收多少字节
print(ret)                 # 收到的消息是bytes类型的
conn.send(b'helloy')       # 向客户端发送信息,必须是bytes类型
conn.close()               # 关闭客户端链接
sk.close()                 # 关闭服务器接口  # 可选

# accept 接受 akˈsept  # connect 连接 kəˈnekt  # address 地址 əˈdres,
# receive 接收 riˈsēv

 

  1.2,client端

import socket
sk = socket.socket()         # 创建客户端scoket(接口)对象
sk.connect(('127.0.0.1',8898)) # 通过本接口尝试链接要链接的服务器及它的接口
sk.send(b'hello!!')          # 向服务器发送信息,必须是bytes类型
ret = sk.recv(1024)
print(ret)                  # 收到的消息是bytes类型的
sk.close()                  # 关闭客户端接口

  

  1.3,问题:在重启服务端时可能会遇到

    解决方法:

#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()         # 创建服务器scoket(接口)对象
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898))  # 给本接口绑定地址,端口   # bind 捆绑
sk.listen()                  # 监听是否有链接请求
conn, addr = sk.accept()     # 接受客户端链接,地址。创建链接对象conn
# print(conn,addr)
ret = conn.recv(1024)      # recvive接收 客户端send发送信息;参数设置接:收多少字节
print(ret)                 # 收到的消息是bytes类型的
conn.send(b'helloy')       # 向客户端发送信息,必须是bytes类型
conn.close()               # 关闭客户端链接
sk.close()                 # 关闭服务器接口  # 可选

# accept 接受 akˈsept  # connect 连接 kəˈnekt  # address 地址 əˈdres,
# receive 接收 riˈsēv 

 

  1.4,稍加完善

import socket
sk = socket.socket()         # 创建服务器scoket(接口/套接字)对象
sk.bind(('127.0.0.1',8898))  # 给本接口绑定地址,端口   # bind 捆绑
sk.listen()                  # 监听是否有链接请求
while True:
    conn, addr = sk.accept()     # 接受客户端链接,地址。创建链接对象conn

    while True:
        msg = conn.recv(1024).decode('utf-8')      # recvive接收 客户端send发送信息;参数设置接:收多少字节;将接收的信息解码
        print(msg)

        if msg == 'bye':    #如果收到客户端发来的是bye,客户端想结束
            break

        info = input('>>>')     # 输入想向客户端发送的消息

        if info == 'bye':       # 如果服务端想结束
            conn.send(b'bye')   # 向客户端发送bye
            break

        conn.send(info.encode('utf-8'))       # 向客户端发送信息,必须是bytes类型

    conn.close()               # 关闭客户端链接
sk.close()                 # 关闭服务器接口,如果不关闭,还能继续接收

# accept 接受 akˈsept  # connect 连接 kəˈnekt  # address 地址 əˈdres,
# receive 接收 riˈsēv

# 服务端只开了一个接口,一次只能与一个客户端建立连接,
# # 当关闭该客户端连接后,才会与另一个客户端建立连接
sever
import socket
sk = socket.socket()         # 创建客户端scoket(接口)对象
sk.connect(('127.0.0.1',8898)) # 通过本接口尝试链接要链接的服务器及它的接口
while True:
    msg = input('>>>')    # 输入要发送的消息

    if msg == 'bye':      # 如果想结束,发送bye
        sk.send(b'bye')
        break

    sk.send(msg.encode('utf-8'))          # 向服务器发送信息,必须是bytes类型
    ret = sk.recv(1024).decode('utf-8')
    if ret == 'bye':
        break
    print(ret)                  # 收到的消息是bytes类型的
sk.close()                  # 关闭客户端接口
client
import socket
sk = socket.socket()         # 创建客户端scoket(接口)对象
sk.connect(('127.0.0.1',8898)) # 通过本接口尝试链接要链接的服务器及它的接口
while True:
    msg = input('>>>')    # 输入要发送的消息

    if msg == 'bye':      # 如果想结束,发送bye
        sk.send(b'bye')
        break

    sk.send(('client2:' + msg).encode('utf-8'))          # 向服务器发送信息,必须是bytes类型
    ret = sk.recv(1024).decode('utf-8')
    if ret == 'bye':
        break
    print(ret)                  # 收到的消息是bytes类型的

# 运行后,如果服务端已经与客户端建立连接,此处会等待,
# 当服务端关闭连接后会与该客户端建立连接,并接收到发送的消息
client2

 

2,基于UDP协议的socket

    udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接

  2.1,server端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  #DGRAM datagram
sk.bind(('127.0.0.1',8080))       #只有服务端有的

msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye',addr)

sk.close()


# udp的server 不需要进行监听也不需要建立连接
# 在启动服务之后只能被动的等待客户端发送消息过来
# 客户端发送消息的同时还会 自带地址信息
# 消息回复的时候 不仅需要发送消息,还需要把对方的地址填写上

   2.2,client端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)

sk.sendto(b'hello',ip_port)
ret,addr = sk.recvfrom(1024)
print(ret.decode('utf-8'))

sk.close()

# client端不需要connect 因为UDP协议是不需要建立连接的
# 直接了解到对方的ip和端口信息就发送数据就行了
# sendto和recvfrom的使用方法是完全和server端一致的

 

3, qq聊天

  3.1,简单实现 

#_*_coding:utf-8_*_
import socket
ip_prot = ('127.0.0.1', 8081)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sk.bind(ip_prot)

while True:
    qq_msg, addr = sk.recvfrom(1024)
    print('%s:%s:\033[1;44m%s\033[0m'%(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg = input('me:').strip()
    sk.sendto(back_msg.encode('utf-8'), addr)
sever
#_*_coding:utf-8_*_
import socket
BUFSIZE = 1024   # buffer 缓冲 [ˈbʌfə(r)]
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

qq_name_dic = {
    'Alice':('127.0.0.1',8081),
    '亚丝娜':('127.0.0.1',8081),
    '桐人':('127.0.0.1',8081),
    '尤吉欧':('127.0.0.1',8081),
}

while True:
    qq_name = input('请选择聊天对象:').strip()
    if not qq_name or qq_name not in qq_name_dic:
        print('不是您的好友!')
        continue
    while True:
        msg = input('请输入消息,回车发送,输入q结束和他的聊天:').strip()
        if msg == 'q':break
        sk.sendto(msg.encode('utf-8'), qq_name_dic[qq_name])

        back_msg, addr = sk.recvfrom(BUFSIZE)
        print('%s:%s\033[1;44m%s\033[0m'%(addr[0], addr[1],back_msg.decode('utf-8')))

sk.close()

# 本qq例,不管人物有几个,每次只能与其中一个人物对话。
# 终端只创建了一个套接字对象,即终端的地址端口是一个,不是说一个人物一个端口。
client

  3.2,稍加完善

#_*_coding:utf-8_*_
import socket
ip_prot = ('127.0.0.1', 8080)
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(ip_prot)

while True:
    qq_msg, addr = sk.recvfrom(1024)
    print('%s:%s'%(addr,qq_msg.decode('utf-8')))
    back_msg = input('SAO:').strip()
    back_msg = ('\033[34mSAO :%s\033[0m'%back_msg).encode('utf-8')
    sk.sendto(back_msg, addr)
sk.close()

# 可分别与各终端互动,
# 但本例中的服务端,在接收到一个终端的消息后必须回复该终端消息后才能接收新消息。
# 本服务端只能被动互动
sever

 

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
while True:
    info = input('亚丝娜 :')
    info = ('\033[34m亚丝娜 :%s\033[0m'%info).encode('utf-8')
    sk.sendto(info,ip_port)
    msg,addr = sk.recvfrom(1024)
    print(msg.decode('utf-8'))

sk.close()
client
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
while True:
    info = input('桐人 :')
    info = ('\033[32m桐人 :%s\033[0m'%info).encode('utf-8')
    sk.sendto(info,ip_port)
    msg,addr = sk.recvfrom(1024)
    print(msg.decode('utf-8'))

sk.close()
client2

4,时间服务器

# _*_coding:utf-8_*_
from socket import *
from time import strftime

ip_port = ('127.0.0.1', 9000)
bufsize = 1024

tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server.bind(ip_port)

while True:
    msg, addr = tcp_server.recvfrom(bufsize)
    print('===>', msg)

    if not msg:
        time_fmt = '%Y-%m-%d %X'
    else:
        time_fmt = msg.decode('utf-8')
    back_msg = strftime(time_fmt)

    tcp_server.sendto(back_msg.encode('utf-8'), addr)

tcp_server.close()
server
#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024

tcp_client=socket(AF_INET,SOCK_DGRAM)



while True:
    msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
    tcp_client.sendto(msg.encode('utf-8'),ip_port)

    data=tcp_client.recv(bufsize)
client

 5,socket参数的详解

socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)

 

创建socket对象的参数说明:
family 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域实际上是使用本地 socket 文件来通信)
type 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。
SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
proto 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
fileno 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。
与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。
这可能有助于使用socket.close()关闭一个独立的插座。 

 黏包问题

题:回答

# 为什么会出现黏包现象?
    # 首先只有在TCP协议中才会出现黏包现象,
    # 是因为TCP协议是面向流的协议
    # 在发送的数据传输的过程中还有缓存机制来避免数据丢失
    # 因此 在连续发送小数据的时候 以及接收大小不符的时候都容易出现黏包现象
    # 本质还是因为我们在接收数据的时候不知道发送的数据的长短
# 解决黏包问题
    # 在传输大量数据之前先告诉接收端要发送的数据大小
    # 如果想更漂亮的解决问题,可以通过struct模块来定制协议

 

 

让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

且只能从管道里读一次结果
注意

同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

1,基于tcp协议实现的黏包

#_*_coding:utf-8_*_
from socket import *
import subprocess

ip_port=('127.0.0.1',8888)
BUFSIZE=1024

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)

while True:
    conn,addr=tcp_socket_server.accept()
    print('客户端',addr)

    while True:
        cmd=conn.recv(BUFSIZE)
        if len(cmd) == 0:break

        res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                         stdout=subprocess.PIPE,
                         stdin=subprocess.PIPE,
                         stderr=subprocess.PIPE)

        stderr=res.stderr.read()
        stdout=res.stdout.read()
        conn.send(stderr)
        conn.send(stdout)
tcp - server
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8888)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    act_res=s.recv(BUFSIZE)

    print(act_res.decode('utf-8'),end='')
tcp - client

2,基于udp协议实现的黏包

#_*_coding:utf-8_*_
from socket import *
import subprocess

ip_port=('127.0.0.1',9000)
bufsize=1024

udp_server=socket(AF_INET,SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind(ip_port)

while True:
    #收消息
    cmd,addr=udp_server.recvfrom(bufsize)
    print('用户命令----->',cmd)

    #逻辑处理
    res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
    stderr=res.stderr.read()
    stdout=res.stdout.read()

    #发消息
    udp_server.sendto(stderr,addr)
    udp_server.sendto(stdout,addr)
udp_server.close()
udp - server
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024

udp_client=socket(AF_INET,SOCK_DGRAM)


while True:
    msg=input('>>: ').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)
    err,addr=udp_client.recvfrom(bufsize)
    out,addr=udp_client.recvfrom(bufsize)
    if err:
        print('error : %s'%err.decode('utf-8'),end='')
    if out:
        print(out.decode('utf-8'), end='')
udp - client

注意:只有TCP有粘包现象,UDP永远不会粘包

 

posted @ 2019-04-03 10:34  冰羽~zZ  阅读(288)  评论(0编辑  收藏  举报