



















Preview text:
I. Giới thiệu đề tài:
• Mục tiêu: Xây dựng một ứng dụng mạng cho phép người dùng quản lý tài
khoản, thực hiện thao tác với file và thư mục từ xa, và phân quyền truy cập cho từng người dùng.
• Ứng dụng thực tế: Phù hợp cho các hệ thống nội bộ cần quản lý dữ liệu an
toàn, chia sẻ file nhanh chóng và phân quyền chi tiết cho người dùng.
• Công nghệ mạng: Dựa trên giao thức TCP/IP và tham khảo các chức năng của
giao thức FTP để xử lý các thao tác file/thư mục.
II. Phần việc đã thực hiện:
1. Đăng ký và đăng nhập: 1.1 Đăng ký:
• Mã code thực hiện đăng ký: • Giải thích: ❖ Nhận username:
o readWithCheck(sock, buff, BUFF_SIZE): Nhận username từ client.
o Nếu client gửi "0" → Hủy đăng ký (gửi mã BACK_TO_MENU về client).
o Nếu không, sao chép username từ buff.
❖ Kiểm tra username tồn tại:
o checkExistence(1, *users, username):
▪ Kiểm tra trong danh sách users.
▪ Trả về 1 nếu username đã tồn tại, 0 nếu không.
o Nếu username tồn tại, gửi mã EXISTENCE_USERNAME.
o Nếu không tồn tại, gửi mã REGISTER_SUCCESS và tiếp tục nhận password.
❖ Nhận và lưu password:
o Nhận password từ client.
o Tạo một user_struct mới để lưu thông tin username và password.
o Thêm user_struct vào danh sách users bằng insertEnd.
o Ghi danh sách users vào file bằng saveUsers.
❖ Hoàn tất đăng ký:
o Gửi mã 800 ( tương ứng với REGISTER_SUCCESS ) để thông báo thành công. 1.2 Đăng nhập:
• Mã code thực hiện đăng nhập: • Giải thích: ❖ Nhận username:
• readWithCheck(sock, buff, BUFF_SIZE): Nhận username từ client.
• Nếu client gửi "0" → Hủy đăng nhập (gửi mã BACK_TO_MENU - 925).
• Nếu không, sao chép username từ buff.
❖ Kiểm tra username:
• findByName(1, users, username):
o Tìm username trong danh sách users.
o Trả về con trỏ user_struct nếu tìm thấy, NULL nếu không.
• Nếu không tồn tại, gửi mã NON_EXISTENCE_USERNAME ( 904 ).
• Nếu tài khoản đã online (status == 1), gửi mã
USER_ALREADY_ONLINE( 923 ).
• Nếu hợp lệ, gửi mã LOGIN_SUCCESS ( 801 ) và tiếp tục nhận password.
❖ Nhận và kiểm tra password:
• strcmp((*loginUser)->password, password):
o So sánh password client gửi với password trong user_struct.
• Nếu sai, gửi mã INCORRECT_PASSWORD ( 900 ). • Nếu đúng:
o Đánh dấu người dùng online (status = 1).
o Lưu danh sách users vào file bằng saveUsers.
o Gửi mã LOGIN_SUCCESS ( 801 ) để thông báo thành công.
❖ Hoàn tất đăng nhập:
• Nếu thành công, trả về 1.
• Nếu thất bại, trả về 0.
• Lưu trạng thái vào file user.txt.
2. Tính năng phân quyền người dùng: II.1. Tạo phòng: • Mã code: • Giải thích:
❖ Nhận tên phòng (group_name):
• readWithCheck(sock, buff, 100): Nhận tên phòng từ client.
❖ Kiểm tra phòng đã tồn tại:
• checkExistence(2, *groups, buff): Kiểm tra danh sách groups xem tên phòng đã tồn tại chưa.
• Nếu đã tồn tại, gửi thông báo "Tên phòng đã được sử dụng.". ❖ Tạo phòng:
• Tạo group_struct mới với các thông tin:
o group_name: Tên phòng.
o owner: Chủ phòng (người tạo).
o members: Danh sách thành viên (ban đầu chỉ có chủ phòng).
• Thêm phòng vào danh sách groups.
❖ Thêm chủ phòng vào danh sách thành viên:
• addMember(*groups, group_name, loginUser->user_name): Thêm chủ
phòng vào danh sách thành viên.
❖ Tạo thư mục vật lý:
• Tạo thư mục tương ứng để lưu phòng trên server.
II.2. Phân quyền người dùng: • Mã code: • Giải thích:
❖ Kiểm tra quyền chủ nhóm: Hàm isOwnerOfGroup(groups, group_name,
username) kiểm tra xem username có phải là chủ nhóm group_name:
Nếu isOwnerOfGroup trả về 1, cho phép thực hiện thao tác đặc quyền (như mời thành viên,
phê duyệt yêu cầu, xóa thành viên).
Nếu trả về 0, từ chối thao tác.
❖ Biến group_struct lưu thông tin nhóm, trong đó owner là tên chủ nhóm.
II.3. Người dùng tham gia và được phê duyệt yêu cầu tham gia nhóm: • Mã code: • Giải thích:
❖ Ghi log yêu cầu phê duyệt:
• Ghi nhận yêu cầu từ client.
❖ Kiểm tra quyền chủ nhóm:
• Sử dụng isOwnerOfGroup() để xác định người dùng hiện tại có phải chủ nhóm không.
• Nếu không phải, gửi mã lỗi NOT_OWNER_OF_GROUP về client.
❖ Lấy danh sách yêu cầu:
• Dùng getRequests() để lọc danh sách các yêu cầu liên quan đến nhóm hiện tại
(current_group) từ requests.
• Kết quả được chuyển thành chuỗi bằng convertUserRequestsToString() và gửi về client.
❖ Nhận lựa chọn từ client:
• Nhận tên thành viên cần phê duyệt (buff).
• Nếu không có yêu cầu nào, dừng xử lý.
❖ Xóa yêu cầu đã xử lý:
• Dùng deleteRequest() để xóa yêu cầu khỏi requests.
• Ghi lại thay đổi bằng writeToRequestFile().
❖ Thêm thành viên vào nhóm:
• Gọi addMember() để thêm thành viên vào nhóm.
• Gọi addGroupToJoinedGroups() để cập nhật danh sách nhóm của thành viên.
• Lưu lại thay đổi vào file (writeToGroupFile() và saveUsers()).
• Gửi mã APPROVE_SUCCESS nếu thành công, hoặc thông báo lỗi nếu thất bại.
II.4. Kick thành viên ra khỏi nhóm: • Mã code: • Giải thích:
❖ Tìm nhóm cần thao tác
• Hàm nhận vào một danh sách các nhóm (groups) và duyệt qua danh sách này để
tìm nhóm có tên trùng khớp với group_name.
• Quá trình duyệt sử dụng một con trỏ (biến groups.cur) để kiểm tra từng nhóm.
• Khi tìm thấy nhóm, hàm lấy danh sách thành viên của nhóm đó để tiếp tục xử lý.
❖ Tìm thành viên cần xóa trong nhóm
• Sau khi lấy được danh sách thành viên của nhóm (members), hàm kiểm tra danh
sách này để tìm thành viên có tên trùng khớp với username.
• Có hai trường hợp xử lý:
o Thành viên ở đầu danh sách: Nếu thành viên cần xóa nằm ở đầu, hàm
chỉ cần cập nhật điểm bắt đầu (root) của danh sách để bỏ qua thành viên đó.
o Thành viên ở giữa hoặc cuối danh sách: Hàm sẽ duyệt qua danh sách
thành viên, giữ lại con trỏ đến thành viên trước đó và sau đó bỏ qua nút
chứa thông tin thành viên cần xóa. Bộ nhớ được giải phóng sau khi nút đó bị loại bỏ.
❖ Cập nhật danh sách thành viên
• Sau khi xóa thành viên, danh sách members được cập nhật lại trong thông tin
của nhóm (group_struct). Điều này đảm bảo danh sách thành viên của nhóm
không còn chứa thông tin về người vừa bị loại bỏ.
❖ Cập nhật danh sách nhóm của người dùng
• Hàm tiếp tục duyệt qua danh sách người dùng (users) để tìm người có tên trùng với username.
• Khi tìm thấy, hàm kiểm tra danh sách các nhóm mà người đó đã tham gia và
loại bỏ thông tin về group_name khỏi danh sách đó.
❖ Lưu lại thay đổi
• Sau khi thực hiện xong các thao tác, hàm ghi lại thông tin nhóm
(writeToGroupFile) và danh sách người dùng (saveUsers) vào file để lưu trữ trạng thái mới.
3. Thao tác với file:
3.1. Tìm kiếm và lựa chọn file: • Mã code: • Giải thích:
❖ Nhận yêu cầu từ client:
o Client gửi tên nhóm qua socket (readWithCheck()).
❖ Tìm kiếm file trong nhóm:
• Hàm: getAllFilesOfGroup()
o Duyệt qua danh sách groups:
▪ Biến groups.cur trỏ lần lượt vào từng nhóm trong danh sách.
▪ So sánh group_name với ((group_struct*)groups.cur->element)- >group_name.
▪ Khi tìm thấy nhóm khớp, trích xuất danh sách files từ
((group_struct*)groups.cur->element)->files.
❖ Chuyển danh sách file thành chuỗi:
• Hàm: convertSimpleFilesToString()
o Duyệt qua danh sách file trong nhóm (files).
o Biến files.cur trỏ lần lượt vào từng file.
o Nối tên file (((simple_file_struct*)files.cur->element)->file_name) vào
chuỗi str để gửi client.
❖ Gửi kết quả về client:
• Chuỗi danh sách file (str) được gửi lại client qua socket (sendWithCheck()). 3.2. Sửa file: Mã code: • Giải thích:
❖ Cập nhật tên file trong danh sách files:
• Bắt đầu từ files.root, duyệt qua từng phần tử trong danh sách (files.cur). • Kiểm tra từng file:
o Nếu path của file khớp với file_name, đổi tên thành new_name.
o Nếu file_name là một phần trong đường dẫn (strstr), cập nhật bằng
cách thay thế chuỗi con file_name thành new_name (hàm replace_substring).
• Cập nhật đường dẫn mới:
o Nếu file nằm trong thư mục con (đường dẫn chứa /), sử dụng hàm
replace_last_segment để thay thế tên cuối cùng của đường dẫn.
❖ Cập nhật danh sách file của nhóm (groups):
• Tìm nhóm khớp với group_name trong danh sách groups:
o Duyệt qua groups bằng con trỏ groups.cur.
o Khi tìm thấy nhóm, trích xuất danh sách file của nhóm (files).
• Duyệt danh sách file trong nhóm (files_of_group.cur):
o Nếu file khớp với file_name, đổi tên thành new_name.
o Nếu file_name là một phần trong đường dẫn file, sử dụng
replace_substring để thay thế tên file trong đường dẫn.
❖ Đổi tên file vật lý trên hệ thống:
o Tạo câu lệnh mv trong biến cmd rồi sử dụng system(cmd) để thực thi lệnh đổi tên file vật lý.
❖ Lưu thay đổi vào file dữ liệu:
• Ta gọi các hàm sau để lưu thay đổi:
o writeToGroupFile(groups): Lưu thay đổi danh sách file của nhóm vào file dữ liệu nhóm.
o saveFiles(*files): Lưu thay đổi danh sách file toàn cục vào file dữ liệu. 3.3. Xoá file: • Mã code: Giải thích:
❖ Tạo đường dẫn đầy đủ của file/thư mục cần xóa:
Tạo ra một đường dẫn đầy đủ (full_path) dựa trên:
• group_name: Thư mục chính của nhóm.
• file_path: Đường dẫn file hoặc thư mục cần xóa.
Ví dụ: Nếu group_name = "project1" và file_path = "docs/file.txt", thì full_path sẽ là:
./files/project1/docs/file.txt
❖ Kiểm tra loại file:
Hàm sử dụng stat() để kiểm tra full_path:
• Nếu là thư mục:
o Dùng opendir() để mở thư mục.
o Duyệt qua các mục con trong thư mục bằng readdir().
o Gọi lại chính hàm deleteFile (đệ quy) để xử lý từng file hoặc thư mục con.
o Đảm bảo xóa hết nội dung trước khi xóa thư mục cha. • Nếu là file:
o Tiếp tục với các bước xử lý để xóa file.
❖ Loại bỏ file khỏi danh sách files
• Duyệt qua danh sách files:
o Nếu file nằm ở đầu danh sách (root), dùng deleteBegin() để xóa.
o Nếu file nằm ở giữa hoặc cuối danh sách, cập nhật liên kết (prev, cur) để
bỏ qua phần tử bị xóa.
Dữ liệu của danh sách files được cập nhật để đảm bảo file không còn được quản lý trong hệ thống.
❖ Cập nhật danh sách file của nhóm
• Hàm tìm nhóm liên quan (group_name) trong danh sách groups. • Khi tìm thấy nhóm:
o Giảm số lượng file của nhóm (number_of_files--).
o Loại bỏ file khỏi danh sách file của nhóm:
▪ Nếu file nằm ở đầu danh sách, dùng deleteBegin() để xóa.
▪ Nếu file nằm ở giữa hoặc cuối danh sách, duyệt danh sách và cập nhật liên kết.
❖ Xóa file hoặc thư mục vật lý
• Sau khi cập nhật danh sách file và nhóm, hàm sử dụng lệnh rm -rf để xóa file
hoặc thư mục trên hệ thống ❖ Lưu trạng thái
• Hàm lưu lại danh sách file và nhóm đã cập nhật vào các file dữ liệu để đảm bảo tính nhất quán:
o writeToGroupFile(groups): Lưu trạng thái của danh sách nhóm.
o saveFiles(*files): Lưu trạng thái của danh sách file. 4. Xử lý truyền dòng:
4.1. Truyền tệp từ server sang client: • Mã code: • Giải thích:
❖ Server nhận yêu cầu tải tệp từ client:
• fname và group_name được client gửi đến để xác định file cần tải. ❖ Mở file:
• fopen: Mở file ở chế độ "ab" (append binary) để ghi dữ liệu nhị phân.
• Nếu không mở được file (ví dụ: sai đường dẫn, quyền truy cập không đủ),
ghi log lỗi và trả về -1.
❖ Nhận dữ liệu từ client:
• readWithCheck(sock, recvBuff, 1024):
Đọc dữ liệu từ socket sock, lưu vào bộ đệm recvBuff, tối đa 1024 byte mỗi lần. Kết quả:
o Trả về số byte thực tế nhận được.
o Trả về 0 nếu kết nối bị đóng.
o Trả về -1 nếu có lỗi.
• fwrite(recvBuff, 1, bytesReceived, fp):
Ghi dữ liệu từ recvBuff vào file mở bởi fp.
❖ Ghi log tiến trình nhận dữ liệu:
• Sử dụng biến sz: Một biến double để tính tổng số chunk đã nhận. ❖ Kết thúc:
• bytesReceived < 1024:
• Nếu số byte nhận được ít hơn 1024 (chunk không đầy):
o Gửi tín hiệu "broken" về client, báo rằng server đã nhận xong hoặc dữ liệu bị lỗi. o Dừng vòng lặp (break).
• bytesReceived == 1024:
• Nếu chunk đầy, gửi tín hiệu "continue" để yêu cầu client gửi chunk tiếp theo.
4.2. Nhận tệp từ client: • Mã code: • Giải thích: ❖ Mở file:
• Mở file với đường dẫn filePath để ghi dữ liệu. • Chế độ mở:
o "ab": Ghi dữ liệu vào cuối file (nếu file đã tồn tại). Dữ liệu được ghi ở dạng nhị phân. • Kiểm tra lỗi:
o Nếu không mở được file (fp == NULL), ghi log lỗi và trả về -1.
❖ Nhận dữ liệu từ client:
• readWithCheck(sock, recvBuff, 1024)
• Đọc dữ liệu từ socket sock vào bộ đệm recvBuff.
• Đọc tối đa 1024 byte trong mỗi lần gọi. • Kết quả:
o Số byte nhận được: Gán vào bytesReceived.
o Kết thúc kết nối: Trả về 0.
o Lỗi: Trả về -1.
• fwrite(recvBuff, 1, bytesReceived, fp)
• Ghi dữ liệu từ recvBuff vào file fp.
• Ghi đúng số byte mà readWithCheck đã nhận được (bytesReceived). • Cập nhật log:
• Ghi log số byte nhận được (bytesReceived).
• Ghi log kích thước dữ liệu nhận được tính bằng MB (mặc dù cách tính sz không chính xác).
❖ Kiểm tra chunk cuối:
• Nếu bytesReceived < 1024:
o Có nghĩa là chunk cuối cùng của file (file đã hết).
o Gửi tín hiệu "broken" cho client để báo dừng truyền dữ liệu. o Thoát khỏi vòng lặp.
• Nếu bytesReceived == 1024:
o Chunk đầy, yêu cầu client gửi tiếp bằng cách gửi "continue".
❖ Đóng file và kiểm tra lỗi: