day18-网络编程基础(一)

勿骄勿燥,仍是要定下心学习,还有有些没定下心python

1.基础知识算法

2.tcp与udp协议编程

3.网络套接字json

4.基于c/s结构的服务器客户端的实验小程序

 

开始今日份总结设计模式

1.基础知识浏览器

现有的软件,绝大多数是基于C/S结构,那么就须要介绍网络编程,毕竟如今的绝大多数数据仍是在网络中传输。下面先说明一些网络的基础知识,不过对于从事网络工程的来讲只是很简单的基础知识,缓存

1.1 C/S架构安全

C/S架构中C指的是client(客户端软件),s指的是server(服务器端软件),而本章的主要学习目的是写一个基于C/S架构的软件,客户端软件与服务器端基于网络通讯。如今基本的C/S架构基本是下图这样:客户端与服务器基于网络传输互相传输数据。服务器

1.2 B/S架构

B/S架构中的B指的是Brower(浏览器),S指的是Server(服务器端),平常中的网页浏览也是B/S架构。(不是很了解)

1.3 OSI的七层协议

了解了C/S结构的大概构成,就说一下OSI七层协议,在美国军方发展ARPA网络以后,将他公布用于学术网络以后,就大爆发同样的发展了,因为网络协议的公开性,各家发展各自的标准,以致于各类网络之间并不能互通,国际ISO标准组织就颁发一套标准七层网络协议,七层网络协议有应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

应用层(7) -------------------------------------------------- 提供应用程序间的通讯

表示层 (6)-------------------------------------------------- 处理数据格式以及数据加密等

会话层 (5)-------------------------------------------------- 创建,维护管理会话

传输层 (4)-------------------------------------------------- 创建主机端到端的链接

网络层 (3)-------------------------------------------------- 寻址以及路由选择

数据链路层(2)--------------------------------------------- 提供介质访问,链路管理等

物理层(1) -------------------------------------------------- 比特流传输

通常567整合为应用程序,1234为数据流层

1.4 经常使用的TCP/IP的五层协议

因为IOS标准组织制定标准时间长,在厂商中TCP/IP更容易理解,虽然有一些结构性的缺陷,可是TCP/IP已经成为名副其实的标准了

主要有应用层,传输层,网络层,数据链路层,物理层

应用层 -------------------------------------------------- 用户数据

传输层 -------------------------------------------------- TCP报头+上层数据

网络层 -------------------------------------------------- IP报头+上层数据

数据链路层 --------------------------------------------- LLC报头+上层数据+FCS MAC报头+上层数据+FCS

物理层 -------------------------------------------------- 0101的Bit

以上都是由上向下传输或者是由下向上传输

 

 

 

2. TCP与UDP

基于TCP/IP协议,主要有俩种传输形式,一种是TCP,一种UDP

TCP(传输控制协议):面向链接 重传机制 确认机制 流量控制  (保证可靠)

UDP:面向无链接 低开销 传输效率高,速度快

2.1 TCP的三次握手与四次挥手

TCP因为传输数据,要和对端要先创建通道才能够传输数据,因此被称之为可靠的传输协议,传输数据以前须要创建通道,等通道创建成功后,发送数据片断,每发送一个数据片断,发送一个确认码ack,发送端只有在收到ack确认码才会发送下一个数据片断,不然会从新发送未被确认数据片断。因为要确认的东西不少,因此TCP的报头有20字节。这样TCP传输就很占用传输带宽。

如下图片就是三次握手以及四次挥手的过程,这个会后面网络编程中较大联系

2.2 UDP协议

UDP协议因为不须要和对端确认通道以及对方是否存在,只须要知道对端是谁就能够,因此UDP也被称之为不可靠传输协议。UDP协议只负责传送,不负责数据是否到达,因此低开销,传输速率高,UDP头部只有8字节。

2.3端口基础

端口范围在0----65535(2*16)

知名端口号(0----1023,其余软件禁止使用),

注册端口号(1024----49151,通常用于软件注册,不过一些知名的端口仍是建议不使用)

随机端口号(49152----65535,通常用于客户端软件,随机端口)

3.基于c/s结构的服务器客户端的实验

3.1基础知识点-socket

对于上面网络基础了解后,咱们能够这么想之后咱们本身敲代码了,那我是否是就须要记住这些几层协议,传输层,网络层具体作什么,这个时候就须要一个新的模块了,socket,python中处理网络编程相关的问题,Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有。这样的话,用户就不须要再次了解下面具体怎么实施,只须要知道怎么操做socket就行了,结构如图。

3.2.1socket的套接字

family(socket家族)

  • socket.AF_UNIX:用于本机进程间通信,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通信,能够经过建立一个本地的socket来完成
  • socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其余的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是不多被使用,或者是根本没有实现,全部地址家族中,AF_INET是使用最普遍的一个,python支持不少种地址家族,可是因为咱们只关心网络编程,因此大部分时候我么只使用AF_INET)

socket type类型

  • socket.SOCK_STREAM #for tcp
  • socket.SOCK_DGRAM #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 #废弃了
(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

服务器端套接字类型

  • s.bind() 绑定(主机,端口号)到套接字
  • s.listen() 开始TCP监听
  • s.accept() 被动接受TCP客户的链接,(阻塞式)等待链接的到来

用户端套接字类型

  • s.connect() 主动初始化TCP服务器链接
  • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公用套接字类型

  • s.recv() 接收数据
  • s.send() 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面经过实例解释)
  • s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
  • s.recvfrom() Receive data from the socket. The return value is a pair (bytes, address)
  • s.getpeername() 链接到当前套接字的远端的地址
  • s.close() 关闭套接字
  • socket.setblocking(flag) #True or False,设置socket为非阻塞模式,之后讲io异步时会用
  • socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
  • socket.getfqdn() 拿到本机的主机名
  • socket.gethostbyname() 经过域名解析ip地址

3.3 tcp创建链接的过程

4.实验

4.1实验一

目的:基于TCP创建一个简单的服务端与客户端,服务端可以接收客户端传输过来的值

服务器端
import socket

sk = socket.socket()#实例化对象
sk.bind(('127.0.0.1',8500))#对象绑定
sk.listen(3)#服务端监听

print('loading.....')
conn,addr = sk.accept()
msg = conn.recv(1024)
print(msg)

conn.close()
sk.close()
客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8500))#客户端链接

sk.send(b'123')

sk.close()

4.2实验二

目的:基于TCP实现客户端与服务器之间的能够退出的聊天程序

#服务器
import socket

sk = socket.socket()#实例化对象
sk.bind(('127.0.0.1',8500))#对象绑定
sk.listen()#服务端监听

print('loading.....')
conn,addr = sk.accept()
print(addr)
while True:
    msg = conn.recv(1024).decode()
    if msg =='q':break
    else:
        print(msg)
        msg1 = input('>>>').strip().encode()
        conn.send(msg1)
        if msg1 =='q':break

conn.close()
sk.close()
#客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8500))#客户端链接

while True:
    msg = input('>>>').strip().encode()
    sk.send(msg)
    if msg =='q':break
    ret = sk.recv(1024).decode()
    if ret =='q':break
    else:print(ret)

sk.close()

咱们来讲一下程序中系统的实现方法,对于咱们写的小程序,并非咱们直接创建链接,服务器端与客户端之间直接收发数据,真正的作法就是咱们把发送的数据交给操做系统,操做系统在调用物理硬件将数据发出,接收端也是发送信息交给操做系统让他从网卡这里获取收到的数据,传递给应用程序。

补充一个:服务器端接收数据中的conn是一个套接字对象,是一个基于TCP协议创建的一个连接

4.3实验三

目的:基于TCP实现简单的时间服务器

#服务器端
import time
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8500))
sk.listen()

conn,addr = sk.accept()
data = conn.recv(1024).decode()
time_date = time.strftime(data).encode()
conn.send(time_date)

conn.close()
sk.close()
#客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8500))

msg ='%Y.%m.%d %H:%M:%S'
sk.send(msg.encode())
date=sk.recv(1024).decode()
print(date)

sk.close()

4.4实验

基于TCP测试一次性发送多个字符串

#服务
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8500))
sk.listen()

conn,addr = sk.accept()
msg = conn.recv(1024).decode()
print(msg)

conn.close()
sk.close()
#客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8500))

sk.send(b'hello')
sk.send(b'world')
print('......')

sk.close()

咱们会发现服务端收到了一个b’helloworld’这么一个bytes类型的字符串,这个现象就叫作黏包现象

先解释一下黏包的产生:

6.5%20buffer%20

它的发生主要是由于socket缓冲区致使的,你的程序实际上无权直接操做网卡的,你操做网卡都是经过操做系统给用户程序暴露出来的接口,那每次你的程序要给远程发数据时,实际上是先把数据从用户态copy到内核态,这样的操做是耗资源和时间的,频繁的在内核态和用户态以前交换数据势必会致使发送效率下降, 所以socket 为提升传输效率,发送方每每要收集到足够多的数据后才发送一次数据给对方。若连续几回须要send的数据都不多,一般TCP socket 会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

仍是看上图,发送端能够是一K一K地发送数据,而接收端的应用程序能够两K两K地提走数据,固然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个总体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,所以TCP协议是面向流的协议,这也是容易出现粘包问题的缘由。而UDP是面向消息的协议,每一个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不一样的。怎样定义消息呢?能够认为对方一次性write/send的数据为一个消息,须要明白的是当对方send一条信息的时候,不管底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈如今内核缓冲区。

这里咱们说一下send与recv的区别

  • send:无论recv仍是send并非直接接受或者发送对方的数据,而是操做本身操做系统的内存,将数据copy一份给内存,不过并非一个send对应一个recv
  • recv:主要是有俩个过程wait data与copy data 俩个过程,wait data 时间是最长的,中间要通过网络传输,内存获取到数据将数据copy到应用层

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要仍是由于接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所形成的。

既然有问题,那就解决问题,既然为了解决这个问题,就会引入管道这个类

#服务端
import socket
import struct

sk = socket.socket()
sk.bind(('127.0.0.1',8500))
sk.listen()

conn,addr = sk.accept()
while True:
    send_msg = input('>>>').strip().encode('utf-8')
    msg_len = struct.pack('i',len(send_msg))
    conn.send(msg_len)
    conn.send(send_msg)


conn.close()
sk.close()
import socket
import struct

sk = socket.socket()
sk.connect(('127.0.0.1',8500))

data =sk.recv(4)
msg_len = int(struct.unpack('i',data)[0])
print(msg_len)
recive_len = 0
msg =b''
while recive_len<msg_len:
    date2 = sk.recv(5)
    msg +=date2
    recive_len+=5
print(msg.decode())

sk.close()

4.5实验

目的:基于TCP服务器端给客户端传文件,验证客户端接收的文件完整性,动态的显示接收进度

#服务器端
import socket
import struct
import json
sk = socket.socket()
sk.bind(('127.0.0.1',8500))
sk.listen()
conn,addr = sk.accept()

header ={'filename':r'D:\test.zip','file_size':922933359,'MD5':'6237eb2c55b34f87e856422896c1f440'}

header_json = json.dumps(header)#将头文件字典转换为json模式
header_bytes = header_json.encode('utf-8')#将json文件转换为bytes类型
header_len = struct.pack('i',len(header_bytes))#将头文件从管道发送过去
conn.send(header_len)
conn.send(header_bytes)
with open(header['filename'],'rb')as f1:
    while True:
        contact = f1.read(1024)
        if contact:
            conn.send(contact)
        else:
            break
print('文件传输完毕!')
conn.close()
sk.close()
#客户端
import socket
import struct
import json
import hashlib
import sys

sk = socket.socket()
sk.connect(('127.0.0.1',8500))

data =sk.recv(4)#接收头文件那四个字节
head_len = int(struct.unpack('i',data)[0])
head_bytes = sk.recv(head_len)#接收头文件

head_json = head_bytes.decode('utf-8')#将bytes类型的头文件解析成json格式
header = json.loads(head_json)#将json格式的文件反解成字典


def check_md5(file):#验证文件MD5
    ret = hashlib.md5()
    with open(file,mode='rb')as f2:
        while True:
            contect = f2.read(1024)#读取1024字节
            if contect:
                ret.update(contect)
            else:
                break
        return ret.hexdigest()

receive_num =0
with open('test.zip','wb')as f1:
    while receive_num < header['file_size']:
        contact = sk.recv(1024)
        if contact:
            f1.write(contact)
            receive_num += 1024
            float_rate =receive_num/header['file_size']
            rate = round(float_rate * 100, 2)
            sys.stdout.write('\r已下载:\033[1;32m{0}%\033[0m'.format(rate))#动态显示接收进度!
        else:
            break
print('文件下载成功!')
num =check_md5('test.zip')
if num ==header['MD5']:
    print('文件校验成功,文件完整')
else:
    print('文件校验失败,文件不完整')

sk.close()