python网络编程(socket)
ps:本文为个人技术复习笔记,所有理解与写法均为现阶段个人视角。为便于日后“零基础状态”的自己上手复习,内容尽可能写得通俗直白,所以专业性可能不是很高,还请见谅
这算是我第一次编写md文档,格式上可能会有问题,但是我已经写了千字了改起来太麻烦了,所以我决定在下篇改好 26/5/16
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
可以使用**socket()**创建一个套接字对象来用于后续的操作
语法,socket(family: int=..., type: int=..., proto: int=..., fileno: Optional[int]=...)
不要传入字符串,要使用socket.AF_INET这样的常量
这些常量本质就是整数(如
AF_INET=2)这样做是为了和操作系统底层保持一致
import socket #导入socket模块
sk = socket.socket(family=socket.AF_INET, type=SOCK_STREAM, proto=0)
括号里面看着这么多其实可以简写成
sk = socket.socket()
这和上面的意思相同
那上面这些参数具体都是什么具体都有什么作用呢?我们看下面的解释
family 地址族,决定了套接字的地址类型
地址类型也可以叫做地址族,它是来决定地址的类型的(这不是废话吗wwww),地址族的类型有本地和网络,确定好地址族可以让套接字决定使用什么方式去本地还是网络通信
常用的family
AF_INET:IPv4 网络通信(支持 TCP/UDP,可跨设备或本机回环)
AF_INET6:IPv6 网络通信(支持 TCP/UDP,可跨设备或本机回环)
AF_UNIX / AF_LOCAL:本地进程间通信(仅限本机,高性能)
type 套接字类型
决定了套接字的通信语意,也就是决定套接字使用什么方式进行传输的
常用类型
SOCK_STREAM 流式类型(TCP)
SOCK_DGRAM 数据包类型(UDP)
proto 协议号
具体使用的协议,前面 type 决定了通信的方式,系统会更具 type 的选择自动选择常用的协议 (proto=0),当然这个也是可以自己指定的
结合上面的解释,我们来翻译下之前到底创建了个什么套接字
import socket #导入socket模块
sk = socket.socket(family=socket.AF_INET, type=SOCK_STREAM, proto=0)
family 类型为AF_INET 也就是ipv4
type 为SOCK_STREAM 使用了流式协议
proto 为0,前面也提到了,此项的值为0是系统自动指定常用的协议,所以这里是tcp
我们使用socket()创建了个ipv4的tcp套接字对象,默认情况下也就是socket()什么都不填写使用的也是这些协议。那现在我们可以通过这个套接字对象来进行操作了吗(?存疑)
当然是不可能的,我们还没有给这个套接字指定地址什么的,如果直接通过发送或是接收函数,数据包到底会去哪呢。所以还需要使用对象内置的函数 bind() 或是 connect() 来决定绑定或连接的地址。通常情况下使用 bind() 的用于服务端,使用connect()用于客户端。现在我们来看看如何同这两个函数来实现通信
bind()
使用这个函数会给当前套接字绑定应该地址,让其可以接收发往绑定地址的数据包
语法,bind(address: Union[_Address, bytes])
需要传入一个数组,数组里面需要包含字符串格式ip地址和整型的端口
# 例子
import socket
sk = socket.socket() #默认使用tcp
sk.bind(('127.0.0.1',51772))
connect()
使用这个函数可以连接对应的地址,让其可以往这个地址发送数据
语法,connect(address: Union[_Address, bytes])
和前面一样,需要传入一个数组,数组里面需要包含字符串格式ip地址和整型的端口
# 例子
import socket
sk = socket.socket() #默认使用tcp
sk.connect(('127.0.0.1',51772))
ok,现在地址也绑定来,也连接来地址,那是否将可以直接通信来呢?
答案是不可能的,如果直接运行服务端程序(*使用bind的程序)*会发现服务端还没有等客户端(使用connect的程序)连接就停止运行了。我们需要让程序在这里等,等有客户端来连接我们的服务端。在没有接触的到网络编程前,我们需要和用户交互可以使用input()来获取用户输入的内容,在没有用户输入的时候我们的程序并不会直接退出,因为input会帮我们阻塞程序。
同样的,在这里我们希望程序等待客户端的连接那也需要程序阻塞在这等待,这将可以使用 accept() 来达到效果
accept() -> (socket object, address info)
accept()函数会返回一个数组,数组里面包含了一个 socket 对象和连接到服务的地址,我们需要使用应该变量来接收函数返回的东西,以便于之后的通信
知道了这个后再来完善我们之前的代码
import socket
sk = socket.socket() #默认使用tcp
sk.bind(('127.0.0.1',51772))
conn,addr = sk.accept()
所以我们的服务端程序最终是这样...吗?
tip:socket 有主动模式和被动模式的,主动模式也就是客户端模式主动的取连接通信,被动模式是程序被动的接收数据
前面也提到 bind() 是绑定应该地址,accept() 阻塞程序等待客户端连接,这个行为是被动的,所以我们现在的主动模式肯定是不行的,还需将其变为被动模式。可以使用 listen 将其变成被动模式也叫监听模式,使用这个函数还需要填入连接数。
连接数也就是当前可连接的数量,目前我们使用的是单线程的,数据是一个一个排队处理的,所以这个连接数可以当成排队队伍的最大长度。如果队伍满了(连接数到了设置的数量),那新来到就不能连接了(不能排队了)
再来完善下服务端代码
import socket
sk = socket.socket() #默认使用tcp
sk.bind(('127.0.0.1',51772))
sk.listen(10) #设置连接数为10
conn,addr = sk.accept()
现在就基本实现了服务端了,我们在看客户端应该如何连接上
import socket
sk = socket.socket() #默认使用tcp
sk.connect(('127.0.0.1',51772))
其实现在这个客户端程序将可以连接服务端了,socket本身就是主动模式,除了连接地址其他也不需额外设置什么
现在可以尝试与服务端简单通信下了
send 发送数据
send只能发送字节流
recv 接收数据
同样的,recv只能接收字节流,必须设置字节长度
# 客户端
import socket
sk = socket.socket() #默认使用tcp
sk.connect(('127.0.0.1',51772))
#需要保证服务端在线才可以连接成功,连接成功后使用send发送数据
sk.send(b'1234') #发送字节格式的1234
import socket
sk = socket.socket() #默认使用tcp
sk.bind(('127.0.0.1',51772))
sk.listen(10) #设置连接数为10
conn,addr = sk.accept() #当客户端连接这里才会有数据
msg = conn.recv(4) #接收4字节的数据
print(msg) # 如何前面客户端正确连接且发送了正确的数据,那这里将会输出 1234
tcp连接是全双工的,也就是双方都可以互发消息,这里暂时将不演示了。下一章<python网络编程2>会介绍全双工通信
END