文章目录
一、网络基础
1、OSI七层模型
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
(1)物理层功能就是用网线等传输介质去传输0101一堆的二进制信号。
(2)数据链路层对0101信号进行分组,遵循以太网协议。一组电信号叫做一个数据帧。数据帧=head +data。本层是广播通信,靠mac地址进行识别主机。
(3)网络层是靠IP地址及子网掩码计算出相应的子网,根据一系列的路由规则找到该子网。
(4)传输层找到该主机后,依靠端口号找到对应的程序。是端对端的传输。其中,0-1023为系统端口号,1024-65535是可以自己用的端口号。该层遵循协议为TCP和UDP。
(5)应用层功能:规定应用程序的数据格式
2、arp协议
arp协议为数据链路层协议。
发包步骤:
(1)计算是否在一个广播域
(2)arp协议:
①在一个广播域中:
②不在一个广播域中:
3、tcp三次握手和四次挥手
二、socket 抽象层
1、什么是socket
socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。
2、socket分类
(1)基于文件类型的套接字家族 AF_UNIX
(2)基于网络类型的套接字家族 AF_INET。在网络编程中,我们只使用AF_INET。
3、数据传输流程
4、基于TCP的套接字代码示例
服务端:
from socket import *
ip_port=("192.168.1.2",8000)
back_log = 5 #定义半链接池大小
buffer_size = 1024 #每次从缓冲区接收的最大字节数
tcp_server = socket(AF_INET,SOCK_STREAM) #创建套接字对象,tcp协议要选SOCK_STREAM
tcp_server.bind(ip_port) #把ip地址和端口号绑定到套接字
tcp_server.listen(back_log) #监听链接
while True:
print("服务器开始运行:")
conn,addr = tcp_server.accept() #与客户端的connect对应,等待触发三次握手,建立双向链接
print("双向链接是:",conn)
print("客户端地址是:",addr)
while True:
data = conn.recv(buffer_size) #等待接受客户端send的数据
if not data:
break
print("客户端发来的消息是:",data.decode("utf-8"))
conn.send(data.upper()) #向客户端发送消息
conn.close() #关闭链接,即触发四次挥手
tcp_server.close() #关闭socket对象
客户端:
from socket import *
ip_port=("192.168.1.2",8000)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port) #尝试连接服务器
while True:
try:
msg = input(">>>").strip()
if not msg:
continue
tcp_client.send(msg.encode("utf-8")) #向服务端发送数据
print("客户端已经发送消息:")
data = tcp_client.recv(buffer_size) #接受服务端发来的数据
print("收到服务端发来的消息:",data.decode("utf-8"))
except KeyboardInterrupt:
break
tcp_client.close()
5、基于UDP的套接字代码示例
服务端:
from socket import *
ip_port = ("192.168.1.2", 8000)
buffer_size = 1024
udp_server = socket(AF_INET, SOCK_DGRAM) # 创建一个服务器的套接字,SOCK_DGRAM表示数据报
udp_server.bind(ip_port) # 把ip地址和端口号绑定到套接字
while True:
data, addr = udp_server.recvfrom(buffer_size) #从客户端接收数据
print(data)
print(addr)
udp_server.sendto(data.upper(),addr) #向客户端发送数据
udp_server.close() # 关闭服务器套接字
客户端:
from socket import *
ip_port = ("192.168.1.2",8000)
buffer_size = 1024
udp_client = socket(AF_INET,SOCK_DGRAM)
while True:
msg = input(">>>").strip()
udp_client.sendto(msg.encode("utf-8"),ip_port) #向服务端发送数据
data,addr=udp_client.recvfrom(buffer_size) #从服务端接收数据
print(data.decode("utf-8"))
udp_client.close() # 关闭客户端套接字
三、小实验
1、基于UDP创建时间同步服务器
服务端:
from socket import *
import time
ip_port = ("192.168.1.2", 8000)
buffer_size = 1024
udp_server = socket(AF_INET, SOCK_DGRAM) # 创建一个服务器的套接字,UDP选SOCK_DGRAM,表示数据报
udp_server.bind(ip_port) # 把ip地址和端口号绑定到套接字
while True:
data, addr = udp_server.recvfrom(buffer_size)
print(data)
if not data:
fmt = "%Y-%m-%d %X"
else:
fmt = data.decode("utf-8")
back_time = time.strftime(fmt)
udp_server.sendto(back_time.encode("utf-8"), addr)
udp_server.close()
客户端:
from socket import *
ip_port = ("192.168.1.2", 8000)
buffer_size=1024
udp_client=socket(AF_INET,SOCK_DGRAM)
while True:
msg = input(">>>").strip()
udp_client.sendto(msg.encode("utf-8"),ip_port)
data,addr = udp_client.recvfrom(buffer_size)
print("ntp服务器的标准时间是",data.decode('utf-8'))
udp_client.close()
2、基于UDP模拟xshell
服务端:
from socket import *
import subprocess
ip_port = ("192.168.1.2",8000)
back_log = 5
buffer_size = 1024
udp_server = socket(AF_INET,SOCK_DGRAM)
udp_server.bind(ip_port)
while True:
cmd,addr = udp_server.recvfrom(buffer_size)
res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
#这三个PIPE的内存空间是不同的
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
'''
subprocess.Popen的结果的编码是以当前所在的系统为准的,如果是windows,
那么res.stdout.read()读出的就是GBK编码的字节流,在接收端需要用GBK解码
'''
if not cmd_res:
cmd_res = "执行成功".encode("utf-8")
udp_server.sendto(cmd_res,addr)
客户端:
from socket import *
ip_port = ("192.168.1.2",8000)
back_log = 5
buffer_size = 1024
udp_client = socket(AF_INET,SOCK_DGRAM)
while True:
cmd = input(">>>").strip()
if not cmd: continue
if cmd == "q": break
udp_client.sendto(cmd.encode("utf-8"),ip_port)
cmd_res,addr = udp_client.recvfrom(buffer_size)
print("命令的执行结果是:",cmd_res.decode("utf-8"))
udp_client.close()
四、粘包
1、什么是粘包
由于套接字的接收端是从自己的缓存中取数据,但是它并不知道取多少数据,造成前后几次的数据混在一起。
2、产生粘包的情况
(1)在发送端,发送的数据比较小,间隔比较短,tcp协议会对其进行优化,在缓冲中,合并成一个包,一起发送。
(2)在接收端,数据没有被取完,缓冲区剩下的数据和下一次的数据混在一起。
(3)只有TCP协议会存在粘包现象,因为它是面向流的,各条数据之间无边界。UDP并不会产生粘包,因为它是面向消息的,接收端缓冲区采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),即面向消息的通信是有消息保护边界的。
客户端:
from socket import *
ip_port = ("127.0.0.1",8000)
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
tcp_client.send("hello".encode("utf-8"))
tcp_client.send("world".encode("utf-8"))
tcp_client.send("CRTao".encode("utf-8"))
tcp_client.close()
服务端1:
from socket import *
ip_port = ("127.0.0.1",8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
conn,addr = tcp_server.accept()
data1 = conn.recv(1024)
print("data1",data1)
data2 = conn.recv(5)
print("data2",data2)
data3 = conn.recv(5)
print("data3",data3)
conn.close()
tcp_server.close()
'''输出结果
data1 b'helloworldCRTao'
data2 b''
data3 b''
'''
服务端2:
from socket import *
ip_port = ("127.0.0.1",8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
conn,addr = tcp_server.accept()
data1 = conn.recv(1)
print("data1",data1)
data2 = conn.recv(5)
print("data2",data2)
data3 = conn.recv(5)
print("data3",data3)
conn.close()
tcp_server.close()
'''输出结果
data1 b'h'
data2 b'ellow'
data3 b'orldC'
'''
3、粘包的解决方法
可以在数据前加个包头,写明数据长度。
服务端:
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,addr=tcp_server.accept()
print('新的客户端链接',addr)
while True:
#收
try:
cmd=conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的运行结果cmd_res
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
err=res.stderr.read()
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
#发
if not cmd_res:
cmd_res='执行成功'.encode('gbk')
length=len(cmd_res)
data_length=struct.pack('i',length) # i表示整型,固定4个字节。struct.pack将整型length直接打包成字节流形式
conn.send(data_length)
conn.send(cmd_res)
except Exception as e:
print(e)
break
客户端:
from socket import *
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd=input('>>: ').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
#解决粘包
length_data=tcp_client.recv(4) # 取数据包头
length=struct.unpack('i',length_data)[0]
recv_size = 0
recv_msg = b''
while recv_size < length:
recv_msg += tcp_client.recv(buffer_size) #取实际的数据
recv_size = len(recv_msg)
print('命令的执行结果是 ',recv_msg.decode('gbk'))
tcp_client.close()
五、补充
客户端套接字函数:
s.connect_ex() :connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数:
s.sendall() :发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.setsockopt() :设置指定套接字的参数
例如,重启服务端时可能会遇到报错:Address already in ues,这个是由于服务端仍然存在四次挥手的time_wait状态在占用地址。解决方法:
#加入一条socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #这句要在bind前添加加
phone.bind(('127.0.0.1',8080)
六、小实验:通讯前验证客户合法性
服务端:
from socket import *
import hmac,os
secret_key = b"lumaxia"
def conn_auth(conn):
'''
认证客户端链接
'''
print("开始验证新链接的合法性")
msg = os.urandom(32) # 成一个包含32字节随机数据的字节串
conn.sendall(msg)
h = hmac.new(secret_key, msg, "sha256") # 创建了一个HMAC对象,第一个参数为加盐,第三个参数为指定加密算法
digest = h.digest() # 获取计算得到的HMAC值
respone = conn.recv(len(digest))
return hmac.compare_digest(respone,digest) # 比较两个哈希值是否相等
def data_handler(conn, bufsize=1024):
'''
处理数据
'''
if not conn_auth(conn):
print("该链接不合法,关闭")
conn.close()
return
print("链接合法,开始通信")
while True:
data = conn.recv(bufsize)
if not data:break
conn.sendall(data.upper())
def server_handler(ip_port, bufsize, backlog=5):
'''
只处理链接
'''
tcp_socket_server = socket(AF_INET, SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(backlog)
while True:
conn,addr = tcp_socket_server.accept()
print("新链接[%s:%s]" %(addr[0], addr[1]))
data_handler(conn, bufsize)
if __name__ == "__main__":
ip_port = ("127.0.0.1",8000)
bufsize = 1024
server_handler(ip_port,bufsize)
客户端1(合法):
from socket import *
import hmac,os
secret_key = b"lumaxia"
def conn_auth(conn):
'''
验证客户端到服务端的链接
'''
msg = conn.recv(32)
h = hmac.new(secret_key,msg,"sha256")
digest = h.digest()
conn.sendall(digest)
def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET,SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)
while True:
data = input(">>>").strip()
if not data:continue
if data == "quit":break
tcp_socket_client.sendall(data.encode("utf-8"))
respone = tcp_socket_client.recv(bufsize)
print(respone.decode("utf-8"))
tcp_socket_client.close()
if __name__ == "__main__":
ip_port = ("127.0.0.1",8000)
bufsize=1024
client_handler(ip_port,bufsize)
客户端2(不合法,没有验证函数):
from socket import *
def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET,SOCK_STREAM)
tcp_socket_client.connect(ip_port)
while True:
data = input(">>>").strip()
if not data:continue
if data == "quit":break
tcp_socket_client.sendall(data.encode("utf-8"))
respone = tcp_socket_client.recv(bufsize)
print(respone.decode("utf-8"))
tcp_socket_client.close()
if __name__ == "__main__":
ip_port = ("127.0.0.1",8000)
bufsize=1024
client_handler(ip_port,bufsize)
客户端3(不合法,加盐错误):
from socket import *
import hmac,os
secret_key = b"lumaxia111"
def conn_auth(conn):
'''
验证客户端到服务端的链接
'''
msg = conn.recv(32)
h = hmac.new(secret_key,msg,"sha256")
digest = h.digest()
conn.sendall(digest)
def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET,SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)
while True:
data = input(">>>").strip()
if not data:continue
if data == "quit":break
tcp_socket_client.sendall(data.encode("utf-8"))
respone = tcp_socket_client.recv(bufsize)
print(respone.decode("utf-8"))
tcp_socket_client.close()
if __name__ == "__main__":
ip_port = ("127.0.0.1",8000)
bufsize=1024
client_handler(ip_port,bufsize)