今天先给大家讲讲什么是Socket,明白其底层实现原理是非常重要的,不明白底层的话写出来的代码会非常low,能否明白底层实现是正规军和杂牌军的重要区别之一,好了,不废话了,开始干!
一.Socket
1.为什么要socket?
可能很多人都听说过C/S架构,即client/server架构(客户端/服务端架构),而C/S架构有硬件C/S架构(打印机),软件C/S架构(如百度网站就是服务端,而我的浏览器就是客户端);而我们学习socket就是为了实现C/S架构,在一定程度上也可以说为了实现网络通信;
2.什么是socket?
socket是应用层与传输层(TCP与UDP)之间的软件抽象层,其作用相当于一个接口,用户只需要调用该接口去组织数据,以符合TCP/UDP协议。
假如把OSI比作教室,应用层就是教室里的某个学生,网络层IP就是教室的位置,链接层就是学生的位置。
注:也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序,而程序的pid是同一台机器上不同进程或者线程的标识。
想了解socket就得了解TCP与UDP协议!
- TCP与UDP
-
UDP协议:是user datagram protocol即用户数据报协议的一种简称,是OSI参考模型中的一种无连接传输协议,其提供面向事物的不可靠信息传输服务。在网络中与TCP协议一样是用来处理数据包的,但是UDP有不提供数据包分组、组装和不能对数据包进行排序,即不保证数据的完整准确的传输。
TCP协议:是transmission control protocol即传输控制协议的一种简称,是一种面向连接的、可靠的、基于字节流的运输层的通信协议。
接下来我们主要介绍两者的区别及其优缺点:
1、TCP为面向连接的(如打电话需要先通过拨号建立连接),UDP是无连接的(如写信,只需知道对方地址即可把信发送出去。)
2、TCP提供可靠的服务(即通过TCP连接传送的数据,无差错、不丢失、不重复,且按序到达,如打电话能保证对方能够完整的得到自己的消息;)UDP则是尽最大的努力交付,不保证可靠交付。(如写信,我只管将芯发送出去,但是不能保证对方一定能收到,也无法保证信件在传输的过程中不被损坏或者串改等。)
-
3.TCP默认会使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。说白了就是会把小数据合并接收发送,这就造成了粘包现象
-
4、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或者广播通信。
5、每一条TCP连接只能是点对点的 ;而UDP支持一对多,多对一和多对多的交互通信。(就类似于电话(TCP)和广播(UDP)。)
6、TCP对系统资源的要求较多,而UDP对系统资源要求较少。
- UDP协议下socket的实现流程
- TCP协议下socket的实现流程
-
TCP协议的通讯流程
- 这里有一篇关于TCP协议的通讯流程的文章,里面详细解答了整个具体流程的实现,写的不错,值得一看
- https://blog.csdn.net/m0_38121874/article/details/81990457
3.接下来我们先看一下简单的基于TCP协议的简单的服务端和客户端
TCP服务端
import socket
#了解为啥是可靠
# 1、基于TCP创建监听套接字
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
#这已经可以解决端口重用问题
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 2、绑定本地终端地址
phone.bind(('127.0.0.1',8000)) #绑定电话卡
# 3、监听
phone.listen(1) #开机------指定半链接池backlog个数,防止洪水攻击,底层就是在tcp三次握手那里设置
print("---->1")
# 4、接受,获取客户端地址并且创建服务套接字
conn,addr = phone.accept()#等电话-----数据
print("----->")
# 5、接收发送数据
msg=conn.recv(1024) # 收消息------
print("客户端发来",msg)
conn.send(msg)#发消息
#断链接四次握手
## 6、关闭套接字
conn.close() #挂雕
phone.close() #关机
TCP客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8000))
info = input()
phone.send(info.encode("utf-8")) #网络传输都是字节传输,所以要转为字节
data = phone.recv(1024)
print("收到服务端发来的消息:",data)
注意: 接收rec(buffer_size)发送send()是在内核态内存进行,buffer_size表示应用要从内核态内存取数据的大小,应用程序再从内核态取数据,TCP服务端无法接收空信息,这是因为客户端内核态内存接受到空信息不send到服务端,所以服务端会卡住,所以编程时注意处理空信息。
用户态的应用程序可以通过三种方式来访问内核态的资源:
1)系统调用
2)库函数
3)Shell脚本
凡是底层实现都是在内核态内存实现,socket消息机制也是如此。
注意:TCP协议rec在自己这端的缓冲区为空时,阻塞;
UDP协议recvfrom在自己这端的缓冲区为空时,就收到一个空。
底层原理在后面几篇给大家详解!
4.基于TCP协议的远程控制程序
服务端
import subprocess
from socket import *
ip_port = ('127.0.0.1',8000) #ip端口
back_log = 5 #最大连接个数
buffer_size = 1024 #一次接受字节数
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
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) #接收到的cmd命令
if not cmd: #linux系统下客户端断开时服务端会一直接受空信息,win下会报错
break
print("接收命令:",cmd)
#运行shell端口程序
res=subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE) #打开shell窗口并获取相关信息,PIPE是管道,不加stdout参数的话直接输出到屏幕
err=res.stderr.read() #读取shell输出信息
if err: #cmd命令错误
cmd_res=err
else:
cmd_res=res.stdout.read()
if not cmd_res:#无输出信息
cmd_res="success".encode("gbk")
conn.send(cmd_res) #因为shell输出信息本身是字节,所以不用转义
except ConnectionResetError: #客户端断开连接会报异常
break
conn.close() #客户端关闭
tcp_server.close() #服务端关闭
客户端
'''远程操作'''
#注意,由于tcp优化算法自身的问题,会出现粘包现象,后面会重点说明粘包的解决办法
from socket import *
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd=input(">>")
if not cmd:
continue
if cmd == 'quit':
break
tcp_client.send(cmd.encode("utf-8"))
cmd_res = tcp_client.recv(buffer_size)
#print("执行结果:",cmd_res.decode("utf-8"))
print("执行结果:",cmd_res.decode("gbk"))#shell输出格式是gbk
tcp_client.close()
5.基于UDP协议的远程控制程序
服务端
from socket import *
import subprocess
ip_port = ('127.0.0.2',8000)
back_log = 5
buffer_size = 1024
udp = socket(AF_INET,SOCK_DGRAM)
udp.bind(ip_port)
while True:
cmd,addr=udp.recvfrom(buffer_size)
if not cmd: # linux系统下客户端断开时服务端会一直接受空信息,win下会报错
break
print("命令:", cmd)
try:
res = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE) # 打开shell窗口并获取相关信息,PIPE是管道,不加stdout参数的话直接输出到屏幕
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
if not cmd_res:
cmd_res = "success".encode("gbk")
udp.sendto(cmd_res,addr)
except ConnectionResetError:
break
udp.close()
客户端
from socket import *
import subprocess
#UDP服务端没有链接,故没有listen,accept
ip_port = ('127.0.0.2',8000)
buffer_size = 1024
udp=socket(AF_INET,SOCK_DGRAM)
while True:
cmd=input(">>:").strip()
if cmd == 'quit':#退出程序
break
udp.sendto(cmd.encode("utf-8"),ip_port)
data,addr=udp.recvfrom(buffer_size)
print("recever-->",data.decode("gbk"))
udp.close()
socket暂且讲到这里,明天继续深入底层原理!觉得不错可以点个赞支持