Python基础----Socket编程规范及底层原理(一)

今天先给大家讲讲什么是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
  1. UDP协议:是user datagram protocol即用户数据报协议的一种简称,是OSI参考模型中的一种无连接传输协议,其提供面向事物的不可靠信息传输服务。在网络中与TCP协议一样是用来处理数据包的,但是UDP有不提供数据包分组、组装和不能对数据包进行排序,即不保证数据的完整准确的传输。

    TCP协议:是transmission control protocol即传输控制协议的一种简称,是一种面向连接的、可靠的、基于字节流的运输层的通信协议。

      接下来我们主要介绍两者的区别及其优缺点:

        1、TCP为面向连接的(如打电话需要先通过拨号建立连接),UDP是无连接的(如写信,只需知道对方地址即可把信发送出去。)

        2、TCP提供可靠的服务(即通过TCP连接传送的数据,无差错、不丢失、不重复,且按序到达,如打电话能保证对方能够完整的得到自己的消息;)UDP则是尽最大的努力交付,不保证可靠交付。(如写信,我只管将芯发送出去,但是不能保证对方一定能收到,也无法保证信件在传输的过程中不被损坏或者串改等。) 

    1.      3.TCP默认会使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。说白了就是会把小数据合并接收发送,这就造成了粘包现象

                     4、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或者广播通信。

            5、每一条TCP连接只能是点对点的 ;而UDP支持一对多,多对一和多对多的交互通信。(就类似于电话(TCP)和广播(UDP)。)

           6、TCP对系统资源的要求较多,而UDP对系统资源要求较少。



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暂且讲到这里,明天继续深入底层原理!觉得不错可以点个赞支持

猜你喜欢

转载自blog.csdn.net/Lzs1998/article/details/87472217