你有看见我弄丢了的那只喵吗

Python 编程之网络脚本阅读笔记(一)

BIGGER SLY 1评论

前言:

        写论文很无聊 而原先FuckBlog项目的后端小伙伴最近正忙于工作上的事情,因此复习网络脚本相关知识,顺便也方便其他人。

0x01 编写简单的套字节程序

以下我们将演示最基本的套字节交互(使用Python3.x 编写)

使用到的模块有socket 曾有人建议如果学习网络原理 那么用C是比较好的

服务端代码如下:

# -*- coding: utf-8 -*-
# 2016/12/7 10:39
"""
-------------------------------------------------------------------------------
Function:  开启TCP/IP套字节,监听客户端消息
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""
from socket import *
my_host=''
my_port=8688
sock_obj=socket(AF_INET,SOCK_STREAM)
#这里面默认TCP/ip协议 如果用UDP 就是那个SOCK_DGRAM不需要加前面的AF_INET具体具体原因不明
sock_obj.bind((my_host, my_port))
sock_obj.listen(5)
while True:
    print('本程序正在运行')
    connection, address =sock_obj.accept()
    print('本服务器正在被',address)
    while True:
        data=connection.recv(1024)
        if not data:break
        connection.send(b'ECHO=>'+data)
    connection.close()

我这里强调一下 代码的第四行,这个写法是约定俗成的 pycharm提示也是默认这个TCP/IP也就是对应这个AF_INET SOCK_STREAM 网上给的中文示例都是这个版本 如果想用其他的协议比如UDP或者AF_UNIX之类的 《Python programing》给我们的建议就是:小菜比还想玩高级的 滚去看Python库手册

第六行的意思是:保持挂起5个请求(就是允许排队等待响应的长度),书上说5 足够了 实战中到底行不行 我没有尝试过

(当我读到这段代码的时候我想到了一个问题 就是当有一万个请求 而服务器忙不过来只能保证五个的时候 其他的都会被deney 那不就和DDOS一个德行==那么我们有啥子解决方案喵? 后面写蛤 )

 

这个程序的意思就是

监听本地端口 8688 然后又消息传递进来加一个ECHO=>回去

 

再来看我们的客户端程序

# -*- coding: utf-8 -*-
# 2016/12/7 10:51
"""
-------------------------------------------------------------------------------
Function:   客户端程序
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""
from socket import *
serverHost='Localhost'
serverPort=8688
message=[b'hello this is Louis Song']
socket_obj=socket(AF_INET, SOCK_STREAM)
socket_obj.connect((serverHost,serverPort))
for line in message:
    socket_obj.send(line)
    data=socket_obj.recv(1024)
    print('Client rec:',data)
socket_obj.close()

非常简单 链接本地端口 然后发送message 然后接受数据并打印

 

如果你对这些依旧陌生建议补一下OSI七层模型什么的 什么 需要我的博客现成的喵 点击这里

0x02处理多客户端访问的server

这里我用windows作为演示(目前暂时没有精力折腾*iux)

请看代码:

 

# -*- coding: utf-8 -*-
# 2016/12/9 19:08
"""
-------------------------------------------------------------------------------
Function:   windows 项下多线程服务端
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""
import  os, time, sys
from socket import *
from multiprocessing import Process
my_host=''
my_port=6666
def now():
    return time.ctime(time.time())

active_children=[]
#linx 使用下述代码
'''
ps:看起来multiprocessing 帮我们搞定了这个所谓reap_children
方法,children的pid可以自己变化 无需我们担心重复
'''
# def reap_children():
#     while active_children:
#         pid, stat=os.waitpid(0, os.W_OK)
#         if not pid:break
#         active_children.remove(pid)


def handle_client(connection):
    print('child:',os.getpid())
    time.sleep(5)
    while True:
        data = connection.recv(1024)
        if not data:break
        reply='ECHO=>%s at %s' %(data, now())
        connection.send(reply.encode())
    connection.close()
    os._exit(0)

def dispatcher():
    while True:
        connection, address= sock_obj.accept()
        print('server connected by:',address,end=' ')
        print('at ', now())
        Process(target=handle_client,args=(connection,)).start()#这里有一个小写的process 有空看一下源码
if __name__=="__main__":
    print('Parent:',os.getpid())
    sock_obj=socket(AF_INET,SOCK_STREAM)
    sock_obj.bind((my_host,my_port))
    sock_obj.listen(5)
    dispatcher()

由于使用了multiprocess 很简单 无需linux那么复杂但是 根据一位小伙伴说 Python自带的多线程出现内存泄漏等问题 我们可能会改写multiprocess的某些方法(这个就以后再说吧)

客户端不变,大家运行很多客户端以后(快速运行个五六次)以后 由于handle_client 里有五秒延时 模拟实际情况中的阻塞,导致后续请求被其他分支所handle 大家可以在记录中明确看到。

注意:Python programing 有关派生进程和套字节

linux项下的os.fork 本质是创建进程副本,可从父进程那里继承文件和套字节描述符。

那么程序如何区分这个是子进程还是父进程呢?看调用返回 如果是0 那么程序就知道自己运行在子进程当中,否则父进程就会获得新的子进程ID.

 

1.关于子进程的退出和僵尸进程

子进程我不写os._exit会怎么样 能咬我么 答:*inux上 子进程不退出就会和其他子进程竞争客户端请求

你咋一听 不是很好么 多几个程序处理不是很好么  我的理解是:反正耗费的内存资源不是你家的吧,维护进程不心疼么。

 

2.  关于僵尸进程的清理

由于是linux的,我这里只是简要介绍一下,在《Python Programing》 781页 说的一个通过信号处理程序手动清理僵尸进程(真傻比的方法)

我个人因此想到了:在分支的时候主进程需要得知分支的PID 如果分支卡死或者长时间没有退出 就执行信号处理程序防止僵尸程序。

3. 线程服务器

进程的开销可比线程大多了 而且在不同平台下不兼容 这里我演示的代码将展现线程服务器的编写 使用_thread模块

# -*- coding: utf-8 -*-
# 2016/12/9 22:53
"""
-------------------------------------------------------------------------------
Function:   线程服务器
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""

import time, _thread as thread, os
from socket import *
my_host=''
my_port=6666
def now():
    return time.ctime(time.time())

active_children=[]

def handle_client(connection):
    # print('child:',os.getpid())
    time.sleep(5)
    while True:
        data = connection.recv(1024)
        if not data:break
        reply='ECHO=>%s at %s' %(data, now())
        connection.send(reply.encode())
    connection.close()
    # os._exit(0)
   #这里注意 调用线程 不需要os._exit 会自动退出 你可以加上这个退出看什么后果
def dispatcher():
    while True:
        connection, address= sock_obj.accept()
        print('server connected by:',address,end=' ')
        print('at ', now())
        thread.start_new_thread(handle_client,(connection,))
if __name__=="__main__":
    print('Parent:',os.getpid())
    sock_obj=socket(AF_INET,SOCK_STREAM)
    sock_obj.bind((my_host,my_port))
    sock_obj.listen(5)
    dispatcher()

唯一改动的地方就是将process方法换成thread了 其他的照旧

进程ID是不变的。

4. 标准服务器类socketserver的使用

socketserver帮助我们更多关注如何处理消息 而非考虑这些不阻塞 或者线程的协调

 

下面看代码

 

# -*- coding: utf-8 -*-
# 2016/12/9 22:53
"""
-------------------------------------------------------------------------------
Function:   线程服务器
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""

import time, os
import socketserver
my_host=''
my_port=6666
my_addr=(my_host,my_port)
def now():
    return time.ctime(time.time())
class my_client_handle(socketserver.BaseRequestHandler):
    #这个handle方法不能改为其他名字 他是重写socketserver里面的方法
    def handle(self):
        print(self.client_address, now())
        time.sleep(5)
        while True:
            data = self.request.recv(1024)
            if not data:break
            reply='ECHO=>%s at %s' %(data, now())
            self.request.send(reply.encode())
        self.request.close()
print('working')
server=socketserver.ThreadingTCPServer(my_addr, my_client_handle)
server.serve_forever()

5. 构建多路复用选择的服务器

CPU 同一时间段多任务切换的能力叫做多路复用

代码如下:

# -*- coding: utf-8 -*-
# 2016/12/10 15:56
"""
-------------------------------------------------------------------------------
Function:   使用select库做响应服务器
Version:    1.0
Author:     SLY
Contact:    slysly759@gmail.com 
 
-------------------------------------------------------------------------------
"""

import sys, time
from select import select
from socket import *
def now():return time.ctime(time.time())
my_host=''
my_port=6000
num_port_socks=2
mainsocks, readsocks, writesocks=[],[],[]
for i in range(num_port_socks):
    port_sock=socket(AF_INET,SOCK_STREAM)
    port_sock.bind((my_host,my_port))
    port_sock.listen(5)
    mainsocks.append(port_sock)
    readsocks.append(port_sock)
    my_port+=1
print('select-server loop starting')
while True:
    readables,writeables, exceptions=select(readsocks,writesocks,[])
    for sock_obj in readables:
        if sock_obj in mainsocks:
            new_sock, address=sock_obj.accept()
            print('Connect:',address,id(new_sock))
            readsocks.append(new_sock)
        else:
            data=sock_obj.recv(1024)
            print('\t got ',data,'on',id(sock_obj))
            if not data:
                sock_obj.close()
                readsocks.remove(sock_obj)
            else:
                reply='Echo=>%s at %s' %(data,now())
                sock_obj.send(reply.encode())

首先有人会问:

a. select原理是什么

b. 为啥需要用多个端口

c. 为啥需要分三个 一个read 一个write 还有一个main 这三个如何分工

 

下面用书上的话来进行阐释:

a. select 作为python标准模块 用于监视输入源 输出源 和特殊条件源列表,并告诉我们那个源立刻立即处理。它简单轮询所有源,看看哪些准备好了(在本程序中应该是接收到套字节的会话)

也就是说使用select让我们程序更多的关注已经准备好的源 而不是处于正在阻塞状态下的服务

优势:由于循环程序段非常简洁 运行起来非常快,因此处理事务非常快。达到我们所说的“并行”效果

劣势; 仍有阻塞风险,当事务处理时间过长会导致阻塞,除非增加线程进程处理时间。

b、c  需要看select源码 我个人对此理解不深,怕误导大家。网上的一些写的也不够深入。建议搜英文。

6. 关于asyncore和twisted

书上建议是 如果喜欢select 而且又不太喜欢那些 socket 调用细节 什么handle write read 之类的 asyncore已经帮你封装好了

这是官方文档里面的举例:

import asyncore

class HTTPClient(asyncore.dispatcher):

    def __init__(self, host, path):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.connect( (host, 80) )
        self.buffer = bytes('GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' %
                            (path, host), 'ascii')

    def handle_connect(self):
        pass

    def handle_close(self):
        self.close()

    def handle_read(self):
        print(self.recv(8192))

    def writable(self):
        return (len(self.buffer) > 0)

    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]


client = HTTPClient('www.python.org', '/')
asyncore.loop()

运行效果是 获取python.org的通信过程

如图:

原谅我目前没法解答大家关于这些实现细节的回答,不过关于Python asyncore 我这里推荐Python官方介绍文档:

https://docs.python.org/3.5/library/asyncore.html

 

喜欢 (4)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. :grin:
    SLY2016-12-14 09:45 回复