一,软件开发的架构
两个程序之间的通讯的应用大致可以分为两种:
第一种:应用类,需要安装的桌面应用
第二种:web类,使用浏览器访问就可以直接使用的应用
应用的本质就是两个程序之间的通讯。
1,C/S架构
C(Client)/S(Server)即:客户端与服务端架构。客户端一把泛指客户端应用程序exe,程序需要先安装,才能运行对操作系统环境依赖较大。
2,B/S架构
B(Browser)/S(Server)即:浏览器端和服务器端架构,其实也是一种客户端。只需在浏览器上过请求HTTP请求服务端相关的资源。
解耦分治的思想:统一入口
二,网络基础
1,IP地址和IP协议:规定网络地址的协议叫IP协议,它定义的地址称为IP地址,采用V4版本即ipv4,规定网络地址由32位2进制表示。IP地址是一台机器在网络上的位置。
范围:0.0.0.0---255.255.255.255,一个IP地址通常写成四段十进制数:例172.16.10.1
本地的回环地址:127.0.0.1
总结一下,IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。
2,mac地址
head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址。
mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
3,arp协议:查询IP地址与mac地址的对应关系
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。
主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址。
收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
4,路由器
路由器(Router),是连接因特网中各局域网、广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径,按前后顺序发送信号。
路由器(Router)又称网关设备(Gateway)是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网。当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成。
路由器只接受源站或其他路由器的信息,属网络层的一种互联设备。
5,局域网
局域网:是指某一区域内多台计算机互联成的计算机组。 局域网是封闭型的,可以由办公室内的两台计算机组成,也可以由一个公司内的上千台计算机组成。
6,子网掩码
子网掩码:所谓子网掩码,就是表示网络特征的一个参数。它形式上等同于ip地址,是一个32位二进制数字,网络部分全为1,主机部分全为0
就是将某个IP地址划分分成网络地址和主机地址两部分。
比如,已知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.000000010 255255.255.255.0:11111111.11111111.11111111.00000000 AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0 结果都是172.16.10.0,因此它们在同一个子网络。
7,tcp协议和udp协议
端口:
端口,在计算机上每一个需要需要网络通信的程序都会开一个端口
在同一时间只会有一个程序占用一个端口,不可能在同一机器上,同一时间 有两个程序占用同一个端口
端口范围:0-65535
一般情况下用8000之后的端口
ip,确定唯一一台机器;端口:确定唯一的程序
ip+端口:找到唯一的一台机器上的唯一一个程序
TCP协议:
tcp协议:当应用程序通过tcp与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手之后,tcp将在两个应用程序之间建立一个全双工的通信。
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的半关闭(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]
udp协议:
当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。
当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
udp与tcp的对比:
tcp,传输控制协议,提供的面向连接、可靠的字节流服务,双方建立tcp连接之后,才能传输数据。
tcp提供超时重发,丢弃重复数据,可靠的,效率低。
udp,用户数据报协议,是一种无连接的协议,一个简单的面向数据报的运输层协议。不可靠,只是把数据传出去,并不能保证能到达目的地。传输数据报前不用建立连接,没超时重发等机制,孤传输速度很快。
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
8,互联网协议与osi模型
互联网协议按照功能不同分为osi七层/tcp/ip五层或tcp/ip四层
应用层
传输层 tcp与udp协议
网络层 IP协议
数据链路层 arp协议
物理层
三,socket(套接字)
1,socket层,socket是应用层与tcp/ip协议族通信的中间软件抽象层,它是一组接口。在设计模式中,socket其实就是一个门面模式。它把复杂的tcp/ip协议隐藏在socket接口后面,对用户来说一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
socket有两种,分别是基于文件型的和基于网络型的。
2,socket(类似于模块)的使用
(1),基于tcp协议的socket(tcp是基于链接的,必须先动服务器,然后再启动客户端取链接服务端)
import socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字 sk.listen() #监听链接 conn,addr = sk.accept() #接受客户端链接 ret = conn.recv(1024) #接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 sk.close() #关闭服务器套接字(可选)
import socket sk = socket.socket() # 创建客户套接字 sk.connect(('127.0.0.1',8898)) # 尝试连接服务器 sk.send(b'hello!') ret = sk.recv(1024) # 对话(发送/接收) print(ret) sk.close() # 关闭客户套接字
(1.1)多个客户端链接服务端(同一时间只能连一个,等一个断开才能接收到另一个客户端。)
import socket #tcp协议 sk = socket.socket() # 买手机 创建一个socket对象 sk.bind(('127.0.0.1',8080)) # 给server端绑定一个ip和端口 sk.listen() # py3.4 while True: conn,addr = sk.accept() # 获取到一个客户端的连接, 已经完成了三次握手建立了一个连接 # 阻塞 while True: msg = conn.recv(1024).decode('utf-8') # 阻塞,直到收到一个客户端发来的消息 print(msg) if msg == 'bye':break info = input('>>>') if info == 'bye': conn.send(b'bye') break conn.send(info.encode('utf-8')) # 发消息 conn.close() # 关闭连接 sk.close() # 关闭socket对象,如果不关闭,还能继续接收 # server client1 建立了长连接 # client2
import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: msg = input('>>>') if msg == 'bye': sk.send(b'bye') break sk.send(msg.encode('utf-8')) ret = sk.recv(1024).decode('utf-8') if ret == 'bye':break print(ret) sk.close()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: msg = input('client2:>>>') if msg == 'bye': sk.send(b'bye') break sk.send(('client2 :'+msg).encode('utf-8')) ret = sk.recv(1024).decode('utf-8') if ret == 'bye':break print(ret) sk.close()
(2)基于udp协议的socket(udp是无链接的,先启动哪一端都不会报错)
import socket sk=socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) msg,addr=sk.recvfrom(1024) print(msg.decode('utf-8')) sk.sendto(b'bye',addr) sk.close()
import socket sk=socket.socket(type=socket.SOCK_DGRAM) ip_port=(('127.0.0.1',8090)) sk.sendto(b'hi',ip_port) ret,addr=sk.recvfrom(1024) print(ret.decode('utf-8')) sk.close()
简单聊天程序:
import socket sk=socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) while True: #仅服务端有 msg,addr=sk.recvfrom(1024) print(msg.decode('utf-8')) if msg.decode('utf-8') == 'bye': break info = input('>>>>') if info=='bye': sk.sendto(b'bye', addr) break sk.sendto(info.encode('utf-8'),addr) sk.close()
import socket sk=socket.socket(type=socket.SOCK_DGRAM) ip_port=('127.0.0.1',8090) while True: info=input('<<<') if info=='bye': sk.sendto(b'bye',ip_port) break sk.sendto(info.encode('utf-8'),ip_port) ret,addr=sk.recvfrom(1024) if ret.decode('utf-8') == 'bye': break print(ret.decode('utf-8')) sk.close()
import socket sk=socket.socket(type=socket.SOCK_DGRAM) ip_port=('127.0.0.1',8090) while True: info=input('<<<') if info=='bye': sk.sendto(b'bye',ip_port) break sk.sendto(('第三个:'+info).encode('utf-8'),ip_port) ret,addr=sk.recvfrom(1024) if ret.decode('utf-8') == 'bye': break print(ret.decode('utf-8')) sk.close()
时间服务器:
import time import socket sk=socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) msg,addr=sk.recvfrom(1024) time_1=msg.decode('utf-8') time.strftime(time_1) sk.sendto((time.strftime(time_1)).encode('utf-8'),addr) sk.close()
import logging import socket logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='test.log', filemode='a') sk=socket.socket(type=socket.SOCK_DGRAM) ip_port=('127.0.0.1',8090) sk.sendto(('%Y-%m-%d %X').encode('utf-8'),ip_port) ret,addr=sk.recvfrom(1024) print(ret.decode('utf-8')) logging.info(ret.decode('utf-8')) sk.close()