网络编程-SOCKET开发
网络编程架构分类
B/S架构
B指的是web(网页),S指的是Server(服务端软件)
C/S架构
C指的是Client(客户端软件),S指的是Server(服务端软件)
OSI七层模型
OSI七层模型设计的目的
是成为一个所有计算机厂商都能实现的开放网络模型,来克服使用众多私有网络模型所带来的困难和低效性。
TCP/IP五层模型:
应用层(表示层、会话层)http协议
大概是OS操作系统,系统软件等用户层面的。
传输层(TCP/UDP协议、端口、四层路由器、四层交换机)
建立端口到端口的通信,有两种传输方式
TCP协议:
TCP是全双工的通信方式,可靠传输,速度慢,对传递的数据的长短没有限制,只要不得到确认,就重新发送数据报,直到收到确认。
TCP的三次握手和四次挥手
SYN::同步标志(请求连接)
ACK:确认标志
FIN:结束标志
UDP协议:
UDP无需连接,不可靠,速度快,传输内容长度有限制。
网络层(IP协议、路由器、三层交换机)
IP协议
IP v4、IP v6
IP地址根据网络ID的不同分为五种类型,分别为A、B、C、D、E类地址
- A类:1.0.0.0-126.0.0.0
- B类:128.0.0.0-191.255.255.255
- C类:192.0.0.0-223.255.255.255
- D类:用于多点广播)
- E类:保留
*特殊:0.0.0.0-当前主机 255.255.255.255-当前子网的广播地址 127.0.0.1-本机地址,又称回环地址。
数据链路层(ARP协议、MAC地址相关、网卡、交换机)
ARP协议:地址解析协议,确定目标物理地址
MAC地址:机器唯一标识
物理层(网线)
TCP/IP的传输(Socket)
socket是应用层与TCP/IP协议族通信的中间软件抽象层,相当于一组接口。引用此接口可以实现TCP连接。
socket server端实例代码:
import socket #导入socket接口 receive = socket.socket() receive.bind(('127.0.0.1', 9999)) #此处的127.0.0.1为IP地址,9999为端口号 receive.listen() #开始TCP监听 conn, addr = receive.accept() #被动接收TCP客户端的连接,(阻塞)等待连接。 while True: conn.send('请输入用户名:'.encode('utf-8')) ret_user = conn.recv(1024).decode('utf-8') conn.send('请输入密码:'.encode('utf-8')) ret_psw = conn.recv(1024).decode('utf-8') if ret_user == 'zhao' and ret_psw == '123': conn.send('登录成功'.encode('utf-8')) break else: conn.send('用户名或密码输入错误'.encode('utf-8')) conn.close() #关闭套接字 receive.close()
socket client端实例代码:
import socket receive = socket.socket() receive.connect(('127.0.0.1', 9999)) #连接IP地址为127.0.0.1,端口为9999的主机 while True: print(receive.recv(1024).decode('utf-8')) user = input('>>>') receive.send(user.encode('utf-8')) print(receive.recv(1024).decode('utf-8')) psw = input('>>>') receive.send(psw.encode('utf-8')) ret = receive.recv(1024).decode('utf-8') if ret == '登录成功': print(ret) break else: print(ret) receive.close()
公共用途的socket函数:
s.recv() 接收数据
s.send() 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 收到的内容为 内容+IP地址
s.close() 关闭套接字
s.getpeername() 连接到当前套接字的远端的地址
socket.setblocking(flag) #True or False,设置socket为非阻塞模式,以后讲io异步时会用
socket.getaddrinfo(host, port, family=0, type=0, proto=0,flags=0)返回远程主机的地址信息
socket.getfqdn() 拿到本机的主机名
socket.gethostbyname() 通过域名解析ip地址
黏包现象的解决
黏包现象
首先,黏包现象只出现在TCP传输中,由于某些原因经过TCP连续发送的信息在很短的时间内某个阶段粘连在一起发送,接收方接收到的是一条消息。
造成黏包的原因
1. 在发送端由于两条消息发送的间隔时间很短,且两条消息本身也很短,在发送之前被合成了一条消息。
2. 在接收端由于接收不及时导致两条先后到达的信息在接收端黏在了一起。
黏包的本质
信息与信息之间没有边界,且无法解决,因为TCP协议是流式传输。
解决黏包问题
struct模块:
把任意长度的数字变成固定的4个字节。
l 简单形式(先发送数据长度,再发送数据)
l 相对规范并复杂的形式(把所有想发送的数据信息放在字典里,发送字典长度,发送字典,发送数据)
struct模块使用示例:
发送: import struct ret=struct.pack(‘i’,10028) #这里的’i’代表将int型10028打包 sk.send(ret) 接收: num=sk.recv(4) num=struct.unpack(‘I’,ret)[0] #这里的’i’代表将ret中的内容解压为int型,必须加[0],因为它传过来的是元组。 msg=conn.recv(num).decode(‘utf-8’)
UDP的传输
socket.SOCK_DGRAM #UDP传输
实例:
server端:
import socket while True: receive = socket.socket(type=socket.SOCK_DGRAM) receive.bind(('0.0.0.0', 9999)) while True: msg, addr = receive.recvfrom(1024) ret = msg.decode('utf-8') if ret.upper() == 'Q': receive.sendto(bytes('您已断开连接!'.encode('utf-8')), addr) print('对方已与您断开连接!') break elif ret == '您已断开连接!': print(ret) break else: print(ret, addr) s = input('>>>').encode('utf-8') receive.sendto(bytes(s), addr) receive.close()
client端:
import socket receive = socket.socket(type=socket.SOCK_DGRAM) while True: addr = input('输入要连接的ip地址:') addr_port = int(input('输入端口号:')) receive.connect((addr, addr_port)) while True: n = input('>>>').encode('utf-8') receive.sendto(bytes(n), (addr, addr_port)) msg = receive.recv(1024) ret = msg.decode('utf-8') if ret.upper() == 'Q': receive.sendto(bytes('您已断开连接!'.encode('utf-8')), (addr, addr_port)) print('对方已与您断开连接!') break elif ret == '您已断开连接!': print(ret) break else: print(ret) receive.close()