python创建TCP Server
使用python创建一个TCP Server并不是什么难事,难的是理解每一行代码背后的意义和原理, 涉及到的知识点包括绑定ip, 绑定端口, 监听, 接受一个客户端的连接请求, 接收数据, 关闭连接, 每个步骤都有细节知识点...
import socket
# 指定协议
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 让端口可以重复使用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip和端口
server.bind(('0.0.0.0', 8080))
# 监听
server.listen(1)
# 等待消息
clientsocket, address = server.accept()
# 接收消息
data = clientsocket.recv(1024)
# 关闭socket
clientsocket.close()
server.close()
这样一段代码,有哪些知识需要深入理解呢?
1. SO_REUSEADDR
如果没有下面这行代码
# 让端口可以重复使用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
一旦server意外死亡或人为干掉,那么大约两分钟内,无法重新启动该server,这是因为,建立socket连接后,主动断开连接的一方所用的端口会进入到TIME_WAIT状态, 如果短时间内再次启动server,就会引发异常 OSError: [Errno 48] Address already in use
2. 绑定ip
server.bind(('0.0.0.0', 8080))
绑定ip写成'0.0.0.0',表示接受所有ip发起的tcp连接,如果写成127.0.0.1,那么就只接受本机上发出的socket连接了
3. 绑定端口
一台主机,可以开放的端口范围是从0~65535,这个范围,是由TCP/IP协议决定的,在该协议中,TCP的头结构如下
在基于IPv4的网络中,源端口号和目标端口号都是16位的,因此最大只能是65535。
端口0在网络编程中有着特殊作用,尤其在unix系统中,如果你申请打开0端口号,0更像是一个统配符,系统会寻找一个合适的端口供你使用,而不是按照你的要求打开端口0。
在TCP/IP 协议中,0端口号是保留的,在TCP和UDP中都不应该使用,1-1023为系统端口,1024-65535为用户端口。
4. 监听
server.listen(1)
listen方法开始监听客户端连接请求,listen的参数设置为n,并不是表示最多可以建立n个连接,而是在服务器拒绝连接之前,操作系统可以挂起的最大连接数量,我这里设置为1,但可以有多于1个客户端向服务端发起连接。
对于这个参数,tornado默认设置为128,我觉得有点小了,nginx一般推荐大一些,搞个2048也没问题,它是用多个进程进行accept操作,一个进程处理不过来了,会换另一个进程,因此处理的非常快
5.accept
accept()接受一个客户端的连接请求,并返回一个新的套接字,这样的解释正确但还不够深入。
作为服务端,系统会维护一个syn队列和一个accept队列,客户端发起连接发送syn包时,系统会把这个连接信息放入到syn队列里,然后返回syn+ack包,等收到客户端最后发回来的ack包时,会从syn队列里把连接信息放入到accept队列里, accept会从accept队列里取连接, 这里的连接都已经完成了3次握手。
6. 接收数据
data = server.recv(1024)
recv负责接收套接字的数据, 参数设置成1024,并不意味着一定会接收到1024字节数据,即便对方发的数据超过1024,recv执行一次究竟能收到多少数据,取决接收缓冲区里有多少数据以及TCP/IP协议,如果已知对方会发送5000字节的数据,那么我们需要自己统计已经接收了多少,还剩余多少没有收到
7. 关闭连接
我这里用了close(),也可以用shutdown(),但你千万不要以为关闭socket连接有两种方法,其实他们很不同。shutdown破坏了连接,而close只是关闭本进程的socket id,不破坏连接,此时,其他进程仍然可以在该连接上发送和接收消息。前面提到过,nginx使用多个进程进行accept操作,这些进程都是fork出来的,他们共同使用同一个socket连接,如果某一个进程执行close,并不影响其他进程继续使用,每进行一次close,计数器就会减一,等到为0时,就是所有进程都执行close了,此时,套接字资源才会被回收。