zoukankan      html  css  js  c++  java
  • 【网络接口API】Python Socket与Linux Socket

    Python Socket与Linux Socket

    socket: Python的底层网络接口,一般情况程序员不需要接触到这个模块。有更多的高级模块,比如requests可以直接使用。本文章试图从Python的socket模块和linux socket api的角度来对Python实现网络通讯方式进行分析,提高对TCP,UDP通讯方式的理解。最后用Python实现一个hello/hi的简单的网络聊天程序。

    1. socket

    在Python中如果想要使用一个自己的网络应用层协议,或者说想使用纯原生TCP,UDP来实现通讯,就需要使用Python的socket模块。

    import socket
    

    socket模块提供了访问BSD套接字的接口。在所有现代Unix系统、Windows、macOS和其他一些平台上可用。

    1.1 socket()方法

    # 使用socket()方法返回一个socket对象
    s = socket.socket([family[, type, proto, fileno]])
    

    重要参数:

    • family: 套接字家族,如ipv4,ipv6,unix系统进程间通信
    • type: 套接字类型,如tcp,upd
    参数 描述
    family
    socket.AF_INET(默认) IPv4
    socket.AF_INET6 IPv6
    socket.AF_UNIX Unix系统进程间通信
    type
    socket.SOCK_STREAM 流式套接字,TCP
    socket.SOCK_DGRAM 数据报套接字,UDP
    socket.SOCK_RAW 原始套接字

    socket方法与Linux Socket的socket函数对应

    // socket(协议域,套接字类型,协议)
    int socket(int domain, int type, int protocol);
    

    通过s = socket.socket()方法,得到了一个socket对象。

    Python中的socket对象的成员方法,是对套接字系统调用的高级实现,往往比C语言更高级。


    2. TCP

    2.1 bind()方法

    通常如果是服务器,需要绑定一个总所周知的地址用于提供服务,所以需要绑定一个(IP:PORT),客户端可以通过连接这个地址来获得服务。而客户端则直接通过连接,由系统随机分配一个端口号。

    python中bind()方法传入一个地址和端口的元组

    s.bind((host: str, port: int))
    

    linux socket将套接字作为对象,传入一个套接字和地址结构体

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    2.2 listen()方法

    开始监听,backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数,默认为1

    s.listen(backlog: int)
    

    linux socket同样需要额外传入套接字参数

    int listen(int sockfd, int backlog);
    

    2.3 connect()方法

    connect方法是客户端用发起某个连接的,接受一个目标主机名和端口号的元组参数

    # address -> (hostname, port)
    s.connect(address)
    # connect_ex是connect的扩展方法,不同在于返回错误代码,而不是抛出错误
    s.connect_ex(address)
    

    linux socket中,参数分别为客户端套接字socket描述符,服务器socket地址,socket地址长度

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    2.4 accpet()方法

    服务器依次调用socket(), bind(), listen()后就会监听指定地址,客户端通过connect()向服务器发起连接请求。服务器监听到请求后会调用accept()函数接受请求。这样端与端的连接就建立好了

    python中,accept()方法阻塞进程,等待连接,返回一个新的套接字对象和连接请求者地址信息。

    # accept() -> (socket object, address info)
    s.accept()
    

    linux socket中,第一个参数是服务器套接字描述符,第二个为一个地址指针,用于返回客户端协议地址,第三个参数是协议地址长度。如果连接成功,函数返回值为内核自动生成的一个全新描述符

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    服务器的监听套接字一般只创建一个,而accept函数会返回一个连接套接字,服务器与客户端之间的通信是在连接套接字上进行。每来一个服务请求新建一个连接套接字与请求者通信,而监听套接字只有一个,完成服务后对应的连接套接字就会被关闭。(可以理解成,监听套接字是专门的接线员,只负责将电话转接给别的部门)

    2.5 recv()与send()

    send(data[, flags]) ->count
    

    发送数据到socket,发送前要将数据转换为utf-8的二进制格式。返回发送数据长度,因为网络可能繁忙,导致数据没有全部发送完毕,所以要再次对剩下的数据进行发送。

    python还有一个sendall()

    sendall(data[, flags])
    

    作用是不停调用send()函数,直到所有数据发送完毕

    linux socket中的send()

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    • send()函数先检查协议是否正在发送缓冲区数据,等待协议发送完毕或则缓冲区已没有数据,那么send比较sockfd缓冲区剩余空间大小和发送数据的len。
    • 如果len大于剩余空间大小,则等待协议发送缓冲中数据
    • 若len小于剩余空间,则将buf中数据拷贝到剩余空间
    • 若发送数据长度大于套接字发送缓冲区长度,则返回-1

    python中,从已连接套接字读取数据的函数为recv()

    s.recv(bufsize: int)
    

    从套接字接受数据,如果没有数据到达套接字,将会阻塞直到来数据或则远程连接关闭。

    如果远程连接关闭且数据已全部读取,则抛出一个错误。

    linux socket也有读取数据函数recv()

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
    • recv()等待s发送缓冲区发送完毕
    • 检查套接字s的接受缓冲区,若协议正在接收数据,则等待接受完毕。
    • 将接收缓冲区的数据拷到buf中,接受数据可能大于buf长度,所以需要多次调用recv()

    3. UDP

    在无连接的情况下,端到端需要使用另外的数据发送和接受方式

    3.1 sendto()

    python中发送UDP数据,将数据data发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。

    s.sendto(data,address)
    

    linux socket: 由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
    

    该函数比send()函数多了两个参数,dest_addr表示目地机的IP地址和端口号信息,而addrlen是地址长度。

    3.2 recvfrom()

    s.recvfrom() -> (data, address)
    

    接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收的数据,address是发送数据的套接字地址。

    linux socket: recvfrom()的情况与sendto()类似,需要指针来存放发送数据的套接字地址

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
    

    4. close()

    python和linux socket都需要对套接字关闭

    python:

    s.close()
    

    linux socket:

    int close(int socketfd)
    

    5. Python实现hello/hi的简单的网络聊天程序

    5.1 server.py

    #! /usr/bin/env python3
    
    import socket
    from threading import Thread
    import traceback
    
    HOST = "127.0.0.1"
    PORT = 65432
    
    def recv_from_client(conn):
        try:
            content = conn.recv(1024)
            return content
        except Exception:
            return None
    
    class ServiceThread(Thread):
        def __init__(self, conn, addr):
            super().__init__()
            self.conn = conn
            self.addr = addr
    
        def run(self):
            try:
                while True:
                    content = recv_from_client(self.conn)
                    if not content:
                        break
                    print(f"{self.addr}: {content.decode('utf-8')}")
                    self.conn.sendall(content)
                self.conn.close()
                print(f"{self.addr[0]}:{self.addr[1]} leave.")
            except Exception:
                traceback.print_exc()
    
    if __name__ == "__main__":
        s = None
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.bind((HOST, PORT))
            s.listen()
            print("Repeater server started successfully.")
            while True:
                conn, addr = s.accept()
                print(f"Connected from {addr}")
                service_thread = ServiceThread(conn, addr)
                service_thread.daemon = True
                service_thread.start()
        except Exception:
            traceback.print_exc()
        s.close()
    

    5.2 client.py

    #! /usr/bin/env python3
    
    import socket
    from threading import Thread
    
    HOST = "127.0.0.1"
    PORT = 65432
    
    class ReadFromConnThread(Thread):
        def __init__(self, conn):
            super().__init__()
            self.conn = conn
    
        def run(self):
            try:
                while True:
                    content = self.conn.recv(1024)
                    print(f"
    ({HOST}:{PORT}): {content.decode('utf-8')}
    YOUR:", end="")
            except Exception:
                pass
    
    if __name__ == "__main__":
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((HOST, PORT))
        read_thread = ReadFromConnThread(s)
        read_thread.daemon = True
        read_thread.start()
        while True:
            content = input("YOUR:")
            if content == "quit":
                break
            s.sendall(content.encode("utf-8"))
        s.close()
    

    5.3 运行截图

    • 服务器

    server

    • 客户端

    client

    作者:SA19225176,万有引力丶

    参考资料来源:USTC Socket网络编程

  • 相关阅读:
    我的大学生涯
    如何设计一个好的Windows 8应用
    [置顶] 十大高明的Google搜索技巧
    [置顶] 走出困境靠自己
    Android代码混淆前后分析
    百度高级搜索
    未来手机什么样 十款未来概念手机图赏
    如何看懂Java混淆后的反编译代码
    最值得一看的几条简单的谷歌 Google 搜索技巧,瞬间提升你的网络搜索能力!
    各种网页尺寸判断方法
  • 原文地址:https://www.cnblogs.com/Axi8/p/11979090.html
Copyright © 2011-2022 走看看