zoukankan      html  css  js  c++  java
  • 初识socket

    一、初识socket

           socket(套接字)起源于20世纪70年代加利福尼亚大学伯克利分校版本的Unix,即人们所说的BSDUnix。因此,有时人们也把套接字称为“伯克利套接字”或“BSD套接字”。一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或IPC。socket(套接字)也可用在相同或者不同的设备进程之间进行通信。

           套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

           为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接为流式套接字(SOCK-STREAM)、数据报套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。

    流式套接字:它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。在TCP/IP协议簇中,使用TCP协议来实现字节流的传输,当用户想要发送大批量的数据或者对数据传输有较高的要求时,可以使用流式套接字。

    数据报套接字:它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端按发送顺序接收数据。在TCP/IP协议簇中,使用UDP协议来实现数据报套接字。在出现差错的可能性较小或允许部分传输出错的应用场合,可以使用数据报套接字进行数据传输,这样通信的效率较高。

    原始套接字:该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备。
    软件开发架构一般分为C/S和B/S两种。

    • C/S:Client与Server ,中文意思:客户端与服务器端架构
    • B/S:Browser与Server,中文意思:浏览器端与服务器端架构

    二、socket.socket()模块简介

    socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    初始化参数:
    family:套接字有很多家族,AF_INET,AF_UNIX ,AF_IRDA,等多种,我们先学习AF_INET,默认是AF_INET。
    AF_UNIX:是基于文件类型的套接字家族,unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
    AF_INET:是基于网络类型的套接字家族(还有AF_INET6被用于ipv6,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我们只使用AF_INET)
    type:套接字类型有如下几种:默认是SOCK_STREAM。

    • SOCK_STREAM:TCP流(常用)
    • SOCK_DGRAM¶:UDP数据报(常用)
    • SOCK_RAW:原始套接字(常用)
    • SOCK_RDM:保证交付数据报但不保证顺序。
    • SOCK_SEQPACKET:可靠的连续数据包服务

    proto:端口号通常为0,这样socket会随机使用一个没被占用的端口,在能确定端口没被占用的情况下可以手动指定端口号,在地址家族为AF_CAN的情况下,协议应该是CAN_RAW、CAN_BCM或CAN_ISOTP之一。
    fileno:从指定的文件描述符中自动检测family,type和proto的值。默认是None。

    三、socket object常用方法:

    socket.bind(address):将socket绑定一个地址,这个socket必须没有绑定过。

    socket.listen([backlog]):socket服务器进入监听模式以接受连接。如果指定了backlog这个值最少为0。backlog代表没有被 accept 取走的连接数量。如果未指定,系统会选择默认的合理值。

    socket.accept():接受一个连接。socket必须绑定到一个地址并监听连接。返回值是一个元组(conn,address),其中conn是一个新的套接字对象,用于在连接上发送和接收数据,address是绑定到连接另一端套接字的地址。

    socket.recv(bufsize):接收发送过来的数据。 返回值是一个字节对象,一次接收的最大数据量由bufsize指定。为了与硬件和网络实际情况保持最佳匹配,bufsize的值应为2的次幂,例如1024,2047,4096等。

    socket.recvfrom(bufsize):接收数据。返回值是一个元组(字节,地址),其中字节是一个字节对象,表示接收到的数据,地址是发送数据的socket的地址。

    socket.recvmsg(bufsize,ancbufsize,):从套接字接收数据和辅助数据。bufsize(字节)接收数据的大小ancbufsize参数设置用于接收辅助数据的内部缓冲区的大小(以字节为单位)。它默认为0,表示将不会接收任何辅助数据。可以使用CMSG_SPACE()或CMSG_LEN()计算辅助数据的适当缓冲区大小,不适合缓冲区的项目可能会被截断或丢弃。返回值是一个四元组:(数据,ancdata,msg_flags,address)。数据项是一个字节对象,ancdata项是零个或多个元组(cmsg_level,cmsg_type,cmsg_data)的列表,表示接收到的辅助数据(控制消息):cmsg_level和cmsg_type是分别指定协议级别和协议特定类型的整数,而cmsg_data是字节对象保存相关数据。 msg_flags项是指示接收消息条件的各种标志的按位或;有关详细信息,请参见系统文档。如果接收套接字未连接,则address是发送套接字的地址(如果有);否则,其值未指定。

    socket.send(bytes):将数据发送到socket。该socket必须连接到远程socket。返回发送的字节数。

    socket.sendall(bytes):与send()类似不同的是此方法继续从字节发送数据,直到所有数据都已发送或发生错误为止。 成功不返回任何内容。 如果出错,则会引发异常,所以无法确定成功发送了多少数据。

    socket.sendto(bytes,address):将数据发送到socket。不应连接到远程socket,因为目标socket是按地址指定的。返回发送的字节数。

    socket.connect(address):根据地址连接远程socket。

    socket.fileno():返回socket的文件描述符(一个小整数),如果失败则返回-1。这对于select.select()非常有用。在Windows下,这个方法返回的小整数不能用于可以使用文件描述符的地方(例如os.fdopen())。Unix没有这个限制。

    socket.getpeername():返回socket连接到的远程地址,端口号。在某些系统上,可能不支持此功能。

    socket.getsockname():返回自己socket的地址和端口号。

    socket.getblocking():如果套接字处于阻塞模式,则返回True;如果处于非阻塞模式,则返回False。适用于Python3.7。

    socket.shutdown(how):关闭连接。如果how=SHUT_RD则不允许接收数据。如果how=SHUT_WR则不允许发送数据。如果how=SHUT_RDWR如何,则不允许发送和接收。

    socket.close():释放与连接关联的资源,但不一定立即关闭连接,套接字对象上的所有后续操作都将失败。远程端将不再接收任何数据。如果希望及时关闭连接,在close()之前调用shutdown()。

    socket.detach():关闭socket对象而不,而不实际关闭底层文件描述符。返回文件描述符,此调用后无法使用socket对象,但可以使用文件描述符,用于其他目的。

    socket.settimeout(value):设置socket阻塞模式下超时时间。value值是非负数(单位:秒),如果给出了value,在value秒后还没有接收到数据,连接请求,将引发超时异常。如果给定0,则套接字将处于非阻塞模式。如果是None,套接字将进入阻塞模式。

    参考文档: https://docs.python.org/3/library/socket.html?highlight=socket#socket.AF_INET。

    四、简单示例

    建立基本的连接

    server端:

    import socket
    server_obj = socket.socket()              # 创建socket对象
    server_obj.bind(("socket服务器地址",9000))  # socket绑定IP地址和监听端口
    server_obj.listen(5)                      # 监听远程socket连接
    con,addr = server_obj.accept()            # 建立socket连接
    msg = con.recv(1024).decode("utf-8")      # 接收远程socket发来的数据
    print(msg)
    con.send(msg.upper().encode("utf-8"))     # 发送数据给远程socket
    con.close()         # 关闭连接
    server_obj.close()  # 关闭连接

    client端:

    import socket
    client = socket.socket()                # 创建socket对象
    client.connect(("远程socket地址",9000))  # 连接远程socket
    msg = input(">>>")
    client.send(msg.encode("utf-8"))             # 发送数据给远程socket
    recv_msg = client.recv(1024).decode("utf-8") # 接收远程socket发来的数据
    print(recv_msg)
    client.close()  # 关闭socket

    上面的示例只能收一次,发一次消息,然后就结束了。下面我们使用while循环,来循环收,发消息。

    server端:

    import socket
    server_obj = socket.socket()              # 创建socket对象
    server_obj.bind(("192.168.10.102",9000))  # socket绑定地址和端口
    server_obj.listen(5)                      # 监听socket连接请求
    con,addr = server_obj.accept()            # 建立socket连接
    print('远端socket对象:',con.getpeername()) # 打印远程socket信息
    while True:
        try:
            msg = con.recv(1024)  # 接收远程socket发来的数据
            if msg.decode('utf-8').upper()=='Q':break # 如果发来的是q表示断开连接
            print(msg.decode("utf-8"))
            con.send('我接到了你发来的消息'.encode('utf-8'))
        except Exception:
            break
    print(连接已断开)
    con.close()
    server_obj.close()

    client端:

    import socket
    client = socket.socket()                # 创建socket对象
    client.connect(("192.168.10.102",9000)) # 连接远程socket
    while True:
        msg = input(">>>")
        if msg.upper() == 'Q':break       # 输入q退出程序
        client.send(msg.encode("utf-8"))  # 发送数据给远程socket
        recv_msg = client.recv(1024).decode("utf-8") # 接收远程socket发来的数据
        print(recv_msg)
    print('退出程序')
    client.close()

    此时我们的服务器只能接受一个客户端的连接,如果客户端断开了,服务端将关闭。如果想让服务器一直接受请求一个客户端断开连接后,继续接受下一个客户端的连接,看如下server代码:

    import socket
    server_obj = socket.socket()              # 创建socket对象
    server_obj.bind(("192.168.10.102",9000))  # socket绑定地址和端口
    server_obj.listen(5)                      # 监听socket连接请求
    while 1:
        print('等待接收远程socket连接......')
        con,addr = server_obj.accept()        # 建立socket连接
        print('连接一台远程socket:',con.getpeername())
        while True:
            try:
                msg = con.recv(1024)          # 接收远程socke
                if msg.decode('utf-8').upper()=='Q':break
                print(msg.decode("utf-8"))
                con.send('我接到了你发来的消息'.encode('utf-8'))
            except Exception:
                break
        con_address,con_port = con.getpeername() # 获取断开socke对象信息
        print(con_address,'断开了连接')
        con.close()
    server_obj.close()

    关于recv要注意的地方:

    当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。关闭远程端并读取所有数据后,返回空字符串。

  • 相关阅读:
    RuntimeError: An attempt has been made to start a new process before the current
    Expected object of backend CPU but got backend CUDA for argument #2 'weight'
    RuntimeError: multi-target not supported at
    模型load文件时报AttributeError: Can't get attribute 'Cours' on <module '__main__' from 错误
    ArrayList扩容源码分析
    HashMap源码分析(put/get)
    索引优化策略有哪些
    Mysql什么是回表查询和覆盖索引
    MyISAM与InnoDB区别
    使用ReentrantLock实现阻塞队列与交替打印
  • 原文地址:https://www.cnblogs.com/caesar-id/p/12057502.html
Copyright © 2011-2022 走看看