Python基础__socket编程

1、计算机网络

1、OSI网络七层模型

osi七层模型

OSI模型描述:

1、应用层:一些终端的应用,比如说 ftp、web程序

2、表示层:主要是进行对接受的数据进行解释、加密与解密、压缩与解压缩

3、会话层:通过传输层(端口号:传输端口与接收端口)简历数据传输的通路

4、传输层:定义了一些传输数据的协议和端口号

5、网络层:主要将下层接收的数据进行IP地址的封装与解封装

6、数据链路层:主要将从物理层接收的数据进行MAC地址的封装与解封装

7、物理层:主要定义物理设备标准,如:网线的接口

2、网络通信要素:

  1. ip地址

    • 用来表示网络上一台独立的主机
    • ip地址=网络地址+主机地址(网络地址:用于识别主机所在的网络/网段,主机地址:用于识别该网络中的主机)
    • 特殊的ip地址:127.0.0.1(本地回环地址、保留地址),可用于简单的测试网卡是否故障,表示本机
  2. 端口号:

    • 用于表示进程的逻辑地址,不同的进程都有不同的端口标识。
    • 端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以都给这些网络应用程序都用数字进行标识,为了方便称呼这些数字,则这些数字成为端口。
  3. 传输协议

    • 通讯协议规则:

      • udp:User Datagram Protocol 用户数据协议

        特点:

        1、面向无连接:传输数据之前源端和目的端不需要建立连接。

        2、每个数据报的大小都限制在64k以内

        3、面向报文的不可靠协议(发送出去的数据不一定会接收到)

        4、传输速率快,效率高

        5、应用场景:邮局寄件、实时在线聊天、视频会议。。。

      • tcp:Transmission Control Protocol传输控制协议

        特点:

        1、面向连接:传输数据之前需要建立连接

        2、在连接过程中进行大量的数据传输

        3、通过“三次握手”的方式完成连接,是完全可靠协议

        4、传输速度慢,效率低

3、网络通讯步骤:

确定对端ip地址-->确定应用程序端口-->确定通讯协议

简单的来说:发送发利用应用软件将上层的应用程序产生的数据前后加上相应的层标识不断的往下层传输(封包过程),最终到达物理层通过看的见摸得着的物理层设备。

2、socke编程

1. 概念理解

要想理解socket,就要先来理解TCP,UDP协议

​ TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准。

​ 从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

  • 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
  • 传输层:TCP,UDP
  • 网络层:IP,ICMP,OSPF,EIGRP,IGMP
  • 数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的

​ 我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,我们经常把socket翻译为套接字,socket是在应用层和传输层(TCP/IP协议族通信)之间的一个抽象层,是一组接口,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

​ 应用程序两端通过“套接字”向网络发出请求或者应答网络请求。可以把socket理解为通信的把手(hand)

​ socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。socket的英文原义是“插槽”或“插座”,就像我们家里座机一样,如果没有网线的那个插口,电话是无法通信的。Socket是实现TCP,UDP协议的接口,便于使用TCP,UDP。

2. socket模块通讯流程

877318-20160721180727638-526428491.png

流程描述:

1、服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
2、服务器为socket绑定ip地址和端口号

3、 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开

4、 客户端创建socket

5、 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket

6、 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求

7、 客户端连接成功,向服务器发送连接状态信息

8、 服务器accept方法返回,连接成功

9、客户端向socket写入信息(或服务端向socket写入信息)

10、 服务器读取信息(客户端读取信息)

11、 客户端关闭

12、 服务器端关闭

# ----------------服务器端-----------------------
import socket
# 创建socket对象
sk = socket.socket()
print(sk)
# 设置创建socket连接的ip地址和端口号
address = ('127.0.0.1',8000)

# socket对象绑定ip地址和端口号
sk.bind(address)

# 设置监听几个连接
sk.listen(100)

#等待连接,阻塞
conn,addr = sk.accept()

messsages = conn.recv(1024)
print(str(messsages,'utf-8'))
conn.close()
sk.close()

# ----------------客户端-----------------------
import socket

# 客户端创建socket对象
sk = socket.socket()

address = ('127.0.0.1',8000)

# 客户端尝试连接服务器端
sk.connect(address)
messages = input('>>>:')

# 连接成功后,发送数据
sk.send(bytes(messages,'utf-8'))

sk.close()

#----------------编码---------------------
# author:Dman
"""
在python3中传输的类型必须是bytes类型(因为bytes更接近底层,更便于计算机识别)

python3.x中只有两种数据类型:
str:unicode
bytes:十六进制
----------------------------
str--->bytes
    s='中国'
    1、bytes(s,'utf8')
    2、s.encode('utf-8')  #python3编码(encode)的时候,会把数据转换成bytes类型
---------------------------------
bytes-->str
    b = bytes('中国')
    str(b,'utf8')
    b.decode()

***int类型无法直接转换成bytes类型
"""

s = 'hello 中国'


# 将str类型转换为bytes类型,两种方式
b = bytes(s, 'utf8')
b1 =  s.encode('utf8')
print(b,b1)

# 将bytes类型转换成str类型,两种方式
s1 = b1.decode('utf8')
s2 = str(b1,'utf8')
print(s1)
print(s2)

总结:
1、在python3中传输的类型必须是bytes类型(因为bytes更接近底层,更便于计算机识别)

2、python3.x中只有两种数据类型:str和unicode类型


bytes:十六进制

**------------->str--->bytes**
s='中国'

方法一:**bytes(s,'utf8')**

方法二:**s.encode('utf-8')**  #python3编码(encode)的时候,会把数据转换成bytes类型

**------------->bytes-->str**
 b = bytes('中国')
方法一: **str(b,'utf8')**
方法二: **b.decode()**

备注:int类型无法直接转换成bytes类型

3. socket模块其他相关方法

1.两组重要参数
> type=SOCK_STREAM #表示使用tcp
>
> type = SOCK_DGRAM #表示使用UDP
>
> family=AF_INET #表示服务器之间的通信(使用ipv4)
>
> family=AF_INET6 #表示服务器之间的通信(使用ipv6)
>
> family=AF_UNIX #表示不同unix进程之间的通信
>

  1. socket模块相关方法

    sk.bind(address)
    
      #s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
    
    sk.listen(backlog)
    
      #开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
    
          #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
          #这个值不能无限大,因为要在内核中维护连接队列
    
    sk.setblocking(bool)
    
      #是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
    
    sk.accept()
    
      #接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
    
      #接收TCP 客户的连接(阻塞式)等待连接的到来
    
    sk.connect(address)
    
      #连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
    
    sk.connect_ex(address)
    
      #同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
    
    sk.close()
    
      #关闭套接字
    
    sk.recv(bufsize[,flag])
    
      #接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
    
    sk.recvfrom(bufsize[.flag])
    
      #与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
    
    sk.send(string[,flag])
    
      #将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
    
    sk.sendall(string[,flag])
    
      #将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
    
          #内部通过递归调用send,将所有内容发送出去。
    
    sk.sendto(string[,flag],address)
    
      #将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
    
    sk.settimeout(timeout)
    
      #设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
    
    sk.getpeername()
    
      #返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    
    sk.getsockname()
    
      #返回套接字自己的地址。通常是一个元组(ipaddr,port)
    
    sk.fileno()
    
      #套接字的文件描述符
    

4. socket模块实战场景展示

  1. socket简单通讯,代码展示

    # author:Dman
    # date:2019/3/23
    #-------------服务器端
    import socket
    # 创建socket对象
    sk = socket.socket()
    print(sk)
    # 设置创建socket连接的ip地址和端口号
    address = ('127.0.0.1',8000)
    
    # socket对象绑定ip地址和端口号
    sk.bind(address)
    
    # 设置监听几个连接
    sk.listen(100)
    
    #等待连接,阻塞
    conn,addr = sk.accept()
    
    messsages = conn.recv(1024)
    print(str(messsages,'utf-8'))
    sk.close()
    
    #---------------客户端------
    import socket
    
    # 客户端创建socket对象
    sk = socket.socket()
    
    address = ('127.0.0.1',8000)
    
    # 客户端尝试连接服务器端
    sk.connect(address)
    messages = input('>>>:')
    
    # 连接成功后,发送数据
    sk.send(bytes(messages,'utf-8'))
    
    sk.close()
    
  1. socket实现一个客户端可以一直和服务器进行交互

    # author:Dman
    # date:2019/3/23
    #----------------服务器端----------
    '''
    实现一个用户可以一直和客户端进行交互
    '''
    import socket
    sk = socket.socket()
    
    address = ('127.0.0.1',8000)
    sk.bind(address)
    sk.listen(10)
    print('waiting....')
    
    
    
    while True:
        conn, addr = sk.accept()
        print('%s is connecting  and waitting for messages...' % addr[0])
    
        while True:
            try:
                data = conn.recv(1024)
            except Exception as e:
                pass
            if str(data,'utf-8') == 'exit':
                conn.close()
                print('客户端请求关闭连接')
                break
            print(str(data,'utf-8'))
            a = input('>>>')
            conn.send(bytes(a,'utf-8'))
    
    sk.close()
    
    #--------------客户端-------------
    import socket
    
    # 客户端创建socket对象
    sk = socket.socket()
    
    address = ('127.0.0.1',8000)
    
    # 客户端尝试连接服务器端
    sk.connect(address)
    
    while True:
    
        messages = input('>>>:')
        # 连接成功后,发送数据
        sk.send(bytes(messages,'utf-8'))
        data = sk.recv(1024)
        print(str(data,'utf-8'))
    
    sk.close()
    
  1. 实现一个服务器和多个客户端进行交互(不是同时进行交互,只是断开一个客户端,服务器可以继续接受其他客户端)

    # author:Dman
    # date:2019/3/23
    # author:Dman
    # date:2019/3/23
    
    #--------------服务器端-------------------
    import socket
    sk = socket.socket()
    
    address = ('127.0.0.1',8000)
    sk.bind(address)
    sk.listen(10)
    print('waiting....')
    
    
    
    while True:
        conn, addr = sk.accept()
        print('%s is connecting  and waitting for messages...' % addr[0])
    
        while True:
            try:
                data = conn.recv(1024)    #win客户端主动关闭的时候会报错
                if str(data, 'utf-8') == 'exit':
                    conn.close()
                    print('客户端请求关闭连接')
                    break
                print('--------', str(data, 'utf-8'))
                a = input('>>>')
                conn.send(bytes(a, 'utf-8'))    #win客户端主动关闭的时候会报错
            except Exception as e:
                conn.close()
                break
    
    sk.close()
    
    #-----------客户端------------------------
    import socket
    
    # 客户端创建socket对象
    sk = socket.socket()
    
    address = ('127.0.0.1',8000)
    
    # 客户端尝试连接服务器端
    sk.connect(address)
    
    while True:
    
        messages = input('>>>:')
        # 连接成功后,发送数据
        sk.send(bytes(messages,'utf-8'))
        data = sk.recv(1024)
        print(str(data,'utf-8'))
    
    sk.close()
    
  2. 实现远程执行命令,一个服务器端和多个客户端交互(非同时)

    # author:Dman
    # date:2019/3/23
    # author:Dman
    # date:2019/3/23
    #-----------------------服务器端----------
    '''
    实现远程执行简单命令,客户端可以收到server的命令结果,步骤:
    1、创建socket连接
    2、server可以循环接受socket连接
    3、接受socket的命令
    5、执行命令,发送命令返回结果的大小
    6、接受客户端的ok,解决黏包
    7、开始发送真正的数据。
    '''
    import socket
    import subprocess
    
    server = socket.socket()
    server.bind(('127.0.0.1',8000))
    server.listen(10)
    print('服务器启动,等待客户端连接....')
    
    while True:
        conn,addr = server.accept()
        print('%s 正在连接...' % addr[0])
        while True:
            try:
                command = conn.recv(1024)
            except Exception:
                print('客户端意外中断。。。')
                break
            if not command:break     # 服务器解决发来的命令为空的情况
    
            obj = subprocess.Popen(command.decode('utf8'),shell=True,stdout=subprocess.PIPE)
            # print(obj.stdout.read().decode('gbk'))
            command_bytes_gbk = obj.stdout.read()        #结果为win命令提示符的编码,默认命令提示符的编码为gbk
            command_bytes_len = len(command_bytes_gbk)
            print("命令的结果大小为:",command_bytes_len)
    
            conn.send(str(command_bytes_len).encode('utf8'))  #发送命令结果的大小,给客户端
    
            conn.recv(1024)       #解决黏包问题
            conn.sendall(command_bytes_gbk)            # 发送命令执行结果
    
        conn.close()
    server.close()
    
    #---------------客户端--------------
    """
    实现在远程服务器执行简单命令,并获取命令结果
    1、发送命令给服务器
    2、接受服务器发来命令执行结果的大小
    3、解决服务器黏包问题
    4、循环接受命令执行结果
    """
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8000))
    print('客户端启动...')
    
    while True:
        message = input('please input command>>>:').strip()
        # if not message:
        #     continue
        client.send(message.encode('utf-8'))   # 发送command给服务器
    
        data_byte_len=client.recv(1024)         #接受服务器发来的命令执行结果的大小。
        data_int_len = int(data_byte_len.decode('utf-8'))
        print("命令的结果大小为:",data_int_len)
        client.send('ok'.encode('utf-8'))   #解决服务器黏包的问题
    
    
        recevied_data = 0                      # 循环开始接受命令
        conten_data = bytes()
        while recevied_data != data_int_len:
            data =client.recv(1024)
            recevied_data += len(data)
            conten_data += data
    
        print(conten_data.decode('gbk'))
    
    client.close()
    
  1. 实现上传文件

    # author:Dman
    # date:2019/3/24
    #--------------------服务器端--------------------------------
    """
    实现文件上传
    """
    import socket
    import os
    
    server = socket.socket()
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    
    server.bind(('127.0.0.1',8000))
    server.listen(10)
    print('waitting for connection...')
    
    while True:
        conn, addr = server.accept()
        print('%s is connecting...' % addr[0])
        while True:
            try:
                command = conn.recv(1024)
            except Exception:
                print('客户端意外中断')
                conn.close()
                continue
    
            cmd,filename,file_size = str(command,'utf-8').split('|') # 接受命令并进行处理
    
            file_path = os.path.join(BASE_DIR,'file',filename) # 绝对路径拼接
    
            print(file_path)
    
            file_size = int(file_size)
            conn.sendall('ok'.encode('utf-8'))
    
    
            has_recevied_length = 0
    
    
            with open(file_path,'ab') as f:
                while has_recevied_length != file_size:
                    data = conn.recv(1024)
                    f.write(data)
                    has_recevied_length += len(data)
    
            print('数据已经保存在 %s' % (os.path.join(BASE_DIR,'file')))
    
    
    
    #----------------客户端----------------------------
    
    # author:Dman
    # date:2019/3/24
    import  socket
    import  os
    
    client_conn = socket.socket()
    
    client_conn.connect(('127.0.0.1',8000))
    
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    print(BASE_DIR)
    
    while True:
        inp = input(">>>: ").strip()
        cmd,path = inp.split('|')
    
        file_path = os.path.join(BASE_DIR,'file',path) #获取绝对文件路径
        filename = os.path.basename(file_path)   #获取文件名
    
        file_size = os.stat(file_path).st_size  #获取文件大小
        file_info = '%s|%s|%s' % (cmd,filename,file_size)
        client_conn.sendall(file_info.encode('utf-8'))
    
        client_conn.recv(1024)
    
        has_sent_length = 0
    
        with open(file_path, 'rb') as f:
            while has_sent_length != file_size:
                data = f.read(1024)     # 每次读取1024个字节
                client_conn.sendall(data)
                has_sent_length +=len(data)
    
        print('上传成功')
    

3、 socketserver模块

  • 模块介绍

    • socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。

    • Server类:它包含了五种server类,BaseServer、TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer。

      BaseServer:socketserver中服务类的基类

      TCPServer:使用TCP协议,该协议在客户端和服务器之间提供连续数据流。

      UDPServer:使用UDP协议,这些数据包在传输过程中可能无序到达或丢失的信息包,参数与TCPServer一样

      UnixStreamServer和UnixDatagramServer:这些不经常使用的类似于TCP和UDP,但使用Unix域套接字,它们在非Unix平台上不可用。参数与TCPServer相同。

      总结 :这五个server类的继承关系如下:

      1553575546622.png
  • RequestHandler类:需要自己去写,必须继承BaseRequestHandler父类,下面是BaseRequestHandler类的源码:

    class BaseRequestHandler:
    
        """Base class for request handler classes.
    
        This class is instantiated for each request to be handled.  The
        constructor sets the instance variables request, client_address
        and server, and then calls the handle() method.  To implement a
        specific service, all you need to do is to derive a class which
        defines a handle() method.
    
        The handle() method can find the request as self.request, the
        client address as self.client_address, and the server (in case it
        needs access to per-server information) as self.server.  Since a
        separate instance is created for each request, the handle() method
        can define arbitrary other instance variariables.
    
        """
    
        def __init__(self, request, client_address, server):
            self.request = request
            self.client_address = client_address
            self.server = server
            self.setup()
            try:
                self.handle()
            finally:
                self.finish()
    
        def setup(self):
            pass
    
        def handle(self):
            pass
    
        def finish(self):
            pass
    
  • socketserver模块使用步骤:

    1、First, you must create a request handler class by subclassing the BaseRequestHandlerclass and overriding its handle() method; this method will process incoming requests.

    2、Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.

    3、Then call the handle_request() orserve_forever() method of the server object to process one or many requests.

    4、Finally, call server_close() to close the socket.

    另外要想socket实现并发效果,必须使用下面的类

    #实现多进程
    class socketserver.ForkingTCPServer
    class socketserver.ForkingUDPServer
    # 实现多线程
    class socketserver.ThreadingTCPServer
    class socketserver.ThreadingUDPServer
    
  • 代码实例:

    # author:Dman
    # date:2019/3/25
    
    #----------------服务端-----------------------
    """
    使用socketserver实现一个多并发的服务端
    1、继承BaseRequestHandler,实现handle方法
    2、在handle方法中去实现我们要处理的业务逻辑
    3、创建ThreadingTCPServer实例
    4、调用serve_forever()方法。
    
    """
    
    import socketserver
    
    class MyServer(socketserver.BaseRequestHandler):
    
        def handle(self):
            print ("服务端启动...")
            while True:
                conn = self.request   # connection
                print (self.client_address)
                while True:
                    client_data=conn.recv(1024)
                    print (str(client_data,"utf8"))
                    print ("waiting...")
                    server_response = input('>>>')
                    conn.sendall(server_response.encode('utf-8'))
                conn.close()
    
    if __name__ == '__main__':
        server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)
        # print()
        server.serve_forever()
        
    #----------------客户端---------------------
    import socket
    ip_port = ('127.0.0.1',8091)
    sk = socket.socket()
    
    sk.connect(ip_port)
    print ("客户端启动:")
    while True:
        inp = input('>>>')
        sk.sendall(bytes(inp,"utf8"))
        if inp == 'exit':
            break
        server_response=sk.recv(1024)
        print (str(server_response,"utf8"))
    
    sk.close()
    

4、 思考

  1. udp和tcp的区别

    tcp需要三次握手,每次收到数据,都要发送应答,UDP不需要关心这些。

  2. 粘包的处理

    在两次send之间加一个recv,这样去解决。这样就会把所有数据都从缓冲区先全部发走,在发送别的。

你可能感兴趣的:(Python基础__socket编程)