zoukankan      html  css  js  c++  java
  • 网络编程|套接字(socket模块)

      网络编程是计算机之间的数据交互。数据传输的大致过程是,计算机A与计算机B通信。计算机A从本机的应用层,传输层,网络层,数据链路层和物理链路层发出信息,通过网络,传输到计算机B,通过计算机B的物理链路层,数据链路层,网络层,传输层到达应用层的应用程序。这也说明,通信本质上应用程序之间的数据交互。

      数据从应用层到物理链路层有很多协议,想要发出信息,就必须遵守这些协议的规范。套接字socket模块就帮助开发者解决这中问题。

      套接字有两种其中,一种是文件类型的套接字:AF_UNIX,一种是网络类型的套接字:AF_INET。文件类型的套接字可以应用在Linux系统,Linux系统一切皆文件。使用python经行网络编程,使用更多的是AF_INET网络类型的套接字。

    TCP通信

    socket通信

      通信代码如下图。整个通信先开启服务端,服务端运行到接受客服端链接时暂停,等待客户端发来的信号开始执行之后的程序。这时启动客户端,客户端发送信息,服务端接受到后从对象中取出数据,最大1024个字节,然后打印hello回复信息。关闭通信。客服端也一样,收到信息。进行打印,关闭通信。其中服务端有一个连接池的概念,这个链接池限制链接客户端的个数。

    # 服务端
    import socket     # 调用socket模块
    server = socket.socket()   # 创建一个套接字对象
    server.bind(('192.168.12.183', 8080))  # 服务端自己的IP和端口 IPv4即可,端口自己配置,在8000以上最好
    server.listen(5)               # 监听链接, 称便连接池
    conn, addr = server.accept()    # 接受客服端链接,也可视为使双向连接通道建立完成。返回的conn是对象客服端套接字对象,addr是地址
    
    data = conn.recv(1024)           # 数据接受
    print(data)                     # 打印接收数据
    conn.send(b'word!')             # 回复信息
    conn.close()                     # 断开通道
    server.close()                   # 关闭通信
    >>>b'hello'
    
    #客户端
    import socket
    client = socket.socket()
    client.connect(('192.168.12.183', 8080))           # 链接服务端,服务端IP与端口
    
    client.send(b'hello')                            # 发送数据
    data = client.recv(1024)                         # 数据接受
    print(data)                                       # 打应数据
    
    client.close()                                    # 关闭通道
    >>>b'word!'

      在通信的时候一般都是可以经行循环通信。在上面的基础上可以改进此通信。使服务端可以随时经行通信。

    # 服务端
    import socket
    server = socket.socket()
    server.bind(('192.168.12.183', 8080))  # 服务端自己的IP和端口
    server.listen(5)
    
    conn, addr = server.accept()    # 建立通道 在此基础上可以经行数据传输
    while True:
    
        data = conn.recv(1024)
        print(data)
    
        conn.send(b'word!')
    
    # 客户端
    import socket
    client = socket.socket()
    
    client.connect(('192.168.12.183', 8080))
    while True:
        msg = input('>>>').strip()
        client.send(msg.encode('utf-8'))
        data = client.recv(1024)
        print(data)

      连接循环。对输入和接受的数据进行逻辑处理是程序更加健壮。

    # 服务端
    import socket
    server = socket.socket()
    server.bind(('192.168.12.183', 8080))  # 服务端自己的IP和端口
    server.listen(5)
    while True:
        conn, addr = server.accept()    # 建立通道 在此基础上可以经行数据传输
        while True:
            try:
                data = conn.recv(1024)
                print(data)
                if len(data) == 0: break   # 客服端异常退出后会循环打印b'',这个时候需要做个判断
                conn.send(b'word!')
            except ConnectionResetError:
                break
    
        conn.close()
    # 客户端
    import socket
    client = socket.socket()
    
    client.connect(('192.168.12.183', 8080))
    while True:
        try:
            msg = input('>>>').strip()
            if not msg: continue
            client.send(msg.encode('utf-8'))
            data = client.recv(1024)
            print(data)
        except ConnectionResetError as e:
            print(e)
            break

       在使用TCP通信时,但多个数据包连续(时间间隔比较短时会出现黏包)发送时,接受方会将多个包同时接受以先后顺序,队列的方式放在一起。接收方会无法将多个文件经行分离。这是一种黏包现象。

    # 服务端
    import socket
    
    server = socket.socket()
    server.bind(('127.0.0.1', 8081))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        data = conn.recv(1024)
        print(data.decode('utf-8'))
        conn.send(b'hello')
        conn.send(b'hello')
        conn.send(b'hello')
        conn.send(b'hello')
        conn.send(b'hello')
    >>>aaa
    # 客户端
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8081))
    
    while True:
        msg = input('>>>').encode('utf-8')
        client.send(msg)
        data = client.recv(1024)
        print(data)
        data = client.recv(1024)
        print(data)
        data = client.recv(1024)
        print(data)
        data = client.recv(1024)
        print(data)
        data = client.recv(1024)
        print(data)
    b'hello'
    b'hellohellohello'
    b'hello'
    黏包现象

      解决黏包现象可以用报头的方式解决这种问题。利用报头固定大小,携带数据大小的属性。告诉接收者发送到数据有多大,按照大小接受。

    # 客户端
    import socket
    import struct
    import json
    
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    
    while True:
        try:
            com = input('>>>').strip()
            if len(com) == 0: continue
            client.send(com.encode('utf-8'))
    
            header = client.recv(4)                # 先收报头
            dict_l = struct.unpack('i',header)[0]    # 解压报头获取携带字典大小数据
    
            j_dict = client.recv(dict_l)           # 按字典大小接收字典
            dict_m = json.loads(j_dict.decode('utf-8'))   # 还原字典
    
            a = 0
            msg = b''
            if a < dict_m['file_msg']:   # dict_m['file_msg']  获取字典中携带要发送数据包的大小
                data = client.recv(1024)
                msg += data
                a += len(data)
            print(msg)
    
    
        except BaseException:
            break
    # 服务端
    import socket
    import json
    import struct
    import subprocess
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        while True:
            try :
                com = conn.recv(1024)
                if len(com) == 0: break
                print(com)
    
                obj = subprocess.Popen(com.decode('utf-8'),shell =True,
                                      stdout = subprocess.PIPE,
                                       stderr= subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()           # 要发送信息
    
                dict_s = {'name':'命令信息', 'file_msg': len(res)}      # 将要发送信息放入字典中
                json_d = json.dumps(dict_s)      # 将字典序列化
    
                header = struct.pack('i',json_d)  # 将序列化的字典大小信息放入报头
    
                conn.send(header)                 # 先发报头
                conn.send(json_d.encode('utf-8'))  # 在发序列化后的字典
                conn.send(res)                     # 最后发要发送到信息
    
            except BaseException:
                break
        conn.close()
    解决粘包问题

    UDP通信

      UDP通信,自带报头,不存在粘包现象。与TCP协议下通信相比,UDP协议下通信不需要建立双向通道,直接向对方地址发送。支持一对一,和一对多,可进行并发通信。

    UDP通信的有四个特点。客户端允许发送,不会粘包,支持并发,客户端不存在也不会出错。

      

    # 客户端
    import socket
    server = socket.socket(type=socket.SOCK_DGRAM)
    client_addr = ('127.0.0.1', 8080)           # 服务端地址端口
    
    while True:
        msg = input('>>>')
        server.sendto(msg.encode('utf-8'), client_addr)        # 发数据包
    
        data, addr = server.recvfrom(1024)               # 收数据包
        print(data)
    # 服务端
    import socket
    
    server = socket.socket(type=socket.SOCK_DGRAM)
    server.bind(('127.0.0.1', 8080))           # 服务端自己的地址和端口
    
    while True:
        data, addr = server.recvfrom(1024)          # 收数据包,返回数据包和客户端地址
        print(data)
    
        msg = 'hello'
        server.sendto(msg.encode('utf-8'), addr)       # 发数据,按返回的客户端地址发送
    UDP协议下通信

      socketserver模块。使TCP协议下通信和UDP协议下通信一样灵活。

    # 客户端
        import socket
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    
    while True:
        client.send(b'hello')
        data = client.recv(1024)
        print(data.decode('utf-8'))
    
    # 服务端
    import socketserver
    
    class MyServer(socketserver.BaseRequestHandler):      # 设计一个类,继承socketserver模块下BaseRequestHandler这个类
        def handle(self):
            while True:
                try:
                    data = self.request.recv(1024)    # 接收数据
                    print(data.decode('utf-8'))
                    self.request.send(b'ok')          # 发送数据
                except ConnectionResetError as e:
                    print(e)
                    break
    if __name__ == '__main__':
        server = socketserver.ThreadingTCPServer(('127.0.01', 8080), MyServer)     # 创建对象
        server.serve_forever()  # 启动对象
    socketserve模块r
  • 相关阅读:
    Hadoop书籍介绍
    WeakReference,SoftReference 和 PhatomReference 浅析
    如何在Java中定义常量(Constant)
    也谈谈Java的垃圾收集(garbage collection)
    csdn的新家
    安装和使用Oracle Instant Client 和 SQLPlus
    Perl中的grep和map
    用Devel::NYTProf 优化perl脚本性能
    DataBase
    Linux下配置listener和tns
  • 原文地址:https://www.cnblogs.com/huaiXin/p/11317246.html
Copyright © 2011-2022 走看看