xinetd服务器
配置xinetd服务
什么是xinetd
xinetd可以统一管理很多服务进程,它能够:
- 绑定、侦听和接受来对自服务器每个端口的请求
- 有客户访问时,调用相应的服务器程序相应
- 节约了系统内存资源
- 同时响应多个客户端的连接请求
Windows系统没有该功能
多数UNIX系统使用的是inetd实现相同的功能
配置文件解析
选项名称 | 说明 |
---|---|
flags | 如果只指定NAMEINARGS,那么它就使参数和inetd一样的传递 |
type | 如果服务不在/etc/services中,则使用UNLISTED,否则忽略这一行 |
port | 如果type = UNILISTED,则在这里指定端口号 |
socket_type | 如果是TCP,使用stream;如果是UDP,则使用dgram |
pprotocol | 指定TCP,还是UDP |
wait | TCP设置为no。对于UDP,如果服务器连接远程主机并为不同客户端建立新的进程,则为no;如果UDP在它的端口上处理所有的信息包,直到它被终止,则为yes |
user | 指定程序的运行身份 |
server | 服务程序的完整路径 |
server_args | 参数,为了和inetd兼容,flags设置为NAMEINARGS,则参数使用服务器名 |
配置xinetd服务
编写xinetd服务配置文件
1.服务器监听在0.0.0.0的12345端口上
2.服务器采用TCP协议进行通信
3.服务器以root身份运行
4.服务器运行文件是/root/PycharmProjects/day10/example.py
eg: vim /etc/xinetd.d/pyserv
service pyserv { flags = NAMEINARGS type = UNLISTED port = 12345 socket_type = stream protocol = TCP wait = no user = root server = /root/PycharmProjects/day10/example.py server_args = /root/PycharmProjects/day10/example.py }
重启xinetd服务器,并查看/var/log/messages日志的报错信息
启动成功检查端口监听:netstat -ntpl | grep :12345
编写xinetd程序
使用标准输入输出
当使用xinetd的时候,它通过两个方法传递socket:如文件描述符0和1
它们很文件描述符一样,分别代表标准输入和标准输出
因为标准输出sys.stdout默认是被缓冲的,所以为了实时性,需要使用到sys.stdout.flush()函数
通过这种方法,服务器端程序既可以当成网络服务器程序使用,也可以像普通的脚本程序一样执行
使用标准输入输出搭建TCP服务器
编写一个TCP服务器
1.服务器采用xinetd的方式运行
2.服务器监听在0.0.0.0的12345端口上
3.收到客户端数据后,将其加上时间戳后回送给客户端
4.如果客户端发过来的字符全是空白字符,则终止与客户端的连接
5.采用标准输入输出的方式进行编写
example.py
#!/usr/bin/env python #coding: utf8 import sys print "Welcome." print "Enter line." sys.stdout.flush() line = sys.stdin.readline().strip() sys.stdout.write("You enterd %s chars\n" %len(line))
ex1.py
#!/usr/bin/env python #coding: utf8 import sys import time sys.stdout.write('> ') sys.stdout.flush() while True: data = sys.stdin.readline() # readline 读取换行符\n if not data.strip(): break sys.stdout.write("[%s] %s> ",(time.ctime(),data)) sys.stdout.flush()
使用socket对象
通过调用socket.fromfd()可以建立socket对象
建立socket对象需要使用xinetd传递给程序的文件描述符
fromfd()函数需要文件数量和一些标准的参数,这些参数与前一章内容相同
文件描述符可以通过fileno()函数得到
使用socket对象搭建tcp服务器
编写一个tcp服务器
1.服务器采用xinetd的方式进行
2.服务器监听在0.0.0.0的12345端口上
3.收到客户端数据后,将其加上时间戳后回送给客户端
4.如果客户端发过来的字符全是空白字符,则终止与客户端的连接
5.采用socket对象的方式进行编写
ex3.py
#!/usr/bin/env python #coding: utf8 import sys import time import socket s = socket.fromfd(sys.stdin.fileno(),socket.AF_INET,socket.SOCK_STREAM) s.sendall("Welcome!\n") s.send("You are connected from %s\n" % str(s.getpeername())) s.send("Now: %s\n" % time.ctime())
ex4.py
#!/usr/bin/env python #coding: utf8 import sys import time import socket s = socket.fromfd(sys.stdin.fileno(),socket.AF_INET,socket.SOCK_STREAM) while True: data = s.recv(4096) if not data.strip(): break s.send("[%s] %s" % (time.ctime(),data)) s.close()
forking
fork工作原理
什么是forking
fork(分岔)在linux系统中使用非常广泛
当某一命令执行时,父进程(当前进程)fork出一个子进程
父进程将自身资源拷贝一份,命令在子进程中运行时,就具有和父进程完全一样的运行环境
1.# bash chdir.sh (fork,chdir.sh 可以没有执行权限,程序在子进程中执行,执行结束子shell被销毁)
2.# ./chdir.sh(fork,chdir.sh必须要有执行权限,程序在子进程中执行,执行结束子shell被销毁)
3.# . chdir.sh(当前进程执行脚本,没有fork子进程)
4.# source chdir.sh
3/4命令是一样的
进程的生命周期
父进程fork子进程并挂起
子进程运行完毕后,释放大部分资源并通知父进程,这个时候,子进程被称作僵尸进程
父进程获知子进程结束,子进程所有资源释放
wait()
parent process -————————————————————————> parent process ——>
| |
fork() | |
| exec() exit() |
child process -————————> child process -————————>zombie process
僵尸进程
僵尸进程没有任何可执行代码,也不能被调度
如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
对于系统管理员来说,可以试图杀死父进程后重启系统来消除僵尸进程
forking编程
forking编程基本思路
需要使用os模块
os.fork()函数实现forking功能
python中,绝大多数的函数只返回一次,os.fork将返回两次
对fork()的调用,针对父进程返回子进程PID;对于子进程返回PID0
#!/usr/bin/env python #coding: utf8 import os print "starting...." os.fork() print "hello world!" #会打印两行 helloworld,因为 fork创建子进程,该子进程具有与父进程相同的运行环境 #因为 print“hello world”,在fork下面,所以 父进程会运行一次,子进程也运行一次
#!/usr/bin/env python #coding: utf8 import os print "starting...." pid = os.fork() if pid: print 'hello from parent.' else: print "hello from child" print "hello from both!" # fork返回两个值,针对父进程返回子进程pid,针对子进程返回0, #当第一次返回时pid为非0值,则打印hello from parent,hello from both #当第二次返回时pid为0,则打印hello from child,因hello from both为全局代码所以也打印出来
因为所有的父子进程拥有相同的资源,所以在编写程序时要避免资源冲突
#!/usr/bin/env python import sys import os ip_list = ("172.40.4.%s" % i for i in range(1,255)) for ip in ip_list: pid = os.fork() if pid ==0: result = os.system("ping -c3 %s > /dev/null" % ip) if result == 0: print "%s: up" % ip else: print "%s: down" % ip sys.exit() #父进程负责 fork子进程,子进程负责ping
使用轮询解决zombie问题
父进程通过os.wait()来得到子进程是否终止的信息
在子进程终止和父进程调用wait()之间这段时间,子进程被称为zombie(僵尸)进程
如果子进程还没有终止,父进程先退出了,那么子进程会持续工作,系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
#!/usr/bin/env python #coding:utf8 import os import time pid = os. fork() if pid: print 'in parent.sleepin....' time.sleep(30) print 'parent done.' else: print 'in child.sleeping...' time.sleep(10) print 'child.done.'
# watch -n 1 ps a 观测效果。查看stat
工作过程:
父进程,子进程分别运行,父进程sleep 30 s,子进程sleep 10s,父进程没有处理子进程的代码,子进程进入zombie状态,父进程sleep后,init进程回收父进程资源,父进程退出,子进程仍在,init接管子进程,并回收子进程资源
使用轮询解决zombie问题
python可以使用waitpid()来处理子进程
waitid()接受两个参数,第一个参数设置为-1,表示与wait()函数相同;第二个参数如果设置为0表示挂起父进程,直到子进程退出,设置为1表示不挂起父进程
waitpid()的返回值: 如果子进程尚未结束则返回0,否则返回子进程的PID
1、挂起的情况
#!/usr/bin/env python #coding:utf8 import os import time pid = os. fork() if pid: print 'in parent.sleepin....' print os.waitpid(-1,0) time.sleep(5) print 'parent done.' else: print 'in child.sleeping...' time.sleep(10) print 'child.done.'
2、不挂起的情况
#!/usr/bin/env python #coding:utf8 import os import time pid = os. fork() if pid: print 'in parent.sleepin....' print os.waitpid(-1,1)
# print os.waitpid(-1,os.WNOHANG) WNOHANG就是1,
time.sleep(5) print 'parent done.' else: print 'in child.sleeping...' time.sleep(10) print 'child.done.'
forking服务器
在网络服务中,forking被广泛使用(apache的工作方式)
如果服务器需要同时响应多个客户端,那么forking是解决问题最常用的方法之一
父进程负责接受客户端的连接请求
子进程负责处理客户端的请求
利用forking创建tcp时间戳服务器
编写tcp服务器
1、服务器监听在0.0.0.0的端口上
2、收到客户端数据后,将其加上时间戳后回送给客户端
3、如果客户端发过来的字符全是空白字符,则终止与客户端的连接
4、服务器能够同时处理多个客户端的请求
5、程序通过forking来实现
#!/usr/bin/env python #coding: utf-8 import os import time import socket host = '' port = 21345 addr = (host,port) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) s.bind(addr) s.listen(1) while True: try: while True: result = os.waitpid(-1,os.WNOHANG) if result[0] == 0: break except OSError: pass cli_sock,cli_addr = s.accept() pid = os.fork() if pid: cli_sock.close() else: s.close() while True: data = cli_sock.recv(4096) if not data.strip(): cli_sock.close() sys.exit() cli_sock.send("[%s] %s" % (time.ctime(),data)) cli_sock.close() s.close()
多线程
多线程工作原理
多线程的动机
在多线程(MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
即使子任务相互独立,互相无关(即,一个子任务的结果不影响其他子任务的结果)时也是这样
如果并行运行这些相互独立的子任务可以大幅度地提升整个任务的效率
多线程任务的工作特点
它们本质上就是异步的,需要有多个并发事务
各个事务的运行顺序可以是不确定的,随机的,不可预测的
这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果
什么是进程
计算机程序只不过是磁盘中可执行的、二进制(或其它类型)的数据
进程(有时被称为重量级进程)是程序的一次执行
每个进程都有自己的地址空间,内存以及其它记录其运行轨迹的辅助数据
操作系统管理在其上运行的所有进程,并为这些进程公平地分配空间
什么是线程
线程(有时被称为轻量级进程)跟进程有些相似。不同的是,所有的线程运行在同一个进程中,共享相同的运行环境
线程有开始,顺序执行和结束三部分
线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步
一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便的共享数据以及相互通讯
线程一般都是并发执行的,正是由于这种并行和数据共享机制使得多个任务的合作变为可能
需要注意的是,在单CPU的系统中,真正的并发十不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其他的线程去运行
多线程编程
多线程相关模块
thread和threading模块允许程序员创建和管理线程
thread模块提供了基本的线程和锁的支持,而threading提供了更高级别/功能更强的线程管理功能
推荐使用更高级别的threading模块
只建议那些有经验的专家在想访问线程的底层结构的时候才使用thread模块
传递函数给Thread类
多线程编程有多种方法,传递函数给threading模块的Thread类是加烧得第一种方法
Thread对象使用start()方法开始线程的执行,使用join()方法挂起程序,直到线程结束
关于setdaemon(1)
#!/usr/bin/env python #coding:utf-8 import threading import time def say_hi(): time.sleep(5) print 'hello' if __name__ == '__main__': t = threading.Thread(target=say_hi) t.setDaemon(1) # 在没有添加这一行的时候 python damon1.py时候,会启动一个工作线程,并等到工作线程结束,程序退出 # 即sleep 5s ,然后打印hello # 加上这一行后,python damon1.py 直接退出 # 一般在服务器中设置这个选项,一旦服务器程序中断,则和客户端的连接也一并中断了 t.start()
多线程tcp 时间戳服务器
#!/usr/bin/env python #coding: utf-8 import os import threading import time import socket def handle_client(cli_sock): while True: data = cli_sock.recv(4096) if not data.strip(): break cli_sock.send("[%s] %s" % (time.ctime(),data)) cli_sock.close() if __name__ == '__main__': host = '' port = 21345 addr = (host,port) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) s.bind(addr) s.listen(1) while True: cli_sock.cli_addr = s.accept() t = threading.Thread(target = handle_client,args=[cli_sock]) t.setDaemon(1) #变成工作线程,工作线程允许主线程直接结束 t.start()
传递可调用类给Thread类
传递可调用类给Thread类是介绍的第二种方法
相对于一个或几个函数来说,由于类对象里可以使用类的强大功能,可以保存更多的信息,这种方法更为灵活
>>> class MyClass(object): ... pass ... >>> a = MyClass() >>> a() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'MyClass' object is not callable >>> class MyClass(object): ... def __call__(self): ... print 'hello' ... >>> a = MyClass() >>> a() hello #!/usr/bin/env python #coding:utf-8 import os import threading class Ping(object): def __init__(self,ip): self.ip = ip def __call__(self): result = os.system("ping -c2 %s &> /dev/null" % ip) if result == 0: print "%s:up" % ip else: print "%s:down" % ip if __name__ == '__main__': ip_list = ("172.16.21.%s" % i for i in range(1,255)) for ip in ip_list: t = threading.Thread(target=Ping(ip)) # 可调用类 t.start()
含有线程的服务器