ผู้เขียนบทความ : นายเลิศฤทธิ์ เจริญมาก COE #12
1.ความเป็นมา
เนื่องจากโลกของเรามีโปรแกรมแชทอยู่มากมาย การพูดคุยในโลกอินเทอร์เน็ตจึงเป็นเรื่องปกติในชีวิตประจำวันแต่บางครั้งการพูดคุยการที่คิดว่าเป็นการส่วนตัวมันอาจจะไม่เป็นเช่นนั้น เนื่องจากบางครั้งรัฐบาลอาจจะขอข้อมูลการพูดคุยของเราจากผู้ให้บริการโปรแกรมแชทต่างๆทำให้เกิดความไม่เป็นส่วนตัว
ผมจึงได้ทำโปรแกรม Api Chat Gui ขึ้นมาเพื่อแก้ปัญหาการพูดคุยแล้วเกิดความไม่เป็นส่วนตัวขึ้น เนื่องจากโปรแกรมนี้การพูดคุยเมื่อมีการปิดโปรแกรมข้อมูลการพูดคุยทั้งหมดจะไม่ถูกบันทึกเก็บไว้ทำให้มีความเป็นส่วนตัวมาก
2.วัตถุประสงค์
1.เพื่อศึกษาหาความรู้ในการใช้ภาษา Python
2.เพื่อศึกษาหาความรู้ในการสร้าง API
3.เพื่อพัฒนาโปรแกรม Chat ที่มีความเป็นส่วนตัว
4.เพื่อนำความรู้ที่ได้ไปใช้ต่อยอดในการศึกษาและการทำงาน
3.ขอบเขต
1.สามารถพูดคุยผ่านโปรแกรม Chat ได้
2.สามารถสร้าง Api เพื่อที่จะเก็บข้อมูลของห้อง Chat แต่ละห้องได้
4.ประโยชน์ที่คาดว่าจะได้รับ
1.สามารถที่จะนำระบบไปต่อยอดหรือประยุกต์ในการพัฒนาต่อได้
2.การพูดคุยผ่านโปรแกรม chat มาความเป็นส่วนตัว
3.สามารถเรียนรู้และเข้าใจในเรื่องของ Socket Tkinter และ Fastapi
5. ความรู้ที่เกี่ยวข้อง
Socket คือ ?
Socket Programming นั้นเป็นการเขียนโปรแกรมที่ใช้ Socket เป็นช่องทางที่ใช้สำหรับการรับส่งข้อมูลกันระหว่างเครื่องหรือโปรแกรม โดยอาจจะเป็นระหว่าง server กับ client ซึ่งการรับส่งข้อมูลสามารถทำได้ทั้งสองทางและเก็บสถานะการติดต่อแต่ละครั้งเอาไว้ได้ ในขณะที่ถ้าเป็นการรับส่งข้อมูลแบบ REST จะไม่จำสถานะการติดต่อของแต่ละครั้ง และการติดต่อต้องเริ่มจากฝั่งใดฝั่งหนึ่งเท่านั้น
การใช้งาน socket จะแบ่งเป็น 2 ส่วนได้แก่ Server และ Client
ในการเขียนคำสั่งของผั่ง Server นั้นจะต้องทำการ import module ต่าง ๆ มาใช้งานในระบบ
import socket
import datetime
import threading
จากนั้นกำหนด IP PORT และ BUFSIZE
HOST = '172.16.241.143'
POST = 9090
BUFSIZE = 4096
คำสั่งสำหรับการเชื่อมต่อและจัดการ Server
def handler(client,addr):
while True:
try:
data = client.recv(BUFSIZE)
check = data.decode('utf-8').split('|')
if check[0] == 'NAME':
cdict[str(addr)] = check[1]
except:
clist.remove(client)
break
if (not data) or (data.decode('utf-8') == 'q'):
clist.remove(client)
print('OUT: ',client)
break
try:
user = cdict[str(addr)]
msg = user + ': ' + data.decode('utf-8')
except:
msg = str(addr) + ': ' + data.decode('utf-8')
print('USER: ',msg)
print('-----------')
try:
check = data.decode('utf-8').split('|')
if check[0] == 'NAME':
pass
else:
for c in clist:
c.sendall(msg.encode('utf-8'))
except:
for c in clist:
c.sendall(msg.encode('utf-8'))
client.close()
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
server.bind((HOST,POST))
server.listen(5)
while True:
client, addr = server.accept()
clist.append(client)
print('ALL CLIENT', clist)
task = threading.Thread(target=handler, args=(client, addr))
task.start()
ในส่วนของทางด้าน Client จะมีการใช้ module tkinter เข้าในการสร้าง Gui หรือ Graphical User Interface เพื่อสร้างหน้าต่างโปรแกรม Chat
โดยจะต้องทำการเรียกใช้ import module เหล่านี้เข้ามาก่อน
from tkinter import *
from tkinter import ttk, messagebox
import tkinter.scrolledtext as st
from tkinter import simpledialog
import socket
import threading
import sys
และจะต้องกำหนด IP POST และ BUFSIZE ให้ตรงกับทางฝั่ง Server
HOST = '172.16.241.143'
POST = 9090
BUFSIZE = 4096
และจะต้องทำการสร้าง Gui ขึ้นมาเพื่อใช้ในการพูดคุย
gui = Tk()
w = 650
h = 680
ws = gui.winfo_screenwidth()
hs = gui.winfo_screenheight()
print(ws,hs)
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
gui.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
gui.title('ห้องแชททั่วไป')
Font1 = ("TH SarabunPSK",20)
Font2 = ("TH SarabunPSK",20)
################### BOX ####################
F1 = Frame(gui)
F1.place(x=5,y=5)
allmsg = StringVar()
chatbox = st.ScrolledText(F1,width=62,heigh=13,font=Font1)
chatbox.pack(expand=True, fill='x')
#################### Messsage ####################
v_msg = StringVar()
F2 = Frame(gui)
F2.place(x=20,y=570)
E1 = ttk.Entry(F2,textvariable=v_msg,font=Font2,width=45)
E1.pack(ipady=20)
#################### Button ####################
def Sendmessage(event=None):
msg = v_msg.get()
client.sendall(msg.encode('utf-8'))
chatbox.delete('1.0','end')
chatbox.insert(INSERT,allmsg.get())
chatbox.yview(END)
v_msg.set('')
E1.focus()
F3 = Frame(gui)
F3.place(x=500,y=570)
B1 = ttk.Button(F3,text='Send',command=Sendmessage)
B1.pack(ipadx=25,ipady=25)
E1.bind('<Return>',Sendmessage)
user = StringVar()
global getname
getname = ''
###################### dialog ###################
def gui2dialog():
gui2 = Toplevel()
gui2.attributes('-topmost',True)
w = 300
h = 200
ws = gui2.winfo_screenwidth()
hs = gui2.winfo_screenheight()
print(ws,hs)
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
gui2.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
v_getname = StringVar()
L = ttk.Label(gui2, text='Name',font=Font2).pack()
EN1 = ttk.Entry(gui2,textvariable=v_getname,font=('TH SarabunPSK',20),width=40)
EN1.pack(padx=30,pady=00)
def Entername(event=None):
global getname
getname = v_getname.get()
gui2.withdraw()
gui.attributes('-topmost',True)
E1.focus()
gui.attributes('-topmost',False)
import random
print('GETNAME',getname)
if getname == '' or getname == None:
num = random.randint(10000,99999)
getname = str(num)
user.set(getname)
chatbox.insert(INSERT,'สวัสดี' + getname)
Fastapi คือ ?
คือ Framework สำหรับการสร้าง API เขียนด้วย Python โดยมี uvicorn เป็นตัวจัดการ run server และด้วยความที่ใช้ uvicorn ที่เป็น ASGI ทำให้รองรับการทำงานแบบ Asynchronous โดย FastAPI จะ generate document มาตรฐาน ตามแบบของ OpenAPI ให้อัตโนมัติ โดยใช้ข้อมูลต่าง ๆ เช่น type ของ input/output และ comment ที่เราเขียนไว้ในโค้ด โดยที่เราไม่ต้องทำอะไรเพิ่มเติมทำให้มีความสะดวกในการใช้ รวมทั้งสนับสนุน JSON Schema สำหรับการ validate JSON อีกด้วย โดยจะมีทั้ง Swagger UI และ Redoc ให้เลย
อย่างแรกเราจะต้องทำการ install fastapi และ uvicorn เข้ามาก่อนด้วยคำสั่ง
pip install fastapi
pip install uvicorn
เมื่อทำการ install เข้ามาแล้วจะต้องทำการสร้าง venv เพื่อใช้เป็นพื้นที่ในการทำงานของ Fastapi
.\venv\scripts\activate
จากนั้นก็ทำการ import module ของ Fastapi เข้ามา
from fastapi import FastAPI
from typing import Optional
from pydantic import BaseModel
จากนั้นก็ทำการสร้าง Class เพื่อทำการเก็บข้อมูล
class Room(BaseModel):
nameroom: str
HOST: Optional[str] = None
POST: str
จากนั้นทำการสร้างฟังก์ชัน Post Get Put และ Delete ขึ้นมาเพื่อใช้ในการใส่ข้อมูล ดูข้อมูล อัพเดทข้อมูล และ ลบข้อมูล
@app.post('/addroom/')
def AddRoom(room: Room):
room_stock.append(room.dict())
print(room_stock)
return room
@app.get('/roomchat/')
def AllRoom():
return room_stock
@app.put('/update/{ID}')
def UpdateRoom(ID: int, room: Room):
room_stock[ID] = room.dict()
return {'ID':ID,'message':'update','data': room.dict()}
@app.delete('/delete/{ID}')
def DeleteRoom(ID: int):
data = room_stock[ID]
del room_stock[ID]
return {'ID':ID,'message':'delete','data':data }
เมื่อทำการสร้างทั้งหมดเสร็จสมบูรณ์แล้ว ก็จะทำการรันคำสั่งด้วย
uvicorn main:app --reload --port 8000 --host 0.0.0.0
โดยก่อนที่เราจะทำการรันคำสั่งจะต้องทำการ Forward port ด้วย Ngrok ก่อน ด้วยคำสั่ง
ngrok http 8000
6.ผลการดำเนินงาน
ภาพการทำงานของระบบ ดูได้ตามรูปนี้
เริ่มต้นการทำงานด้วยการรัน Server ใน cmd
python s2.py
จากนั้นทำการรัน Client ด้วยคำสั่ง ใน VS Code
python c2.py
เมื่อทำการรันแล้วจะเกิดหน้าต่าง Gui ขึ้นมา
จากนั้นทำการรัน API ด้วยคำสั่ง
uvicorn main:app --reload --port 8000 --host 0.0.0.0
จากนั้นก็ทำการ Forward port ด้วย Ngrok จากคำสั่ง
ngrok http 8000
เมื่อทำการรัน API แล้วให้เข้าไปที่ localhost ของ เพื่อทำการใส่ข้อมูล
จะปรากฏหน้าต่างของ FastAPI โดยสร้าง document มาตรฐาน ตามแบบของ OpenAPI ให้อัตโนมัติ โดยใช้ข้อมูลต่าง ๆ เช่น type ของ input/output และ comment ที่เราเขียนไว้ในโค้ด
หน้าตาของลิ้ง API
และข้อมูลที่เราทำการใส่เราไป
โค้ดทั้งหมดของ Server
import socket
import datetime
import threading
HOST = '172.16.241.143'
POST = 9090
BUFSIZE = 4096
clist = []
cdict = {}
def handler(client,addr):
while True:
try:
data = client.recv(BUFSIZE)
check = data.decode('utf-8').split('|')
if check[0] == 'NAME':
cdict[str(addr)] = check[1]
except:
clist.remove(client)
break
if (not data) or (data.decode('utf-8') == 'q'):
clist.remove(client)
print('OUT: ',client)
break
try:
user = cdict[str(addr)]
msg = user + ': ' + data.decode('utf-8')
except:
msg = str(addr) + ': ' + data.decode('utf-8')
print('USER: ',msg)
print('-----------')
try:
check = data.decode('utf-8').split('|')
if check[0] == 'NAME':
pass
else:
for c in clist:
c.sendall(msg.encode('utf-8'))
except:
for c in clist:
c.sendall(msg.encode('utf-8'))
client.close()
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
server.bind((HOST,POST))
server.listen(5)
while True:
client, addr = server.accept()
clist.append(client)
print('ALL CLIENT', clist)
task = threading.Thread(target=handler, args=(client, addr))
task.start()
โค้ดทั้งหมดของ Client
from tkinter import *
from tkinter import ttk, messagebox
import tkinter.scrolledtext as st
from tkinter import simpledialog
import socket
import threading
import sys
HOST = '172.16.241.143'
POST = 9090
BUFSIZE = 4096
global client
def handler(client):
while TRUE:
try:
data = client.recv(BUFSIZE)
except:
print('ERROR')
break
if (not data) or (data.decode('utf-8') == 'q'):
print('OUT!')
break
allmsg.set(allmsg.get() + data.decode('utf-8') + '\n')
chatbox.delete('1.0','end')
chatbox.insert(INSERT,allmsg.get())
chatbox.yview(END)
#print('USER :', data.decode('utf+8'))
client.close()
messagebox.showerror('Connection Failed', 'ตัดการเชื่อมต่อ')
###############################################
gui = Tk()
w = 650
h = 680
ws = gui.winfo_screenwidth()
hs = gui.winfo_screenheight()
print(ws,hs)
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
gui.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
gui.title('ห้องแชททั่วไป')
Font1 = ("TH SarabunPSK",20)
Font2 = ("TH SarabunPSK",20)
################### BOX ####################
F1 = Frame(gui)
F1.place(x=5,y=5)
allmsg = StringVar()
chatbox = st.ScrolledText(F1,width=62,heigh=13,font=Font1)
chatbox.pack(expand=True, fill='x')
#################### Messsage ####################
v_msg = StringVar()
F2 = Frame(gui)
F2.place(x=20,y=570)
E1 = ttk.Entry(F2,textvariable=v_msg,font=Font2,width=45)
E1.pack(ipady=20)
#################### Button ####################
def Sendmessage(event=None):
msg = v_msg.get()
client.sendall(msg.encode('utf-8'))
chatbox.delete('1.0','end')
chatbox.insert(INSERT,allmsg.get())
chatbox.yview(END)
v_msg.set('')
E1.focus()
F3 = Frame(gui)
F3.place(x=500,y=570)
B1 = ttk.Button(F3,text='Send',command=Sendmessage)
B1.pack(ipadx=25,ipady=25)
E1.bind('<Return>',Sendmessage)
user = StringVar()
global getname
getname = ''
###################### dialog ###################
def gui2dialog():
gui2 = Toplevel()
gui2.attributes('-topmost',True)
w = 300
h = 200
ws = gui2.winfo_screenwidth()
hs = gui2.winfo_screenheight()
print(ws,hs)
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
gui2.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
v_getname = StringVar()
L = ttk.Label(gui2, text='Name',font=Font2).pack()
EN1 = ttk.Entry(gui2,textvariable=v_getname,font=('TH SarabunPSK',20),width=40)
EN1.pack(padx=30,pady=00)
def Entername(event=None):
global getname
getname = v_getname.get()
gui2.withdraw()
gui.attributes('-topmost',True)
E1.focus()
gui.attributes('-topmost',False)
import random
print('GETNAME',getname)
if getname == '' or getname == None:
num = random.randint(10000,99999)
getname = str(num)
user.set(getname)
chatbox.insert(INSERT,'สวัสดี' + getname)
################ RUN SERVER ###########################
global client
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
try:
client.connect((HOST,POST))
firsttext = 'NAME|' + user.get()
client.send(firsttext.encode('utf-8'))
task = threading.Thread(target=handler,args=(client,))
task.start()
except:
print('ERROR!')
messagebox.showerror('Connection Failed', 'ไม่สามารถเชื่อมต่อกับ server ได้')
BB2 = ttk.Button(gui2,text='Enter Chatroom',command=Entername)
BB2.pack(ipady=10,ipadx=20,pady=20)
EN1.bind('<Return>',Entername)
def Close():
gui2.attributes('-topmost',False)
checkenter = messagebox.askyesno('ยืนยันการปิดโปรแกรม','หากไม่กรอกชื่อจะไม่สามารถใช้งานได้\nคุณต้องการออกจากโปรแกรมหรือไม่?')
print('CHECK: ',checkenter)
if checkenter == True:
gui2.destroy()
else:
gui2.attributes('-topmost',True)
gui2.protocol('WM_DELETE_WINDOW',Close)
gui2.mainloop()
gui2dialog()
gui.mainloop()
โค้ดทั้งหมดของ FastAPI
from fastapi import FastAPI
from typing import Optional
from pydantic import BaseModel
app = FastAPI()
class Room(BaseModel):
nameroom: str
HOST: Optional[str] = None
POST: str
room_stock = []
@app.post('/addroom/')
def AddRoom(room: Room):
room_stock.append(room.dict())
print(room_stock)
return room
@app.get('/roomchat/')
def AllRoom():
return room_stock
@app.put('/update/{ID}')
def UpdateRoom(ID: int, room: Room):
room_stock[ID] = room.dict()
return {'ID':ID,'message':'update','data': room.dict()}
@app.delete('/delete/{ID}')
def DeleteRoom(ID: int):
data = room_stock[ID]
del room_stock[ID]
return {'ID':ID,'message':'delete','data':data }
7.สรุปผลและข้อเสนอแนะ
จากผลการทดลองพบว่าการพูดคุยผ่านโปรแกรม Chat สามารถทำงานได้ปกติ แต่ทางด้านของ API เมื่อทำการปิด Server ของ API ในเครื่องจะไม่สามารถเข้าไปดูหน้า API ได้เนื่องจากทางผู้เขียนไม่มี Server ในการเก็บข้อมูลของ API ทำให้เมื่อทำการปิดแล้วจะไม่สามมารถเข้าไปดูหน้า API ได้
8.ข้อมูลอ้างอิง
1.การสร้าง Server และ Client ของโปรแกรม Chat
ข้อมูลเกี่ยวกับ Socket
ข้อมูลเกี่ยวกับ Tkinter
https://realpython.com/python-gui-tkinter/
2.การสร้าง API
ข้อมูลเพิ่มเติมเกี่ยวกับ FastAPI
ข้อมูลเพิ่มเติมเกี่ยวกับ FastAPI