某个设备上的应用怎么跟其他设备进行交换,这就是这次的内容。
要学习这次内容,我们需先了解OSI七层网络模型、TCP/IP三次握手、UDP
1、OSI七层网络模型、TCP/IP、三次握手、UDP
我们大部分开发软件和系统都属于应用层的范畴
TCP三次握手
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.
UDP
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。
2、Socket套接字
套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
Socket套接字是处在应用层和运输层之间的,能够帮我们抽象出TCP或UDP的协议,能够帮我们指定IP地址的端对端的发送。
用户进程(应用程序)是通过创建一个Socket端口,然后才能与网络进行通信,去连接到网络上其他的用户进程。
python提供了socket模块、socketserver模块
服务端与客户端在网络上交换数据时,都需要创建Spcket才能进行数据交换。Socket也分为服务端Socket和客户端Socket
,
3、TCP/IP协议、各层次通信协议简图
4、B/S架构和C/S架构
S: Sever 服务端
CS: Client/Server(客户机/服务器)结构
BS: Browser/Server(浏览器/服务器)结构
标准网络编程用的都是C/S架构,比如QQ、微信、端游
B/S 浏览器:ie,火狐,谷歌(Google Chrome)
B/S架构减轻了工作量,但是B/S架构只能在浏览器,http协议,html等等这一系列标准下去运行,做一些特殊处理是有一定的障碍,而且有一些私有化数据的安全性要求的话,浏览器更容易被攻破。所以更需要一些安全漏洞的检查。但是B/S架构的优点还是远远大于缺点的,所以应用广泛。
5、TCP和UDP
都是传输协议
服务端server& 客户端client
服务端一般就是装在机房里的服务器,客户端就是装在自己电脑和手机上的应用程序。
TCP是安全的,或者说传输相对UDP是安全的,它经过三次握手之后这个链接是可以被信任的,确保客户端和服务端的通信时可以完成的。
UDP就是相当于我就对着你扔出去一些消息,你不需要告诉我你收没收到,所以这个连接相对来说是不安全的,也就是说客户端发送给服务端的消息,你收没收到,客户端是不管的。
所以要根据不同的应用场景来选择不同的传输协议,若更在意系统的性能,对收没收到消息反而不是很在意,此时就应该选择UDP。
如果是局域网,可以用UDP来做一些应用;互联网的话还是使用TCP。
TCP的Server端和Client端
TCP建立链接的一些要求,有发就有收,收发必须相等
Client事先知道Server的ip地址和端口
6、Python网络编程模块
socket、 socketserver
服务器端要先运行
1、将小写转化为大写
服务端
import socket # 引入socket模块
hostaddress = ("127.0.0.1", 8888) # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序
# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个socket对象
sk.bind(hostaddress) # 绑定
sk.listen(5) # 监听,默认是1
print("启动socket服务,等待客户端连接...")
conn, clientaddress = sk.accept() # 阻塞状态,等到客户端一个连接的对象
# bytes 字节数组
data = conn.recv(1024).decode() # 接受客户端发来的内容,长度,decode方法转化为字符串
print("接收到客户端 %s 发送来的信息 : %s" % (clientaddress, data))
res = data.upper() # 发来的字母转换为全大写
# str
conn.sendall(res.encode()) # 将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
conn.close()
客户端
import socket
serveraddress = ("127.0.0.1", 8888)
sk = socket.socket()
sk.connect(serveraddress) # 连接ip地址
sss = 'abc'
sk.sendall(sss.encode()) # 转成bytes字节数组才能发送
answer = sk.recv(1024).decode() # 应答,decode转换成字节
print("收到服务器应答:%s" % answer)
sk.close() # 关闭连接
服务端指定了监听的端口,客户端不需要指定,因为客户端只需要知道服务器端是谁,自己随便找一个未被占用的端口来跟服务器通信,最后close释放掉就可以了,操作系统会帮我们处理掉
2、代码优化一下,具有多次收发消息的能力
引入循环,while True(无限循环或者死循环)
服务端
import socket # 引入socket模块
hostaddress = ("127.0.0.1", 8888) # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序
# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个socket对象
sk.bind(hostaddress) # 绑定
sk.listen(5) # 监听,默认是1
print("启动socket服务,等待客户端连接...")
conn, clientaddress = sk.accept() # 阻塞状态,等到客户端一个连接的对象
while True:
# bytes 字节数组
data = conn.recv(1024).decode() # 接受客户端发来的内容,长度,decode方法转化为字符串
if data == 'exit':
print("客户端发送完成, 断开连接。")
break
print("接收到客户端 %s 发送来的信息 : %s" % (clientaddress, data))
res = data.upper() # 处理,发来的字母转换为全大写
# str
conn.sendall(res.encode()) # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
conn.close()
客户端
import socket
serveraddress = ("127.0.0.1", 8888)
sk = socket.socket()
sk.connect(serveraddress) # 连接ip地址
while True:
sss = input('发送内容:').strip() # 去空格
sk.sendall(sss.encode()) # 转成bytes字节数组才能发送
if sss == 'exit':
print('客户端退出连接.')
break
answer = sk.recv(1024).decode() # 应答,decode转换成字节
print("收到服务器应答:%s" % answer)
sk.close() # 关闭连接
但是只能连接一个客户,其他客户连接能发送但是没有响应
3、继续优化,服务端可以接受多个客户端的请求
引入多线程 同时干多件事
import socket # 引入socket模块
import threading # 引入多线程
def deal(link, client):
print('新线程开始处理客户端 %s: %s 的数据请求' % client)
while True:
# bytes 字节数组
data = link.recv(1024).decode() # 接受客户端发来的内容,长度,decode方法转化为字符串
if data == 'exit':
print("客户端发送完成, 断开连接。")
break
print("接收到客户端 %s 发送来的信息 : %s" % (client, data))
res = data.upper() # 处理,发来的字母转换为全大写
# str
link.sendall(res.encode()) # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
link.close()
hostaddress = ("127.0.0.1", 8888) # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序
# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个socket对象
sk.bind(hostaddress) # 绑定
sk.listen(5) # 监听,默认是1
print("启动socket服务,等待客户端连接...")
while True:
conn, clientaddress = sk.accept() # 阻塞状态,等到客户端一个连接的对象
xd = threading.Thread(target=deal, args=(conn, clientaddress)) # 创建一个线程,方法deal,参数 连接,客户端地址
xd.start() # 运行
可以处理并发场景,不同的客户端有不同的端口号
当连接的客户端断开,服务端也并不会停下来,其他的客户端还可以继续连接
实际写代码中,服务端比较复杂
7、socketserver
实例代码中起名字不能和包名一样,所以起名字可以加一个下划线以便于区分,避免写错
出现了一次错误,端口不能同时使用(上一个例子中的server和这次socketserver端口都是8888)
import socketserver
class MyHandler(socketserver.BaseRequestHandler): # 定义一个类,里面是它的父类
def handle(self): # handle是重写父类的一个方法,作用是子进程里处理数据
while True:
data = self.request.recv(1024).decode()
if data == 'exit':
print("客户端发送完成, 断开连接。")
break
print("接收到客户端 %s 发送来的信息 : %s" % (self.client_address, data)) # 地址
res = data.upper() # 处理,发来的字母转换为全大写
# str
self.request.sendall(res.encode()) # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
self.request.close()
# 定义完,开始调用
hostaddress = ("127.0.0.1", 8888)
server = socketserver.ThreadingTCPServer(hostaddress, MyHandler) # 传入地址参数,处理类
print("启动socket服务,等待客户端连接...")
server.serve_forever() # 启动服务器的监听
8、UDP的server和client
上面的例子都是TCP的,此来介绍UDP的server和client
服务端
import socket
hostaddress = ('127.0.0.1', 8888)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 第二个参数代表的是以UDP的方式创造监听
sk.bind(hostaddress)
print("启动udp socket服务,等待客户端数据...")
while True:
data = sk.recv(1024).decode()
print("udp服务器接收到客户端的数据: %s" % data)
if data == 'exit':
print("客户端请求退出。")
break
sk.close()
客户端
import socket
serveraddress = ("127.0.0.1", 9999)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
sss = input('[udp]发送内容:').strip()
sk.sendto(sss.encode(), serveraddress) # sendto是udp方式的发送,不需要建立连接,直接往ip地址扔
if sss == 'exit':
print('客户端退出。')
break
sk.close()