美文网首页
socket编程, since 2021-07-04

socket编程, since 2021-07-04

作者: Mc杰夫 | 来源:发表于2021-07-04 14:10 被阅读0次

    (2021.07.04 Sun)

    socket基本

    socket(套接字)是计算机网络节点间和进程间通信的一种方式/约定,网络节点可通过socket发送或接收信息。socket封装和提供了网络协议(栈)的接口,通过接口可控制协议栈的工作,实现跨主机网络通信。

    在同一台主机中,进程可以通过唯一进程标识符识别,即PID,而跨主机间的进程用PID不适合。在socket中,不同主机的进程通过网络地址、协议和端口的三元组来唯一标识进程,即(IP, protocol, port)

    Unix/Linux的基本哲学是"一切皆文件",文件可以通过"打开"、"读写"、"关闭"的方式进行操作。socket继承了这种哲学。在socket中,网络上的设备都被当做文件,并赋值文件标识符,对这些设备的操作,可类比于对本地磁盘上的文件进行操作。

    socket中封装了网络协议的实现。一个典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

    Python、JAVA等都提供了对socket的支持,并提供了专用包。

    socket类型

    SOCK_STREAM流格式套接字

    SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。该格式使用了TCP作为传输层协议,因此提供了可靠通信,即数据的无损和正序。

    流格式套接字的内部有一个缓冲区(也就是字符数组),通过 socket 传输的数据将保存到这个缓冲区。接收端在收到数据后并不一定立即读取,只要数据不超过缓冲区的容量,接收端有可能在缓冲区被填满以后一次性地读取,也可能分成好几次读取。

    HTTP使用了SOCK_STREAM套接字。

    SOCK_DGRAM数据包格式套接字

    无连接套接字,其传输层使用了UDP协议。数据传输过程中只管传输数据,不做数据校验,是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字。传输时限制数据大小,发送和接收同步。常用于视频会议等场合。

    (2021.08.08 Sun)

    服务端和客户端创建socket连接的流程-TCP

    1. 服务端调用socket函数创建一个socket,该函数的返回值是socket唯一描述字(socket descriptor)
    2. 服务端调用bind函数将地址族中的地址赋给socket,也就是将服务端的地址绑定到socket,客户端可通过该地址与服务端通信
    3. 服务端调用listen函数监听socket,判断是否有来自客户端的connect申请
    4. 客户端调用connect函数,向服务器发出连接请求
    5. 服务端listen到客户端的connect请求,调用accept函数,通过三次握手建立连接;连接建立完成,可类比文件读写操作,对socket进行操作
    6. 两端通过read/write,或recvmsg/sendmsg函数对socket进行读写操作
    7. 操作完成调用close函数完成TCP连接

    注: 在服务端调用accept时,成功连接后会返回一个已完成连接的socket,用来传输数据。监听的socket和用于数据传输的socket是两个socket。

    创建socket和通信的流程

    socket中三次握手的流程

    当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

    socket handshake

    socket demo, in Python

    # 2021.08.08 Sun
    # socket demo: tcp
    
    import argparse, socket
    
    def recvall(sock, length):
        data = b''
        while len(data) < length:
            more = sock.recv(length - len(data))
            print(more,'\n')
            if not more:
                # raise EOFError('was expecting %d bytes but only received %d bytes before the socekt closed' % (length, len(data)))
                raise EOFError('was expecting {} bytes but only received {} bytes before the socekt closed'.format(length, len(data)))
            data += more
        return data
    
    def server(interface, port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((interface, port))
        sock.listen(1)
        print('listening at ', sock.getsockname())
        while True:
            sc, sockname = sock.accept()
            print('we have accepted a connection from ', sockname)
            print('Socket name: ', sc.getsockname())
            print('Socket peer: ', sc.getpeername())
            # message = recvall(sc, 16)
            message = sc.recv(8096)
            print('Incoming sixteen-octet message: ', repr(message))
            sc.sendall(b'ffff, client')
            sc.close()
            print('Reply sent, socket closed')
    
    def client(host, port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((host, port))
        print('client has been assigned socket name ', sock.getsockname())
        sock.sendall(b'hi server')
        # reply = recvall(sock, 8096)
        reply = sock.recv(8096)
        print('server said ', repr(reply))
        sock.close()
    
    if __name__ == '__main__':
        choices = {'client': client, 'server': server}
        parser = argparse.ArgumentParser(description='Send and receive over TCP')
        parser.add_argument('role', choices=choices, help='which role to play')
        parser.add_argument('host', help='interface the server listens at; '
                                         ' host the client sends to')
        parser.add_argument('-p', metavar='PORT', type=int, default=1060,
                            help='TCP port (default 1060)')
        args = parser.parse_args()
        function = choices[args.role]
        print('host: ', args.host,', port: ', args.p)
        function(args.host, args.p)
    
    

    在服务端和客户端可同时使用该脚本。服务端调用方法:

    $ python3 socket_tcp.py server '192.168.1.3' # specify host ip
    

    客户端调用方法:

    $ python3 socket_tcp.py client '192.168.1.3' # specify server host ip
    

    设置传输字节的长度可运行该程序。

    (2021.08.12 Thur)

    服务端和客户端创建socket连接的流程-UDP

    与TCP相比,UDP没有握手过程,在实现上的服务器端少了accept步骤。

    # socket_udp.py
    # UDP client and server
    
    import argparse, socket
    from datetime import datetime
    
    MAX_BYTES = 65535
    
    def server(port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind(('127.0.0.1', port))
        print('listening at {}'.format(socket.getsockname()))
        while True:
            data, address = sock.recvfrom(MAX_BYTES)
            text = data.decode('ascii')
            print('client at {} says {!r}'.format(address, text))
            text = 'your data was {} bytes long'.format(len(data))
            data = text.encode('ascii')
            sock.sendto(data, address)
    
    def client(port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        text = 'The time is {}'.format(datatime.now())
        data = text.encode('ascii')
        sock.sendto(data, ('127.0.0.1', port))
        print('The OS assigned me the address {}'.format(sock.getsockname()))
        data, address = sock.recvfrom(MAX_BYTES) 
        text = data.decode('ascii')
        print('the server {} replied {!r}'.format(address, text))
    
    if __name__ == '__main__':
        choices = {'client': client, 'server': server}
        parser = argparse.ArgumentParser(description='Send adn receive UDP locally')
        parser.add_argument('role', choices=choices, help='which role to play')
        parser.add_argument('-p', metavar='PORT', type=int, default=1060, help='UDP port (default 1060)')
        args = parser.parse_args()
        function = choices[args.role]
        function(args.p)
    

    在服务器端的输入

    $python3 socket_udp.py server
    Listening at ('127.0.0.1', 1060)
    

    在客户端的输入

    $python3 socket_udp.py client
    

    注意到与TCP不同的是,代码中并没有套接字的connect()函数。如果使用sento()函数,那么每次向服务器发送信息的时候都必须显式的给出服务器的IP地址和端口;而如果使用connec()调用,操作系统事先就已经知道数据包要发送到的远程地址,这样就可以简单的把要发送的数据作为参数传入send()调用,无需重复给出服务器地址。

    广播和多播(multicast)

    UDP有个特别强大的功能:广播。通过广播,数据包的目标地址被设置为本机连接的整个子网,然后使用物理网卡将数据包广播,就无需复制该数据包并单独将其发送给所有连接到该子网的主机。这里暂只介绍广播。

    广播发生在接收广播数据包的服务器发送广播数据包的客户端之间。和前面UDP的例子不同之处在于,使用socket对象时,先调用setsockopt()方法,设置为允许进行广播。

    # socket upd broadcast
    
    import argparse, socket
    
    BUFSIZE = 65535
    
    def server(interface, port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind((interface, port))
        print('Listening for dataframs at {}'.format(sock.getsockname()))
        while True:
            data, address = sock.recvfrom(BUFSIZE)
            text = data.decode('ascii')
            print('the client at {} say: {!r}'.format(address, text))
    
    def client(network, port):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        text = 'Broadcast datagram.'
        sock.sendto(text.encode('ascii'), (network, port))
    
    if __name__ == '__main__':
        choices = {'client': client, 'server': server}
        parser = argparse.ArgumentParser(description='Send, receive UDP broadcast')
        parser.add_argument('role', choices=choices, help='which role to take')
        parser.add_argument('host', help='interface the server listens at; network the client sends to')
        parser.add_argument('-p', metavar='port', type=int, default=1060, help='UDP port (default 1060)')
        args = parser.parse_args()
        function = choices[args.role]
        function(args.host, args.p)
    

    运行方法
    首先使用下面指令运行一到两台网络中的服务器

    $python socket_udp_broadcast.py server ""
    Listing for broadcasts at ('0.0.0.0', 1060)
    

    保持各服务器运行。在客户端上输入ifconfig找到broadcast地址,比如192.168.1.255,在本地网络上运行

    $python socket_udp_broadcast.py client 192.168.1.255
    

    另一种广播的方法<broadcast>,不需要在本地获得广播地址,并且向本机的所有网络端口进行广播。

    $python socket_udp_broadcast.py client "<broadcast>"
    

    (2021.08.15 Sun)

    Socket参数

    在socket初始化过程中,需要对socket做如下设置

    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((host, port))
    

    除了指定了服务器/客户端的IP和port,还需要指定socket方法中的两个参数,即地址族(address family)和套接字类型(socket type)。另有一个隐藏参数时协议(protocol),暂不讨论。

    地址族

    某个特定的机器可能连接到多个不同类型的网络。对地址族的选择指定了想要进行通信的网络类型。可找出socket的方法并打印以AF_(address family)开头的符号,查看其它选择,诸如AF_APPLETALKAF_BLUETOOTH等。TCP和UDP可用AF_INET

    套接字类型

    该类型给出了希望在已经选择的网络上使用的特定通信技术。
    TCP选SOCK_STREAM,UDP选SOCK_DGRAM。

    地址解析

    socket.getaddrinfo()用于地址解析。

    Reference

    1 图片源自网络
    2 Brandon R. and etc,诸豪文译,Python网络编程(第3版),中国工信出版社,人民邮电出版社

    相关文章

      网友评论

          本文标题:socket编程, since 2021-07-04

          本文链接:https://www.haomeiwen.com/subject/vowmultx.html