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()
含有線程的服務器