zoukankan      html  css  js  c++  java
  • Python成长之路 socket网络编程

    Socket网络编程

    socket通常被称作"套接字",应用程序通过"套接字"向网络发出请求或者应答网络请求,是主机或一台计算机上的进程可以通信。

    socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,服务器和客户端各自维护一个"文件",在建立连接打开后,各自的"文件"可以被对方读取和可以向自己的"文件"写入内容 。通讯结束时,关闭各自的"文件"。socket是实现TCP,UDP协议的接口,便于使用TCP、UDP。

    socket简单的例子

    服务器(server)

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    
    server = socket.socket()  # 声明socket类型,同时创建socket实例
    server.bind(('localhost', 8888))  # 为socket实例绑定IP地址和端口号。
    
    server.listen(5)  # 启动监听,等待客户端的接入请求
    
    while True:
        print('等待客户端接入...')
        conn, addr = server.accept() # Accept阻塞,直到有客户端连接进来
    client_data = conn.recv(1024).decode('utf-8')  #  接收客户端的信息
    print('客户端的消息:',client_data) 
    conn.send('已连接'.encode('utf-8'))  # 向客户端发送消息
    server.close() # 关闭连接

    客户端 (client)

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    
    client = socket.socket()  # 创建一个实例
    client.connect(('localhost', 8888))  # 连接到服务器
    
    
    client.send('请求连接'.encode())  # 发送信息给服务器
    
    client_data = client.recv(1024).decode('utf-8')  # 接受服务器反馈的信息
    print(client_data)
     client.close()  # 关闭连接

    Socket对象常用的方法

    服务器端的方法:

    • s.bind() #绑定IP地址和端口(host,port)到套接字,在AF_INET下以元组(host,port)的方式表示
    • s.listen() #开启TCP监听,backlog指定在拒绝连接之前,操作系统的最大挂起数量,至少1一般都为5
    • s.accept() # 被动等待TCP客户端连接,默认为阻塞,等待有客户端连接,程序才会向下走

    客户端的方法:

    • s.connect() #主动初始化TCP服务器连接,一般address的格式化为元组(host,port),如果连接错误,返回socket.error错误
    • s.connect() # connect()函数的扩展版本,出错时犯会错误代码,不会抛出异常

    公用的方法:

    • s.recv()  #接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略
    • s.send() #发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
    • s.sendall() #完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
    • s.recvfrom() #接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址
    • s.sendto() #发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
    • s.close() #关闭套接字
    • s.getpeername() # 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port) 这个方法以及下面的方法我是没用所以就不详细说了,了解一下就好
    • s.getsockname() #返回套接字自己的地址。通常是一个元组(ipaddr,port)   
    • s.setsockopt(level,optname,value ) #设置给定套接字选项的值。  
    • s.getsockopt(level,optname,value ) # 返回套接字选项的值   
    • s.settimeout(timeout)  # 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
    • s.gettimeout() # 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
    • s.fileno() #返回套接字的文件描述符。
    • s.setblocking(flag) #如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
    • s.makefile() #创建一个与该套接字相关连的文件

    使用socket实现简单的远程输入系统命令返回结果值

    服务器

    import socket
    import os
    
    server = socket.socket()
    server.bind(('localhost', 8888))
    server.listen()
    print('等待客户端接入')
    while True:
        conn, addr = server.accept()
        print('new conn', addr)
        while True:
            data = conn.recv(1024).decode("utf-8")
            if not data:
                print("客户端断开")
                break
            print("执行指令:", data)
            cmd_res = os.popen(data).read().encode("utf-8")
            if len(cmd_res) == 0:
                cmd_res = '输入的命令不合法'.encode('utf-8')
            conn.send(str(len(cmd_res)).encode('utf-8'))  # 向客户端发送它要接受的数据的大小
            print(str(len(cmd_res)))
            check_res = conn.recv(1024)  # 主要是为了解决粘包的问题
            conn.send(cmd_res)
    server.close()

    客户端

    #!/usr/bin/env python
    #-*-coding:utf-8-*-
    
    import socket
    
    client = socket.socket()
    client.connect(('localhost', 8888))
    while True:
        cmd = input('>>>').strip().encode("utf-8")
        if cmd == 'exit'.encode('utf-8'):
            break
        if len(cmd) == 0:
            continue
        client.send(cmd)
        len_res = client.recv(1024).decode("utf-8")
        client.send(bytes('ok',encoding='utf8'))  # 主要是为了在数据太多的时候发生粘包的情况
        # print('返回数据的大小', len_res) 查看要接受多少数据
        received_num = 0
        received_data = ''
        while received_num != int(len_res):  # 主要是实现数据在一次返回中全部接收完毕
            recv_num = client.recv(1024)
            received_num += len(recv_num)
            received_data += str(recv_num)
        print(eval(received_data).decode('utf-8'))
        # cmd_res = client.recv(1024).decode("utf-8")
        # print(cmd_res)
    
    client.close()

    粘包;主要是为了解决上面的check_res和下面的返回的数据粘在一起,导致我们的结果不正确。所以在发送数据大小后立马实现接收客户端对数据大小的确认,这样就会是数据产生粘包。

    在上面创建socket实例的时候(socket.socket())后面的括号有三个参数

    第一个参数:地址簇

    • socket.AF_INET         代表的是使用IPV4(默认)
    • socket.AF_INET6            代表的是IPv6

    第二个参数:类型

    • socket.SOCK_STREAM       使用的是TCP(默认)
    • socket.SOCK_DGRAM         使用的是UDP

    第三个参数:协议

    • 0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

     下面的列子是根据上面的例子实现远程下载

    FTPserver

    #!/usr/bin/env python
    #-*-coding:utf-8-*-
    
    import socket
    import os,subprocess
    import hashlib
    
    server = socket.socket()
    server.bind(('localhost', 8888))
    server.listen()
    print('等待客户端接入')
    while True:
        conn, addr = server.accept()
        print('new conn', addr)
        while True:
            data = conn.recv(1024).decode("utf-8")
            if not data:
                print("客户端{}断开".format(addr))
                break
            cmd, filename = data.split()
            if os.path.isfile(filename):
                f = open(filename, 'rb')
                m = hashlib.md5()
                file_size = str(os.stat(filename).st_size).encode('utf-8')
                conn.send(file_size)
                ack = conn.recv(1024)  # 粘包
                for line in f:
                    m.update(line)
                    conn.send(line)
                f.close()
                conn.send(m.hexdigest().encode('utf-8'))
            # cmd_res = subprocess.getoutput(data).encode('utf-8')
            # cmd_len = str(len(cmd_res)).encode('utf-8')
            # conn.send(cmd_len)
    
            # ack = conn.recv(1024)  # 粘包
            # conn.send(cmd_res)
    server.close()
    client
    #!/usr/bin/env python
    #-*-coding:utf-8-*-
    
    import socket, hashlib
    
    client = socket.socket()
    client.connect(('192.168.132.66', 8888))
    while True:
        cmd = input('>>>').strip().encode("utf-8")
        if cmd == 'exit'.encode('utf-8'):
            break
        if len(cmd) == 0:
            continue
        if cmd.decode('utf-8').startswith('get'):
            client.send(cmd)
            file_size = int(client.recv(1024).decode('utf-8'))
            client.send(b'ok')
            filename = cmd.decode('utf-8').split()[1]
            f = open(filename + '.new','wb')
            m = hashlib.md5()
            received_data = 0
            while received_data != file_size:
                if file_size - received_data > 1024:
                    size = 1024
                else:
                    size = file_size - received_data
                data = client.recv(size)
                f.write(data)
                received_data += len(data)
                m.update(data)
            else:
                print('file recv done',received_data,file_size)
                received_md5 = client.recv(1024).decode('utf-8')
                tota_md5 = m.hexdigest()
                print('发送',received_md5)
                print("接收",tota_md5)
            f.close()
       
    
    client.close()

    以上的实例中,客户端必须排队与服务器进行通讯,只有当正在通讯的客户端断开连接才能够到下一个客户端通讯,下面我们将说道多个客户端可以同时和服务器通讯,也就是客户端并发。

    多并发服务器

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    import socketserver  # 实现并发的需要的模块
    
    
    class MyTCPHandler(socketserver.BaseRequestHandler):
    
        def handle(self):
            while True:
                try:
                    self.data = self.request.recv(1024).strip().decode("utf-8")
                    # self.data 为接收到的信息并转换为utf-8的格式
                    print("{} wrote:".format(self.client_address[0]))
                    # self.client_address[0]表示的是客户端的IP地址与socket的addr是一样的
                    # self.client_address[1]表示的是客户端的端口号与socket的port是一样的
                    print(self.data)
                    if not self.data:
                        break
                    # 上面的判断是客户端自动结束程序关闭连接通道的判断
                    self.request.send(self.data.upper().encode('utf-8'))
                except ConnectionResetError as e:
                    print("erro", e)
                    break
                # 上面的错误处理是直接断开程序做的判断捕获的异常
    if __name__ == "__main__":
        HOST, PORT = "localhost", 8888  # 服务器绑定的IP和端口号
        server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
        # 这个实现多并发的关键就在ThreadingTCPServer()的方法实现了可以同时和多个会话
        # 传入端口地址和我们新建的继承自socketserver模块下的BaseRequestHandler类 实例化对象
        server.serve_forever()  # 通过调用对象的serve_forever()方法来激活服务端

     客户端就用简单的客户端就好

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    import socket
    client = socket.socket()
    client.connect(("localhost", 8888))
    
    while True:
        msg = input(">>>").strip()
        if not msg:
            continue
        if msg == 'exit':
            break
        client.send(msg.encode("utf-8"))
        received = client.recv(1024)
        print(received.decode("utf-8"))
    
    client.close()

     上面的就是一些基础的socket的应用,可以在上面的基础上深入的使用socket。这些是关于我对socket的简单的初期认识。

  • 相关阅读:
    Spring Boot 入门
    门罗币(MONERO)钱包生成教程
    数据库 一对多,多对多 表设计
    使用nginx+lua脚本读写redis缓存
    在Spring MVC中使用注解的方式校验RequestParams
    MySQL命名、设计及使用规范《MySQL命名、设计及使用规范》
    Mycat分表分库
    Go在Ubuntu 14.04 64位上的安装过程
    Android获取设备屏幕宽高像素值的两个方法
    php_curl.dll libssh2.dll 始终无法加载的原因 及解决办法
  • 原文地址:https://www.cnblogs.com/yang-China/p/8302780.html
Copyright © 2011-2022 走看看