Python網路程式設計(子程式的建立與處理、簡單群聊工具)

巴黎香榭發表於2018-08-10
前言:
昨天我們已經瞭解了多程式的原理以及它的實際使用

Unix/Linux作業系統提供了一個fork()系統呼叫,它非常特殊。普通的函式呼叫,呼叫一次,返回一次,

但是fork()呼叫一次,返回兩次,因為作業系統自動把當前程式(稱為父程式)複製了一份(稱為子程式),然後,分別在父程式和子程式內返回。

子程式永遠返回0,而父程式返回子程式的ID。這樣做的理由是,一個父程式可以fork出很多子程式,所以,

父程式要記下每個子程式的ID,而子程式只需要呼叫getppid()就可以拿到父程式的ID。

Python的os模組封裝了常見的系統呼叫,其中就包括fork,可以在Python程式中輕鬆建立子程式

既然是程式那麼就會有執行和退出
接下來我們就來了解一下程式的退出以及處理
 
孤兒程式:
    當父程式於子程式退出,此時子程式就會成為孤兒程式
    特徵:
         孤兒程式會被系統指定程式收養,即系統程式成為
這個孤兒程式新的父程式,系統程式會自動處理程式退出狀態
殭屍程式:
    當子程式於父程式退出父程式沒處理子程式的退出狀態
    此時子程式就會成為殭屍程式
    殭屍程式會滯留部分PCB資訊在記憶體中,大量的殭屍程式會消耗系統給的記憶體資源
    所以要儘量避免殭屍程式的產生

如何避免殭屍程式的產生:
    1.父程式先退出
    2.父程式處理子程式狀態
    PID,status = os.wait
      功能:
         在父程式中阻塞等待處理子程式的退出
      返回值:
           pid :退出的那個子程式PID
   status :子程式的退出狀態
      獲取原來的退出狀態:
             wait(status)
      pid,status = os.waitpid(pid,option)
          功能:在父程式阻塞等待處理子程式的退出
          引數 :
                    pid   -1 表示等待任意子程式退出 
                            >0 表示等待對應PID號的子程式退出
option 
                               0 表示阻塞等待
WNOHANG 表示非阻塞 
          返回值:pid 
                        退出的那個子程式的PID
status 子程式的退出狀態
建立二級子程式
  • 父程式建立子程式等待程式退出
  • 子程式建立下一個程式,然後立即退出
  • 二級子程式成為孤兒程式  處理具體工作
multiprocessing  模組建立程式
  1.需要將要做的事情封裝為函式
  2.使用multiprocessing提供的process 建立程式物件
  3.通過程式物件和process初始化程式進行程式的設定繫結函式
  4.啟動程式,會自動執行繫結的函式
  5.完成程式的回收

建立程式物件:
   process()
     功能:
       建立程式物件
     引數:
       target函式物件
       name 程式稱()
       args元組 用來給target函式位置傳參
       kwargs字典 用來給target函式鍵值傳參
   p.start()
     功能:
       啟動程式自動執行terget繫結函式,
       此時程式被建立
   p.join([timeout])
     功能:
       等待阻塞子程式退出
     引數:超時檢測
     如果不使用join回收可能產生殭屍程式

  • 使用multiprocessing建立程式子程式同樣複製父程式的全部記憶體空間
  • 之後自己的獨立空間 執行互不干擾 子程式也有自己的PID特有資源等
  • 使用multiprocessing建立子程式,一般父程式功能就是建立子程式
  • 回收子程式返回事件交給子程式完成
簡單群聊:
功能:
  類似QQ群聊
  1.進入聊天室需要輸入姓名 姓名不能重複
  2.有人進入聊天室會向其他人發起通知  xxx進入聊天室
  3.如果一個人發訊息則其他人都能收到  xxx說:…
  4.如果某個人退出聊天室也會收到通知  xxx退出聊天室
  5.服務端可以喊話:此時群裡所有的都能收到服務端訊息  管理員說:…
伺服器端:
from socket import * 
import os, sys


# 傳送管理員訊息
def do_child(s, addr):
    while True:
        msg = input("管理員訊息:")
        msg = "C 管理員 " + msg
        s.sendto(msg.encode(), addr)


# 使用者登入
def do_login(s, user, name, addr):
    if (name in user) or name == "管理員":
        s.sendto("該使用者已存在".encode(), addr)
        return
    s.sendto(b`OK`, addr)
    # 通知所有人
    msg = "
歡迎 %s 進入聊天室" % name
    for i in user:
        s.sendto(msg.encode(), user[i])
    # 插入user
    user[name] = addr


def do_chat(s, user, name, data):
    msg = "
{} 說: {}".format(name, data)
    for i in user:
        if i != name:
            s.sendto(msg.encode(), user[i])


def do_quit(s, user, name):
    msg = "
%s 離開了聊天室" % name
    for i in user:
        if i == name:
            s.sendto(b`EXIT`, user[i])
        else:
            s.sendto(msg.encode(), user[i])
    del user[name]  # 刪除離開的使用者


# 接收客戶端請求並處理
def do_parent(s):
    # 用於儲存使用者 {`Alex`:(`127.0.0.1`,8888)}
    user = {}
    while True:
        msg, addr = s.recvfrom(1024)
        msgList = msg.decode().split(` `)
        if msgList[0] == `L`:
            do_login(s, user, msgList[1], addr)
        elif msgList[0] == `C`:
            # "C Levi [I miss you]"
            data = ` `.join(msgList[2:])
            do_chat(s, user, msgList[1], data)
        elif msgList[0] == `Q`:
            do_quit(s, user, msgList[1])


# 建立套接字,網路連線,建立父子程式
def main():
    # server address
    ADDR = (`0.0.0.0`, 8888)
    # 建立套接字
    s = socket(AF_INET, SOCK_DGRAM)
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    s.bind(ADDR)

    # 建立父子程式
    pid = os.fork()
    if pid < 0:
        sys.exit("建立程式失敗")
    elif pid == 0:
        do_child(s, ADDR)
    else:
        do_parent(s)

if __name__ == "__main__":
    main()
客戶端:
from socket import * 
import sys, os


def login(s, ADDR):
    while True:
        name = input("請輸入使用者名稱:")
        msg = "L " + name
        s.sendto(msg.encode(), ADDR)
        # 接收登入結果
        data, addr = s.recvfrom(1024)
        if data.decode() == `OK`:
            print("@進入聊天室@")
            return name
        else:
            print(data.decode())


# 傳送訊息
def do_child(s, name, addr):
    while True:
        text = input("發言(quit退出):")
        # 退出
        if text.strip() == "quit":
            msg = "Q " + name
            s.sendto(msg.encode(), addr)
            sys.exit("退出聊天室")

        msg = "C %s %s" % (name, text)
        s.sendto(msg.encode(), addr)


# 接收訊息
def do_parent(s):
    while True:
        msg, addr = s.recvfrom(1024)
        if msg.decode() == `EXIT`:
            sys.exit(0)
        print(msg.decode() + "
發言(quit退出):",end="")


# main控制套接字的建立
def main():
    if len(sys.argv) < 3:
        print("argv is error")
        return
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])
    ADDR = (HOST, PORT)

    s = socket(AF_INET, SOCK_DGRAM)

    name = login(s, ADDR)
    if name:
        pid = os.fork()
        if pid < 0:
            sys.exit("建立子程式失敗")
        elif pid == 0:
            do_child(s, name, ADDR)
        else:
            do_parent(s)
    else:
        return


if __name__ == "__main__":
    main()



    


相關文章