day18-网络编程基础(一)

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

1.基础知识

2.基于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

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

1.5 TCP与UDP

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

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

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

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

TCP由于传输数据,要和对端要先建立通道才可以传输数据,所以被称之为可靠的传输协议,传输数据之前需要建立通道,等通道建立成功后,发送数据片段,每发送一个数据片段,发送一个确认码ack,发送端只有在收到ack确认码才会发送下一个数据片段,否则会重新发送未被确认数据片段。由于要确认的东西很多,所以TCP的报头有20字节。这样TCP传输就很占用传输带宽。

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

1.2 OSI的七层协议

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

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

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

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

传输层 (4)-------------------------------------------------- 建立主机端到端的连接

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

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

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

一般567整合为应用程序,1234为数据流层

1.3 常用的TCP/IP的五层协议

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

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

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

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

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

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

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

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

1.4 TCP与UDP

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

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

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

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

TCP由于传输数据,要和对端要先建立通道才可以传输数据,所以被称之为可靠的传输协议,传输数据之前需要建立通道,等通道建立成功后,发送数据片段,每发送一个数据片段,发送一个确认码ack,发送端只有在收到ack确认码才会发送下一个数据片段,否则会重新发送未被确认数据片段。由于要确认的东西很多,所以TCP的报头有20字节。这样TCP传输就很占用传输带宽。

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

1.5.2 UDP协议

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

1.5.3端口基础

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

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

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

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

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

2.1基础知识点-socket

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

2.1.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地址

2.1.2 tcp建立连接的过程

2.2实验一

目的:建立一个简单的服务端与客户端,服务端能够接收客户端传输过来的值

服务器端
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()

2.3实验二

目的:实现客户端与服务器之间的可以退出的聊天程序

#服务器
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协议建立的一个链接

2.4实验三

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

#服务器端
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()
2.5实验

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

#服务
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()

2.6实验

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

#服务器端
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()

猜你喜欢

转载自www.cnblogs.com/gbq-dog/p/10306616.html