欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

初识socket

程序员文章站 2022-03-21 18:08:37
一、初识socket socket(套接字)起源于20世纪70年代加利福尼亚大学伯克利分校版本的Unix,即人们所说的BSDUnix。因此,有时人们也把套接字称为“伯克利套接字”或“BSD套接字”。一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或IPC。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对象,但可以使用文件描述符,用于其他目的。

参考文档: 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会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。关闭远程端并读取所有数据后,返回空字符串。