网络编程
知识储备
1 # C/S架构
2 client<---->server
3 # B/S架构
4 browser<---->server
物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
数据链路层由来:单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思
数据链路层功能:定义了电信号的分组方式
以太网协议:
形成统一标准,以太网协议ethernet
ethernet规定
- 一组电信号构成一个数据包,叫做’帧‘
- 每一数据帧分成:包头head和数据data两部分
head包含:(固定18个字节)
- 源,6个字节
- 目,6个字节
- 数据类型,6个字节
data包含:(最短46字节,最长1500字节)
- 数据包的具体内容
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
网络层由来:有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址),ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼。
现在还需要一种手段来判定,必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,就采用路由的方式(向不同广播域/子网分发数据包)。
网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
传输层的由来:传输层以下都是由操作系统控制,网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,
那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。
传输层功能:建立端口到端口的通信
补充:端口范围0-65535,0-1023为系统占用端口
tcp协议:
可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
以太网头 | ip 头 | tcp头 | 数据 |
udp协议:
不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。
以太网头 | ip头 | udp头 | 数据 |
tcp报文
tcp三次握手和四次挥手
tcp建立的是双向链接
C -----------------------》 S
C <----------------------- S
C ---- syn=1 seq=x ----> S S ---- ack=1+x syn=1 seq=y ----> C C ---- ack=1+y ----> S
S ---- fin=1 ---->C C ---- ack=1 ----> S C ---- fin=1 ----> S S ---- ack=1 ----> C
应用层由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式
应用层功能:规定应用程序的数据格式。
例:TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了”应用层”。
什么是网络?
1、物理连接介质;
2、互联网协议。
MAC+IP:定位全世界一台独一无二的计算机
MAC+IP+PORT:定位全世界一台独一无二的计算机上的程序
但在程序中,因为操作系统配备了ARP协议,可实现IP和MAC的转换
总结:因此【IP+PORT】即可定位定位全世界一台独一无二的计算机上的程序
TCP UDP TCP与UDP基本区别 1.基于连接与无连接 2.TCP要求系统资源较多,UDP较少; 3.UDP程序结构较简单 4.流模式(TCP)与数据报模式(UDP); 5.TCP保证数据正确性,UDP可能丢包 6.TCP保证数据顺序,UDP不保证 UDP应用场景: 1.面向数据报方式 2.网络数据大多为短消息 3.拥有大量Client 4.对数据安全性无特殊要求 5.网络负担非常重,但对响应速度要求高 具体编程时的区别 1.socket()的参数不同 2.UDP Server不需要调用listen和accept 3.UDP收发数据用sendto/recvfrom函数 4.TCP:地址信息在connect/accept时确定 5.UDP:在sendto/recvfrom函数中每次均 需指定地址信息 6.UDP:shutdown函数无效 编程区别 通常我们在说到网络编程时默认是指TCP编程,即用前面提到的socket函数创建一个socket用于TCP通讯,函数参数我们通常填为SOCK_STREAM。即socket(PF_INET, SOCK_STREAM, 0),这表示建立一个socket用于流式网络通讯。 SOCK_STREAM这种的特点是面向连接的,即每次收发数据之前必须通过connect建立连接,也是双向的,即任何一方都可以收发数据,协议本身提供了一些保障机制保证它是可靠的、有序的,即每个包按照发送的顺序到达接收方。 而SOCK_DGRAM这种是User Datagram Protocol协议的网络通讯,它是无连接的,不可靠的,因为通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据。任何一方建立一个socket以后就可以用sendto发送数据,也可以用recvfrom接收数据。根本不关心对方是否存在,是否发送了数据。它的特点是通讯速度比较快。大家都知道TCP是要经过三次握手的,而UDP没有。 基于上述不同,UDP和TCP编程步骤也有些不同,如下: TCP: TCP编程的服务器端一般步骤是: 1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt(); * 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、开启监听,用函数listen(); 5、接收客户端上来的连接,用函数accept(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; 8、关闭监听; TCP编程的客户端一般步骤是: 1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4、设置要连接的对方的IP地址和端口等属性; 5、连接服务器,用函数connect(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; UDP: 与之对应的UDP编程步骤要简单许多,分别如下: UDP编程的服务器端一般步骤是: 1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、循环接收数据,用函数recvfrom(); 5、关闭网络连接; UDP编程的客户端一般步骤是: 1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 4、设置对方的IP地址和端口等属性; 5、发送数据,用函数sendto(); 6、关闭网络连接; TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。 UDP补充: UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。 TCP补充: TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。 TCP与UDP区别总结: 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付 3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) 4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 5、TCP首部开销20字节;UDP的首部开销小,只有8个字节 6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP套接字工作流程:
套接字程序编码:
1、client.connect() ----》server.accept(),此过程就实现封装三次握手过程
2、服务器端的conn套接字对象起到收发消息的作用,即可收消息,可发消息
socket服务端(单步)详细步骤说明:
1 import socket 2 3 # 买电话 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 # 默认socket.AF_INET 6 # socket.SOCK_STREAM称为流式协议(TCP),socket.SOCK_DGRAM为UDP协议 7 8 # 插手机卡 9 phone.bind(('127.0.0.1',8080)) # 手机卡里面关键是绑定自己的手机号,教室IP:192.168.12.112,端口,0-65535,0-1024给系统用 10 11 # 开机 12 phone.listen(5) # 同一时间请求数,开启半链接池,以后一般都是写在配置文件里,并不是只能建立5个链接, 13 print('start...') 14 15 # 等电话链接 16 conn,client_addr = phone.accept() # conn,套接字的收发消息对象,客户端client_addr(随机变) 17 print('链接来了:',conn,client_addr) 18 19 # 收发消息 20 msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况 21 print('客户端的消息:',msg) 22 conn.send(msg.upper()) 23 24 # 挂电话 25 conn.close() #回收系统资源,跟打开文件后关闭文件是一个道理 26 27 # 关机 28 phone.close()
服务端Sever(循环):
1 import socket 2 3 # 买电话 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 6 # 插手机卡 7 phone.bind(('127.0.0.1',8081)) 8 9 # 开机 10 phone.listen(5) 11 print('start...') 12 # 链接循环 13 while True: 14 conn,client_addr = phone.accept() 15 print('客户端:',client_addr) 16 17 # 收发消息,通信循环 18 while True: 19 try: 20 msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况 21 print('客户端的消息:',msg) 22 conn.send(msg.upper()) 23 except ConnectionResetError: 24 break # 此刻异常出现,conn是无意义对象,故不可用continue,而采用break 25 26 conn.close() 27 # 运行至此步,进行下一步链接 28 29 phone.close()
客户端Client:
(补充:程序结束后,回收程序资源的同时也要回收系统资源。最后应补充一段代码:client.close())
1 import socket 2 3 # 买电话 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 6 # 拨电话,服务端IP和端口 7 phone.connect(('127.0.0.1',8081)) 8 9 # 发消息 10 while True: 11 12 msg = input('>>>:').strip() 13 phone.send(msg.encode('utf-8')) 14 # 收消息 15 data = phone.recv(1024) 16 print(data.decode('utf-8'))
编写C/S架构的程序,实现远程执行命令:
#subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
1 import socket 2 import subprocess 3 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 phone.bind(('127.0.0.1',8082)) 6 phone.listen(5) 7 print('连线中...') 8 9 while True: 10 conn,client_addr = phone.accept() 11 print('建立连接客户端:',client_addr) 12 13 while True: 14 try: 15 cmd = conn.recv(1024) 16 print('客户端的消息:',cmd.decode('utf-8')) 17 obj = subprocess.Popen(cmd.decode('utf-8'), 18 shell=True, 19 stdout=subprocess.PIPE, 20 stderr=subprocess.PIPE 21 ) 22 stdout = obj.stdout.read() # 成功结果 23 stderr = obj.stderr.read() # 错误结果 24 25 conn.send(stdout+stderr) 26 27 except ConnectionResetError: 28 print('一个客户端中止链接') 29 break 30 conn.close() 31 32 phone.close()
1 import socket 2 import subprocess 3 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 phone.connect(('127.0.0.1',8082)) 6 7 8 while True: 9 cmd = input('请输入通信内容>>:').strip() 10 11 phone.send(cmd.encode('utf-8')) 12 msg = phone.recv(1024) 13 print(msg.decode('gbk')) # 服务器端返回命令行内容,需gbk解码 14 15 phone.close()
这里,会出现一个问题,当命令执行结果大于1024字节时,会有消息冗余问题。
在找到解决方案前,需了解,不管收发,其实数据都是先要经过应用程序内存和操作系统缓存的处理过程。
其次,tcp流协议有两个特点:
1,像水流一样,源源不断从源头向对方堆砌数据
2,negal算法:优化数据传输,将时间间隔比较短和数据比较小的数据预留
那么,现在只要知道对方要发多少数据,那么我就能知道如何判断是否收到全部数据
在此之前补充一下,使用stuct模块可高度定制化的报头数据
最后,解决粘包的方法见下:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2018/04/19 14:03 4 # @Author : MJay_Lee 5 # @File : Server.py 6 # @Contact : limengjiejj@hotmail.com 7 8 import socket 9 import subprocess 10 import struct 11 import json 12 13 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 14 phone.bind(('127.0.0.1',8082)) 15 phone.listen(5) 16 print('连线中...') 17 18 while True: 19 conn,client_addr = phone.accept() 20 print('建立连接客户端:',client_addr) 21 22 while True: 23 try: 24 cmd = conn.recv(1024) 25 print('客户端的消息:',cmd.decode('utf-8')) 26 obj = subprocess.Popen(cmd.decode('utf-8'), 27 shell=True, 28 stdout=subprocess.PIPE, 29 stderr=subprocess.PIPE 30 ) 31 stdout = obj.stdout.read() # 成功结果 32 stderr = obj.stderr.read() # 错误结果 33 34 print(len(stdout)+len(stderr)) # 打印出服务器端返回消息的长度 35 # conn.send(stdout+stderr) 36 37 38 #1、发送数据长度,发送数据长度,制作报头 39 header_dic = { 40 'total_size':len(stdout) + len(stderr), 41 'md5':'asdasdqasdasd', 42 'filename':'test.txt' 43 } 44 header_json = json.dumps(header_dic) 45 header_bytes = header_json.encode('utf-8') 46 47 # 2 先发送报头的长度 48 header_size = len(header_bytes) 49 conn.send(struct.pack('i',header_size)) 50 51 #3、发送报头 52 conn.send(header_bytes) 53 54 #4、发送真实数据 55 conn.send(stdout) 56 conn.send(stderr) 57 58 except ConnectionResetError: 59 print('一个客户端中止链接') 60 break 61 conn.close() 62 phone.close()
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2018/04/19 14:03 4 # @Author : MJay_Lee 5 # @File : Client.py 6 # @Contact : limengjiejj@hotmail.com 7 8 import socket 9 import subprocess 10 import struct 11 import json 12 13 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 14 client.connect(('127.0.0.1',8082)) 15 16 17 while True: 18 cmd = input('请输入通信内容>>:').strip() 19 client.send(cmd.encode('utf-8')) 20 #1、先收报头长度 21 header_size = struct.unpack('i',client.recv(4))[0] 22 #2、接收报头 23 header_bytes = client.recv(header_size) 24 #3、解析报头 25 header_json = header_bytes.decode('utf-8') 26 header_dic = json.loads(header_json) 27 print(header_dic) 28 29 30 total_size = header_dic['total_size'] 31 print(total_size) 32 #4、根据报头内的信息,收取真实的数据 33 recv_size = 0 34 res = b'' 35 while recv_size < total_size: 36 recv_data = client.recv(1024) 37 res += recv_data 38 recv_size += len(recv_data) 39 print(res.decode('gbk')) 40 41 client.close()
补充:客户端连接个数超出服务器设置的backlog值,出现:“ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。”,解决方案如下“优化客户端终极版”
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2018/04/23 14:08 4 # @Author : MJay_Lee 5 # @File : client.py 6 # @Contact : limengjiejj@hotmail.com 7 8 import socket 9 import time 10 import json 11 import struct 12 13 while True: 14 try: 15 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 client.connect(('127.0.0.1', 8080)) 17 break 18 except ConnectionRefusedError: 19 print('等待3秒') 20 time.sleep(3) 21 22 23 while True: 24 cmd = input('请输入通信内容:').strip() 25 if not cmd:continue 26 client.send(cmd.encode('utf-8')) 27 # 获得报头长度 28 header_size = struct.unpack('i',client.recv(4))[0] 29 # 获取报头 30 header_bytes = client.recv(header_size) 31 # 解析报头 32 header_json = header_bytes.decode('utf-8') 33 header_dic = json.loads(header_json) 34 35 total_size = header_dic['header_len'] 36 recv_size = 0 37 res = b'' 38 39 while recv_size < total_size: 40 recv_data = client.recv(1024) 41 res += recv_data 42 recv_size += len(recv_data) 43 44 print('来自服务器的消息:',res.decode('gbk')) 45 46 client.close()
相比较于UDP,TCP之所以可靠,是因为TCP的工作原理(每发一个数据,得到回应,才会清掉系统缓存里的数据。),而非多一个链接。
场景:多用于查询场景,可靠性要求不高
注意:UDP协议发消息的时候,有效传输最大51bytes,否则就容易出现丢包
套接字常识:WEB服务端端口80,DNS端口53
三个状态