- BT cho môn
CSC10008-18CLC3
: "Mạng máy tính" - Họ và tên: Bùi Văn Thiện
- MSSV:
18127221
./
├───dist
│ requirements.txt : List các thư viện ngoài (dùng cho crawler.py)
│
├───docs
│ README.md : File báo cáo này (bản Markdown)
│ README.pdf : File báo cáo này (bản PDF)
│
└───src
server.py : Server
crawler.py : Trình scrap trang web để lấy KQXS
utils.py : Công cụ chung cho server + crawler;
Giá trị mặc định chung cho server + client
client.py : Client
rogue_client.py : Giả lập client tự động
(dùng để test Server)
File log và database được lưu trong thư mục data
(cùng level với src
).
- Chạy
crawler.py
để lấy dữ liệu về. - Chạy
server.py
.Ctrl-C
để dừng server. - Chạy
client.py
. Gõh
(hoặchelp
) để yêu cầu hướng dẫn từ server.Ctrl-C
để ngắt kết nối và dừng client.
BT này được viết bằng Python 3.8.2 trên môi trường Windows (sử dụng IDE là VSCode), có sử dụng 2 thư viện bên ngoài: BeautifulSoup4
(parse, truy vấn và sửa đổi cây HTML/XML) và requests
(tạo và xử lý HTTP request; thay thế cho urllib.request
). Dữ liệu được lưu lại dưới dạng JSON (kết quả sổ xố) và plain text (log ghi lại trong quá trình chạy)
Tất cả các giao tiếp giữa 2 bên client và server đều thông qua TCP sockets được cung cấp bởi module socket
.
Nguồn dữ liệu được cung cấp bởi Xổ Số Đại Phát (kết quả xổ số) và Xổ Số Minh Ngọc (cơ cấu giải thưởng1 cho Miền Bắc2, Miền Trung, Miền Nam)
server.py
là server chính.- Mô hình xử lý của server là song song (mỗi một client kết nối tới có một process tương ứng được tạo để đáp ứng yêu cầu.)
- Server có 4 phần chính:
- Database: các hàm truy vấn file database
Province_PrintAll()
liệt kê tất cả các tỉnh (hoặcmax_count
số tỉnh, nếu có đưa thêm tham số đó vào) có mở kết quả xố số, sắp xếp theo ngày gần nhất trướcProvince_PrintResult()
tra cứu và trả về kết quả xổ số của một tỉnhPList_Search
dùng để map tên tỉnh đã được loại bỏ dấu và khoảng cách với tên tỉnh nguyên vẹn tương ứng. Dictionary này được dùng trong việc nhận yêu cầu là tên tỉnh từ phía client.
- Prizes: các hàm liên quan tới cơ cấu giải thưởng
Prize_Check()
kiểm tra vé của một tỉnh xác định có trúng thưởng khôngPrize_Print()
định dạng kết quả nhận được từPrize_Check()
để cho người dùng đọc
- Clients: hàm làm việc với clients
client_handler()
tiếp nhận một client và bắt đầu một phiên nghe yêu cầu — đưa phản hồi
- main: "hàm main" của server. Xem phần Kịch bản để biết các bước khởi động và xử lý của server.
- Database: các hàm truy vấn file database
crawler.py
là công cụ đi kèm để scrap trang web- Crawler này được thiết kế để hoạt động cụ thể chỉ trên trang Xổ số Đại Phát.
PAGES
liệt kê cụ thể các trang con (và thông tin vùng miền đi kèm) tương ứng với kết quả của các tỉnh. Hiện tại có tổng cộng 41 trang con.province_parse_site()
tải trang kết quả của một tỉnh về và trích xuất kết quả.
utils.py
cung cấp những "quy ước" chung và công cụ dùng chung cho bên server liên quan đến:- File:
VAR_ROOT
thống nhất chỗ các file dữ liệu được lưuvar_root_check()
tự động tạoVAR_ROOT
nếu chưa cóDB_FILE
&DB_LOCKFILE
chỉ tên file database và lockfile đi kèmlockfile()
là cơ chế lockfile cơ bản để bảo vệdb
khi có crawler chạy trong nềnLOG_FILE
chỉ tên log file
- Log:
log
được cài đặt để ghi log đồng thời ra log file và trên console (stdout
)- Trình ghi log này được sử dụng bởi server và crawler để thay thế cho việc
print()
các thông báo.
- Ngày giờ:
today
lấy thời gian hiện tại (có thể không chính xác bằng việc lấy thẳng từdatetime.datetime.now()
, nhưng gọitoday
ở những chỗ chỉ cần thông tin ngày tháng sẽ tiện hơn)map_weekday()
chuyển đổi quy ước weekday của Python (0-5
là Thứ 2-7,6
là CN) sang quy ước weekday dễ đọc hơn (2-7
làThứ hai - Thứ bảy
,1
làChủ nhật
)
- Xử lý chuỗi:
strip_accent()
được dùng để loại bỏ dấu và khoảng cách khỏi tên tỉnh (VD:Sài Gòn
sẽ thànhSaiGon
)
- File:
client.py
là phần giao diện (CLI) cho người dùng.- Client ở đây tương đối đơn giản, chỉ có nhiệm vụ kết nối đến server và bắt đầu gửi yêu cầu — nhận kết quả
rogue_client.py
giả lập client tự động để test server.- Mỗi một client là một process
- Kịch bản của client ở đây cũng tương tự như
client.py
, nhưng client sẽ không hiện lên phản hồi của server và hành động gửi yêu cầu được thực hiện một cách ngẫu nhiên (gửi yêu cầu trợ giúp, list danh sách tỉnh, tra cứu tỉnh hoặc dò vé) với khoảng thời gian chờ giữa các yêu cầu ngẫu nhiên (từ 50ms đến 1s)
utils.py
cung cấp những "quy ước" chung cho server và client:HOST
vàPORT
chỉ định địa chỉ server bind/client kết nối đếnMSG_SIZE
chỉ định kích thước buffer nhận tin của client (socket.socket.recv()
)
Kịch bản này là phần giải thích chi tiết của Hướng dẫn nhanh cho lần đầu chạy.
- Crawler
- Check coi có các crawler khác / các chương trình khác đang làm việc với file database hay không thông qua sự hiện diện của lockfile.
- Nếu lockfile tồn tại: dừng crawler và thông báo
- Nếu lockfile không tồn tại: đặt lockfile và chỉ gỡ khi crawler đã hoàn tất
- Kiểm tra website có truy cập được không (bằng cách thử truy cập trang chủ và xem mã của HTTP response. Nếu là
200
thì ổn; nếu không phải thì dừng lại và báo lỗi.) - Gọi
province_parse_site()
đối với từng tỉnh một trongPAGES
- Lưu lại thành file dưới định dạng:
- Dòng đầu tiên là timestamp thời gian khi đã hoàn tất việc lấy dữ liệu
- Dòng thứ hai là một JSON object chứa ngày mở thưởng và kết quả xổ số tương ứng với các tỉnh
- Check coi có các crawler khác / các chương trình khác đang làm việc với file database hay không thông qua sự hiện diện của lockfile.
- Server3
- Check sự hiện diện của lockfile
- Nếu lockfile tồn tại; dừng server và thông báo
- Trong trường hợp còn lại: server sẽ tiếp tục tới bước tiếp theo.
- Đọc file database
- Kiểm tra database có quá hạn (1 ngày) chưa: nếu có thì cảnh báo
- Tạo socket, bind vào địa chỉ và port có sẵn trong
utils.py
- Bắt đầu vòng lặp chờ client4:
- Server có khoảng 1 giây để nghe và accept client mới.
- Nếu vượt quá thời gian trên mà không có client mới, socket sẽ timeout và quay lại đầu vòng lặp
- Nếu có một client mới: server sẽ tạo một process là một "client handler" để tiếp nhận client mới đó
- Khi người dùng nhấn
Ctrl-C
: server sẽ terminate tất cả các client handlers và dừng lại.
- Check sự hiện diện của lockfile
- Client handler
- Gửi phản hồi đầu tiên cho server: địa chỉ của chính client đó
- Bắt đầu vòng lặp nghe yêu cầu — đưa phản hồi cho đến khi client chủ động ngắt kết nối (hoặc bản thân client handler bị terminate bởi phần main của server)
- Khi client yêu cầu trợ giúp (
h
hoặchelp
): gửi trợ giúp và list các tỉnh có kết quả xổ số trong 2 ngày gần đây nhất - Khi client yêu cầu danh sách tất cả các tỉnh được hỗ trợ (
p
hoặcprovinces
): gửi danh sách các tỉnh nhóm và xếp theo ngày gần đây nhất trước - Khi client yêu cầu tra cứu tỉnh hoặc dò vé: thực hiện việc tra cứu và trả về kết quả như thường
- Khi client yêu cầu trợ giúp (
- Client
- Tự động thử kết nối đến server tại địa chỉ và port có sẵn trong
utils.py
- Chờ phản hồi đầu tiên của server (ở đây server sẽ trả lại chính địa chỉ của client) và đưa ra trên màn hình.
- Trong trường hợp nếu quá 5 giây chưa thấy server phản hồi, client sẽ tự động ngắt kết nối và dừng lại.
- Bắt đầu vào vòng lặp đưa yêu cầu — nghe phản hồi cho tới khi người dùng nhấn
Ctrl-C
(khi đó client sẽ ngắt kết nối và dừng lại.)
- Tự động thử kết nối đến server tại địa chỉ và port có sẵn trong
- Môi trường Linux chưa được hỗ trợ hoàn toàn do BT này chủ yếu được viết trên Windows (như đã mô tả ở trên.) Các lỗi hiện tại đã biết (đã thử trên Ubuntu 20.04
focal
trên Python 3.7.6):- Server không thể tắt được bằng
Ctrl-C
- Server không thể tắt được bằng
- Một điểm trừ của server là không có quan hệ với crawler: khi lần đầu chạy mà không có dữ liệu, server sẽ không tự động gọi crawler; khi dữ liệu đã bị hết hạn (quá 1 ngày), server chỉ cảnh báo mà không tự động gọi crawler.
Footnotes
-
Để đơn giản hóa cách server check vé số, giải phụ đặc biệt và giải khuyến khích không được hỗ trợ. Điều này cũng đồng nghĩa kết quả trúng thưởng có được từ việc sử dụng chương trình trong BT này chỉ mang tính chất tham khảo. ↩
-
Vé số miền Bắc ngoài 6 số ngẫu nhiên còn có thêm một phần tên "kí hiệu trúng giải đặc biệt" theo định dạng
{1-2 chữ số}{2 chữ cái}
. Do đề BT không yêu cầu rõ ở điểm này nên để đơn giản hóa cách server check vé số, mã đặc biệt này sẽ bị bỏ qua. ↩ -
Thực tế phần bắt đầu của server là ở Database: server sẽ đọc file dữ liệu trước khi bắt đầu vào "main" thật sự. Việc đưa bước đọc file thành bước đầu tiên là để hỗ trợ viết/gỡ lỗi các truy vấn đến database (được thực hiện thông qua Python Interactive Console) ↩
-
Đây là một workaround (có thể là dạng busy waiting) cho việc
socket.accept()
chặn các exception nhưKeyboardInterrupt
không nhận được bởi main. ↩