VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • Python3基础之初识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会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。关闭远程端并读取所有数据后,返回空字符串。


相关教程