Bài 4- Giải thuật và các kỹ thuật cơ
bản trong lập trình Client
Chủ đề
Nắm được các bước (giải thuật) lập trình chương
trình Client
Biết cách sử dụng các hàm Socket API trong lập
trình Client
Thực hành lập trình một chương trình Client đơn
giản
Nội dung
4.1) Cấu trúc tổng quát chương trình Client
4.2) Giải thuật tổng quát chương trình Client
4.3) Cách gọi hàm Socket API trong từng bước
4.1 Cấu trúc chương trình Client
Về cơ bản, client không yêu cầu mức ưu tiên cao
trong hệ thống
Không yêu cầu nhiều tài nguyên và không cần
hoạt động liên tục
Cấu trúc chương trình gồm 3
module
Module giao diện để tương tác với user
Module kết nối với server
Module thực thi giao thức trao đổi dữ liệu với
server
Gửi bản tin yêu cầu đến Server
Nhận bản tin phản hồi từ Server và xử lý
Các chức năng hỗ trợ thực thi
giao thức
Đọc và phân tích cú pháp bản tin giao thức
Client/Server
Xử lý dữ liệu trước khi gửi và sau khi nhận
Thao tác với bộ đệm buffer chứa dữ liệu
Hiển thị trạng thái phiên làm việc
Thông báo và xử lý lỗi gặp phải
Đọc/ghi dữ liệu từ tệp tin
4.2) Giải thuật chương trình client có
5 bước
Một Client nói chung cần thực hiện 5 bước sau
Bước 1) Xác định địa chỉ remote socket phía
server
Bước 2) Tạo local socket tại client
Bước 3) Kết nối local socket với remote
socket
Bước 4) Gửi/nhận/xử lý dữ liệu qua kết nối
socket
Bước 5) Đóng kết nối
Phân rã chức năng theo module
Bước 1 -> 3 được thực hiện bởi module kết nối
socket
Bước 4 ->5 được thực hiện bởi module thực thi
giao thức (module chính)
Bước 1 3 là thao tác chung đối với mọi Client
Bước 4 và 5 tùy theo từng giao thức ứng dụng
như HTTP, FTP, POP3 hay SMTP
Ví dụ minh họa: Chương trình
EchoClient
Chức năng: kiểm tra phản hồi của server qua
TCP/IP
Các công đoạn xây dựng chương trình gồm:
Các chức năng bổ trợ: truy vấn thông tin, thông báo
lỗi
Lập trình module kết nối
Lập trình module tương tác với Server theo giao
thức ứng dụng Echo
Lập chương trình chính
Giao thức ECHO
Echo dùng để kiểm tra hoạt động của mạng và của
server
Client gửi đến Server 1 bản tin là một xâu kí tự bất
kỳ
Server gửi trả lại một bản tin với nội dung hoàn
toàn trùng với bản tin mà client gửi đến
Ví dụ:
Client gửi bản tin ”HELLO”
Server nhận được và gửi lại “HELLO”
Client gửi “How are you?”
Server gửi lại “How are you?”
Cách thức hoạt động
User chạy chương trình TCPEchoClient với tham
số hàm main là tên server
TCPEchoClient.exe <tên server>
Nếu kết nối thành công, nhập bản tin là xâu kí tự
từ bàn phím và gửi bản tin đến server
Nhận bản tin phản hồi và in ra màn hình
Đóng kết nối và kết thúc nếu bản tin nhập vào là
xâu rỗng
Các tệp mã nguồn của project
socketbasic.cpp: Module chức năng khởi tạo và
giải phóng WS, truy vấn thông tin
errorhandler.cpp: Module chức năng báo lỗi
connect.cpp : chức năng kết nối đến server
echoclient.cpp: chức năng trao đổi các bản tin
theo giao thức Echo
Bước 1: Xác định địa chỉ socket
phía Server
Internet
POP3
transport
network
interface
Server
transport
network
interface
Client
UDP
TCP
Địa chỉ Socket của tiến trình Server
- Kiểu Socket (TCP hay UDP)
- Cổng
- Địa chỉ IP
Xác định địa chỉ Socket của
server ntn ?
Địa chỉ của remote socket phía
Server có 3 tham số
Loại socket: chọn TCP hoặc UDP tùy vào ứng
dụng
FTP : TCP socket
POP3: TCP socket
DNS: UDP socket
Số cổng: chọn cổng của dịch vụ tương ứng
FTP: 21
POP3: 110
Địa chỉ IP: lấy từ hostname của server hoặc địa
chỉ IP dạng thập phân
hostname: ftp.nuce.edu.vn
Địa chỉ IP: 220.231.122.114
Có 2 cách lấy địa chỉ IP
Nếu biết hostname, ví dụ: ftp.nuce.edu.vn
Sử dụng hàm API gethostbyname để lấy thông tin
về trạm server qua hostname
Thông tin hàm trả về là cấu trúc hostent
Thông qua trường h_addr_list có thể lấy địa chỉ IP
dạng 4-byte
Nếu biết địa chỉ IP dạng thập phân, ví dụ :
“220.231.122.114”
Sử dụng hàm API inet_addr để chuyển đổi thành
địa chỉ IP dạng số 32 bit
Cách lấy số cổng
Nếu biết số cổng trực tiếp, ví dụ 80, 7 hoặc 21
Sử dụng hàm htons để biến đổi số cổng sang dạng
NBO
Nếu chỉ nhớ tên dịch vụ, ví dụ HTTP, ECHO, FTP
Sử dụng hàm API getservbyname để truy vấn
thông tin dịch vụ từ tên dịch vụ
Sử dụng cấu trúc địa chỉ socket
Địa chỉ socket được lưu trong cấu trúc dữ liệu
struct sockaddr_in
Chương trình cần khai báo một biến kiểu struct
sockaddr_in và gán giá trị cổng, địa chỉ IP vừa lấy
được cho các trường dữ liệu tương ứng
struct sockaddr_in sin; // biến cấu trúc địa chỉ socket
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = server_addr.s_addr; //gán địa chỉ IP
sin.sin_port = server_port; // gán số cổng
Bước 2: Tạo local socket tại
Client
Mỗi socket được tham chiếu bởi một biến kiểu
SOCKET
Sử dụng hàm API socket để tạo 1 socket mới
PF_INET: họ giao thức TCP/IP
sock_type = SOCK_STREAM nếu tạo TCP socket
sock_type = SOCK_DGRAM nếu tạo UDP socket
SOCKET s = socket(PF_INET, sock_type, 0);
Tiếp…
Nếu thành công, hàm socket trả về một giá trị
kiểu SOCKET
Nếu không thành công, hàm trả về giá trị hằng
INVALID_SOCKET
Bước 3: Kết nối đến remote
socket tại Server
Gọi hàm API connect và truyền tham số cần thiết
để kết nối với socket tại server
Nếu giá trị trả về ret == SOCKET_ERROR
không kết nối được, ngược lại là kết nối thành
công
int ret = connect(s, (struct sockaddr*) &sin, sizeof(sin));
if(ret==SOCKET_ERROR)
errexit(GetLastError(), "Khong ket noi duoc voi server: %s,
%s", host, service);
s: biến local socket vừa tạo
sin : biến cấu trúc địa chỉ socket của server
Chú ý
Có nhiều khả năng xảy ra lỗi khi kết nối
Server không trả lời
Server từ chối kết nối
Time out do trễ truyền dữ liệu
…
Khi lập trình mạng, cần chú ý kiểm tra kết quả gọi
hàm SOCKET API để biết thao tác thực hiện có
thành công hay không
Nếu không kiểm tra mà vẫn tiếp tục các thao tác
tiếp theo sẽ dẫn đến lỗi chương trình
Bước 4: Client tương tác với Server
Client gửi bản tin yêu cầu bằng cách
Tạo nội dung bản tin và đưa bản tin vào vùng đệm
buffer (bộ nhớ)
Gọi hàm API send để gửi bản tin qua socket
Client nhận bản tin phản hồi từ server
Chuẩn bị vùng đệm buffer sẽ nhận nội dung bản tin
Gọi hàm API recv để đọc dữ liệu từ socket vào
buffer
int byte_send = send(s, buffer, msg_length, 0);
int byte_recv = recv(s, buffer, sizeof(buffer), 0);
Hàm send và recv
Nếu gửi dữ liệu thành công, hàm trả về số byte
dữ liệu đã gửi
Ngược lại, hàm trả về giá trị SOCKET_ERROR
Để tìm nguyên nhân gây lỗi, gọi hàm
GetLastError()
Tương tự, hàm recv trả về số byte nhận được,
nếu thành công, trả về SOCKET_ERROR nếu
gặp lỗi
Chuẩn bị vùng đệm buffer
Vùng đệm có thể khai báo dưới dạng mảng kiểu
char
char buffer[512]; // vùng đêm 512 bytes
Chú ý tránh tràn vùng đệm gây lỗi chương trình
khi gọi hàm recv nhiều lần
Bước 5: Đóng kết nối
Sau khi kết thúc gửi/nhận các bản, client có thể
đóng kết nối
Gọi hàm API closesocket
closesocket(s)