TCP单进程服务器
# coding=utf-8
from socket import *
import time
tcpSocket = socket(AF_INET, SOCK_STREAM)
# 重复使用绑定信息,不必等待2MSL时间
tcpSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
address = ('', 7788)
tcpSocket.bind(address)
tcpSocket.listen(5)
while True:
time.sleep(0.01)
print('开启等待')
newData, newAddr = tcpSocket.accept()
print('%s客户端已经连接,准备处理数据' % newAddr[0])
try:
while True:
recvData = newData.recv(1024)
if len(recvData) > 0:
print(recvData)
else:
print('%s客户端已经关闭' % newAddr[0])
break
finally:
newData.close()
tcpSocket.close()
此类型服务器虽然能满足多个客户端请求,但是在同一时间内只能处理一个客户端的任务。无法做到同时处理多个客户端请求的任务。
TCP多进程服务器
# coding=utf-8
from socket import *
from multiprocessing import Process
import time
def main():
tcpSocket = socket(AF_INET, SOCK_STREAM)
# 重复使用绑定信息,不必等待2MSL时间
tcpSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
address = ('', 7788)
tcpSocket.bind(address)
tcpSocket.listen(5)
try:
while True:
time.sleep(0.01)
print('开启等待')
newData, newAddr = tcpSocket.accept()
print('%s客户端已经连接,准备处理数据' % newAddr[0])
p = Process(target=recv, args=(newData, newAddr))
p.start()
newData.close()
finally:
tcpSocket.close()
def recv(newData, newAddr):
while True:
recvData = newData.recv(1024)
if len(recvData) > 0:
print(recvData)
else:
print('%s客户端已经关闭' % newAddr[0])
break
newData.close()
# tcpSocket.close()
if __name__ == '__main__':
main()
此服务器通过创建多进程,可以解决多个客户端同时请求的问题,但是由于创建进程需要消耗大量资源,所以并不能很好的应付大用户量的请求。
TCP多线程服务器
# coding=utf-8
from socket import *
from threading import Thread
import time
def main():
tcpSocket = socket(AF_INET, SOCK_STREAM)
# 重复使用绑定信息,不必等待2MSL时间
tcpSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
address = ('', 7788)
tcpSocket.bind(address)
tcpSocket.listen(5)
try:
while True:
time.sleep(0.01)
print('开启等待')
newData, newAddr = tcpSocket.accept()
print('%s客户端已经连接,准备处理数据' % newAddr[0])
p = Thread(target=recv, args=(newData, newAddr))
p.start()
finally:
tcpSocket.close()
def recv(newData, newAddr):
while True:
recvData = newData.recv(1024)
if len(recvData) > 0:
print(recvData)
else:
print('%s客户端已经关闭' % newAddr[0])
break
newData.close()
# tcpSocket.close()
if __name__ == '__main__':
main()
此服务器与多进程服务器类似,线程在节省资源方面比线程更具优势。但是由于GIL的问题所以没法做到CPU的高效利用,CPU在同一时间段内只能执行一个线程。
TCP单进程非阻塞服务器
# coding=utf-8
from socket import *
import time
g_clientinfoList = []
def main():
tcpSocket = socket(AF_INET, SOCK_STREAM)
tcpSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
address = ('', 7788)
tcpSocket.bind(address)
tcpSocket.listen(5)
# 设置socket为不阻塞
tcpSocket.setblocking(False)
while True:
try:
clientInfo = tcpSocket.accept()
except Exception as result:
pass
else:
# 设置clientInfo[0]即监听socket的第一个元素-新socket为不阻塞
clientInfo[0].setblocking(False)
g_clientinfoList.append(clientInfo)
print('%s连接到服务器' % str(clientInfo[1]))
# 定义一个需要删除的socket对象的列表,来暂时存储需要删除的socket
needDelInfoList = []
for clientSocket, clientAddr in g_clientinfoList:
try:
newData = clientSocket.recv(1024)
except Exception as result:
pass
else:
if newData:
print('%s:%s' % (str(clientAddr), newData))
else:
clientSocket.close()
needDelInfoList.append((clientSocket, clientAddr))
print('%s已经离开服务器' % str(clientAddr))
# 避免在上面for循环中删除连续的socket的误删的情况
for needDelInfo in needDelInfoList:
g_clientinfoList.remove(needDelInfo)
if __name__ == '__main__':
main()
此服务器中通过设置socket的setblocking为False。为不阻塞(默认创建出来的socket是阻塞的)。当socket没有accept到,即客户端无connect请求的时候,会产生一个异常,这里通过try来避免这个异常。通过g_clientinfoList这个全局变量list,将客户端每次connect的clientInfo的socket append加进去。在下面for循环的过程中再try的方式处理异常。最终完成处理多个客户端请求的任务。
TCP单进程select服务器
# coding=utf-8
from socket import *
import select
import sys
tcpSocket = socket(AF_INET, SOCK_STREAM)
tcpSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
addres = ('', 7788)
tcpSocket.bind(addres)
tcpSocket.listen(5)
inputs = [tcpSocket, sys.stdin]
runing = True
while True:
'''当select遍历inputs中的对象,如果出现了可读的情况(select中参数检测的以此是可读、可写、异常)时,
会将那些可读的对象放到readabled的List中,例如:客户端新建一个链接,那个inputs中的tcpSocket变为可读
的对象,就会添加到readabled中。并且等待到资源可用的时候,才进行唤醒'''
readabled, writeabled, exceptional = select.select(inputs, [], [])
for socket in readabled:
if socket == tcpSocket:
conn, addr = tcpSocket.accept()
# 如果是一个客户端connect创建,服务器的会有一个监听套接字可用。进而把监听套接字中的conn加入列表中,以便下次循环。
inputs.append(conn)
print('%s已经连入系统' % str(addr))
elif socket == sys.stdin:
# 加入随便一个变量保证退出时不在命令行中执行
cmd = sys.stdin.readline()
runing = False
break
else:
data = socket.recv(1024)
if data:
print(data)
else:
inputs.remove(socket)
socket.close()
if not runing:
break
tcpSocket.close()
此服务器思路与非阻塞类似,不过非阻塞服务器通过自己创建列表来管理的方式,而select则是通过让系统来遍历查找可用的资源。系统级的select要比应用级的list遍历快很多且方便管理。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低。
TCP单进程epoll服务器
import socket
import select
# 创建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置可以重复使用绑定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定本机信息
s.bind(("", 7788))
# 变为被动
s.listen(10)
# 创建一个epoll对象
epoll = select.epoll()
# 测试,用来打印套接字对应的文件描述符
# print s.fileno()
# print select.EPOLLIN|select.EPOLLET
# 注册事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已经注册过,则会发生异常
# 将创建的套接字添加到epoll的事件监听中
epoll.register(s.fileno(), select.EPOLLIN | select.EPOLLET)
connections = {}
addresses = {}
# 循环等待客户端的到来或者对方发送数据
while True:
# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list = epoll.poll()
# 对事件进行判断
for fd, events in epoll_list:
# 如果是socket创建的套接字被激活
if fd == s.fileno():
conn, addr = s.accept()
print('有新的客户端到来%s' % str(addr))
# 将 conn 和 addr 信息分别保存起来
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr
# 向 epoll 中注册 连接 socket 的 可读 事件
epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
elif events == select.EPOLLIN:
# 从激活 fd 上接收
recvData = connections[fd].recv(1024)
if len(recvData) > 0:
print('recv:%s' % recvData)
else:
# 从 epoll 中移除该 连接 fd
epoll.unregister(fd)
# server 侧主动关闭该 连接 fd
connections[fd].close()
print("%s---offline---" % str(addresses[fd]))