ขอจงทรงพระเจริญ ด้วยเกล้าด้วยกระหม่อมขอเดชะ ข้าพระพุทธเจ้าคณะสมาชิกไดอารีอีส.คอม

threaded python socket server

กว่าจะทำตัวให้ชินกับมันได้ document ของโมดูล SocketServer ก็ช่างหายากเหลือเกิน

โมดูล SocketServer เป็นโมดูลที่จะช่วยให้เราสามารถเปิดใช้งาน socket server ได้จากเครื่องของเรา ลองคิดดูเล่นๆ ในการทำงานปกติ เครื่องคอมพิวเตอร์ใน network ก็ต้องคุยกัน ส่งข้อมูลหากัน python ก็ไม่น้อยหน้า มี module ชื่อ socket กับ SocketServer เอาไว้ให้ใช้

Socket โดยธรรมชาติยังแบ่งออกไปได้อีกหลายประเภท แต่ช่างมัน ไม่ต้องพูดถึง เวลาที่เราสร้าง socket ระหว่างเครื่อง โปรแกรมจะ block ไม่ให้เครื่องอื่นเข้ามาใช้งานได้ โดย request ที่เหลือก็จะไปรออยู่ใน queue แต่ถ้า request ตัวปัจจุบัน ต้องใช้เวลานาน ก็จะทำให้ request ที่ยังไม่ได้ process รอไปโดยปริยาย (ลองสังเกตพวก app โง่ๆ ที่พัฒนาให้หน่วยงานราชการดู กดส่ง 1 ที ต้องรอไปอีก 1 ชาติ กว่าจะเสร็จ โอกาสที่จะ timeout แล้วไม่ได้งานก็เกิดขึ้นได้ง่าย)

วิธีแก้มีหลายอย่าง ตั้งแต่เขียนให้เป็น non-blocking ซึ่งโปรแกรมจะไม่รอให้ socket เขียนข้อมูลให้เสร็จ ใช้โมดูล select  แต่วิธีนี้มันซับซ้อนเกินกว่าสมองเราจะเข้าใจ (สมองคนอื่นไม่รู้)

อีกวิธีนึงที่นั่งศึกษามาสองสามวัน เรียกว่า threading โดยที่โปรแกรมจะแตก thread ออกมาเพื่อรับ request เป็นจำนวนหลายๆ อัน ผมเข้าใจว่าวิธีนี้ก็ยังคงเป็นแบบ blocking อยู่ แต่เนื่องจาก request ทั้งหลายไม่ต้องรอคิว ทำให้ใช้งานได้เร็วขึ้น มั้ง

การทำ threading ทำได้ง่ายๆ แค่โค้ดไม่กี่บรรทัด (เพราะเป็น python) แต่คนเขียนต้องเข้าใจหลักการเขียนโปรแกรมเชิงวัตถุซักเล็กน้อย เพราะมันจะมีการ inherit เอาอะไรหลายๆ อย่างมาจาก predefined class ของโมดูลชื่อ SocketServer

มาดูโค้ดง่ายๆ กัน

บรรทัดแรก
import SocketServer, string

เรียกใช้โมดูล SocketServer กับ string เราเอา string มาใช้เพื่อเอาไปปรับแต่ง message ที่จะส่งเข้ามาให้สวย

ต่อไป
class thisisatest(SocketServer.StreamRequestHandler):   
    def handle(self):
        print "Got connection from",self.client_address
        while 1:
            cmdrecv = self.rfile.readline(512)
            cmdrecv = cmdrecv.strip()
            cmdrecv = cmdrecv+"\n"
            self.wfile.write(cmdrecv)   

เป็นการสร้าง request handler class เพื่อที่จะใช้ในการรองรับกับ message ที่ส่งเข้ามาผ่านทาง socket หน้าที่หลักๆ ที่ class นี้จะทำ คือรับข้อมูลจาก socket ใช้คำสั่ง readline() แล้วก็ strip เอาพวกช่องว่าง กะ escape character ออกไป (มีประโยชน์ถ้าจะเอาไปเข้า if statement ต่อ) แล้วก็ส่ง string ชื่อ cmdrecv กลับไปยัง socket โดย class นี้จะ inherit คุณสมบัติมาจาก class StreamRequestHandler ของโมดูล SocketServer

ต่อไป
class thisisaserver(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    daemon_threads = True
    allow_reuse_address = True

class นี้ใช้สร้าง TCP Server จุดประสงค์หลักของ class นี้คือการสร้าง tcp server ให้เป็น object พร้อมทั้งบอก config ว่าให้กระจายงานเป็น thread เพราะถ้าไม่เป็น object ระบบจะเรียกใช้ class นี้ซ้ำๆ ไม่ได้ daemon_threads จะเป็นบอกระบบว่าแต่ละ thread จะทำงานแยกเป็นอิสระจากกัน ทำให้โปรแกรมไม่ต้องรอให้ thread หยุดทำงานให้หมด ก่อนที่ตัวเองจะหยุดทำงาน

allow_reuse_address จะบอกให้โปรแกรมใช้งาน address เก่าได้ทันที หากการปิดโปรแกรมครั้งเก่าเป็นไปโดยไม่สมบูรณ์ ถ้าไม่สั่ง อาจจะเกิดปัญหาว่า address already binded ได้ มีประโยชน์สำหรับพวกที่ต้องรันโปรแกรมบ่อยๆ โดยเฉพาะตอนเขียน :-D

แล้วก็
server = thisisaserver(('', 7000), thisisatest)

บรรทัดนี้ ก็เรียกใช้ class thisisaserver กำหนดให้ใช้งานกับ local ip คือเว้นว่างเอาไว้ แล้วก็ port 7000 สังเกตว่าช่องที่ให้ใส่ address จะเป็น tuple คืออยู่ในวงเล็บ แล้วก็ให้เรียก thisisatest class เป็นตัวรับ request

สุดท้าย
try:
    server.serve_forever()
except KeyboardInterrupt:
    print "Terminating"
    server.server_close()

เป็นการใส่ loop ให้โปรแกรมทำงานต่อไปเรื่อยๆ จนกว่าจะได้รับ exception ชื่อ KeyboardInterrupt ซึ่งจะทำให้ object server เนี่ย ปิดตัวเองไปนั่นเอง

ถ้าอยากลองเล่น ว่าจะได้ผลยังไง ก็รันไฟล์นี้ไว้ แล้ว telnet เข้าไปที่เครื่องตัวเอง port 7000 พิมพ์อะไรก็ได้ลงไป แล้วกด enter โปรแกรมจะส่ง text ที่เราพิมพ์เข้าไปกลับออกมาให้ดู พร้อมกับขึ้นบรรทัดใหม่ให้
<< 10 สัญญาณ ที่บอกว่าคุณเป็นสาวกของ *NIX เข้าแล้ว รักนุ่น >>

 

  กุมภาพันธ์
อ. จ. อ. พ. พฤ. ศ. ส.
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
[ archive / ไดอารีทั้งหมด ]

- my ibook

eXTReMe Tracker