Python-TCP服务器

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]))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 9,392评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,635评论 24 1,002
  • A2-06曾莉 #周检视# 0623-0629 百日目标检视 1. 目标.健身塑形,每月跑步150公里,体重减至4...
    ALLY曾莉阅读 146评论 1 0
  • 如果不是你发了朋友圈,我不会知道你出行了,而且去的地方是你的老家,就是说,你并不是本城原住民。 因为是你,对那个并...
    爱吃核桃不吐皮阅读 141评论 0 0