一、初识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对象,但可以使用文件描述符,用于其他目的。
参考文档: 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会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。关闭远程端并读取所有数据后,返回空字符串。