Bài giảng Lập trình mạng - Chương 3: Windows Socket - Lương Ánh Hoàng

Windows Socket (WinSock)

– Phiên bản hiện tại là WinSock 2.0

– Các ứng dụng sẽ giao tiếp với thư viện liên kết động ở

tầng trên cùng: WS2_32.DLL.

– Provider do nhà sản xuất của các giao thức cung cấp.

Tầng này bổ sung giao thức của các tầng mạng khác

nhau cho WinSock như TCP/IP, IPX/SPX, AppleTalk,

NetBIOS.tầng này vẫn chạy ở UserMode.

– WinSock Kernel Mode Driver (AFD.SYS) là driver

chạy ở KernelMode, nhận dữ liệu từ tầng trên, quản lý

kết nối, bộ đệm, tài nguyên liên quan đến socket và giao

tiếp với driver điều khiển thiết bị.

pdf 90 trang kimcuc 21980
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình mạng - Chương 3: Windows Socket - Lương Ánh Hoàng", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

Tóm tắt nội dung tài liệu: Bài giảng Lập trình mạng - Chương 3: Windows Socket - Lương Ánh Hoàng

Bài giảng Lập trình mạng - Chương 3: Windows Socket - Lương Ánh Hoàng
Lương Ánh Ho{ng 
hoangla@soict.hut.edu.vn 
Chương 3. Windows Socket 
• 3.1. Kiến trúc 
• 3.2. Đặc tính 
• 3.3. Lập trình WinSock 
• 3.4. Các phương pháp vào ra 
Chương 3. Windows Socket 
51 
• Windows Socket (WinSock) 
– Bộ thư viện liên kết động của Microsoft. 
– Cung cấp các API dùng để xây dựng ứng dụng mạng hiệu 
năng cao. 
3.1 Kiến trúc 
52 
Application 
Winsock 2 DLL ( WS2_32.DLL) 
Layered/Base Provider 
RSVP Proxy Default 
Provider 
MSAFD.DLL 
Winsock Kernel Mode Driver (AFD.SYS) 
Transport Protocols 
• Windows Socket (WinSock) 
– Phiên bản hiện tại là WinSock 2.0 
– Các ứng dụng sẽ giao tiếp với thư viện liên kết động ở 
tầng trên cùng: WS2_32.DLL. 
– Provider do nhà sản xuất của các giao thức cung cấp. 
Tầng này bổ sung giao thức của các tầng mạng khác 
nhau cho WinSock như TCP/IP, IPX/SPX, AppleTalk, 
NetBIOS...tầng này vẫn chạy ở UserMode. 
– WinSock Kernel Mode Driver (AFD.SYS) là driver 
chạy ở KernelMode, nhận dữ liệu từ tầng trên, quản lý 
kết nối, bộ đệm, tài nguyên liên quan đến socket và giao 
tiếp với driver điều khiển thiết bị. 
3.1 Kiến trúc 
53 
• Windows Socket (WinSock) 
– Transport Protocols là các driver ở tầng thấp nhất, 
điều khiển trực tiếp thiết bị. Các driver này do nhà sản 
xuất phần cứng xây dựng, và giao tiếp với AFD.SYS 
thông qua giao diện TDI ( Transport Driver Interface) 
– Việc lập trình Socket sẽ chỉ thao tác với đối tượng 
SOCKET. 
– Mỗi ứng dụng cần có một SOCKET trước khi muốn trao 
đổi dữ liệu với ứng dụng khác. 
– Đường dây ảo nối giữa các SOCKET sẽ là kênh truyền dữ 
liệu của hai ứng dụng. 
3.1 Kiến trúc 
54 
• Hỗ trợ các giao thức hướng thông điệp (message 
oriented) 
– Thông điệp truyền đi được tái tạo nguyên vẹn cả về kích 
thước và biên ở bên nhận 
3.2 Đặc tính 
55 
• Hỗ trợ các giao thức hướng dòng (stream 
oriented) 
– Biên của thông điệp không được bảo toàn khi truyền đi 
3.2 Đặc tính 
56 
• Hỗ trợ các giao thức hướng kết nối và không kết 
nối 
– Giao thức hướng kết nối (connection oriented) thực 
hiện thiết lập kênh truyền trước khi truyền thông tin. 
Thí dụ: TCP 
– Giao thức không kết nối (connection less) không cần 
thiết lập kênh truyền trước khi truyền. Thí dụ: UDP 
3.2 Đặc tính 
57 
• Hỗ trợ các giao thức hướng kết nối và không kết 
nối 
– Giao thức hướng kết nối (connection oriented) thực 
hiện thiết lập kênh truyền trước khi truyền thông tin. 
Thí dụ: TCP 
– Giao thức không kết nối (connection less) không cần 
thiết lập kênh truyền trước khi truyền. Thí dụ: UDP 
3.2 Đặc tính 
58 
• Hỗ trợ các giao thức tin cậy và trật tự 
– Tin cậy (reliability): đảm bảo chính xác từng byte được 
gửi đến đích. 
– Trật tự (ordering): đảm bảo chính xác trật tự từng byte 
dữ liệu. Byte nào gửi trước sẽ được nhận trước, byte gửi 
sau sẽ được nhận sau. 
3.2 Đặc tính 
59 
• Multicast 
– WinSock hỗ trợ các giao thức Multicast: gửi dữ liệu đến 
một hoặc nhiều máy trong mạng. 
• Chất lượng dịch vụ - Quality of Service (QoS) 
– Cho phép ứng dụng yêu cầu một phần băng thông dành 
riêng cho mục đích nào đó. Thí dụ: truyền hình thời gian 
thực. 
3.2 Đặc tính 
60 
• Chuẩn bị môi trường 
– Hệ điều hành Windows 95/98/2000/Me/XP/2003/Vista/7. 
– Visual Studio C++ 
– Thư viện trực tuyến MSDN 
– Thêm tiêu đề WINSOCK2.H vào đầu mỗi tệp mã nguồn. 
– Thêm thư viện WS2_32.LIB vào mỗi Project bằng cách 
Project => Property => Configuration Properties=> 
Linker=>Input=>Additional Dependencies 
3.3 Lập trình WinSock 
61 
• Khởi tạo WinSock 
– WinSock cần được khởi tạo ở đầu mỗi ứng dụng trước khi có thể sử 
dụng 
– Hàm WSAStartup sẽ làm nhiệm khởi tạo 
 wVersionRequested: [IN] phiên bản WinSock cần dùng. 
 lpWSAData: [OUT] con trỏ chứa thông tin về WinSock cài đặt 
trong hệ thống. 
 Giá trị trả về: 
 Thành công: 0 
 Thất bại: SOCKET_ERROR 
3.3 Lập trình WinSock 
62 
int WSAStartup( 
 WORD wVersionRequested, 
 LPWSADATA lpWSAData 
); 
• Khởi tạo WinSock 
– Thí dụ 
3.3 Lập trình WinSock 
63 
WSADATA wsaData; 
WORD wVersion = MAKEWORD(2,2); // Khởi tạo phiên bản 2.2 
if (WSAStartup(wVersion,&wsaData)) 
{ 
 printf(“Version not supported”); 
} 
• Giải phóng WinSock 
– Ứng dụng khi kết thúc sử dụng WinSock có thể gọi hàm sau để giải 
phóng tài nguyên về cho hệ thống 
 int WSACleanup(void); 
 Giá trị trả về: 
 Thành công: 0 
 Thất bại: SOCKET_ERROR 
3.3 Lập trình WinSock 
64 
• Xác định lỗi 
– Phần lớn các hàm của WinSock nếu thành công đều trả về 0. 
– Nếu thất bại, giá trị trả về của hàm là SOCKET_ERROR. 
– Ứng dụng có thể lấy mã lỗi gần nhất bằng hàm 
int WSAGetLastError(void); 
– Tra cứu lỗi với công cụ Error Lookup trong Visual Studio 
3.3 Lập trình WinSock 
65 
• Tạo SOCKET 
– SOCKET là một số nguyên trừu tượng hóa kết nối mạng của ứng 
dụng. 
– Ứng dụng phải tạo SOCKET trước khi có thể gửi nhận dữ liệu. 
– Hàm socket được sử dụng để tạo SOCKET 
 Trong đó: 
 af: [IN] Address Family, họ giao thức sẽ sử dụng, thường là 
AF_INET. 
 type: [IN] Kiểu socket, SOCK_STREAM cho TCP/IP và 
SOCK_DGRAM cho UDP/IP. 
 protocol: [IN] Giao thức tầng giao vận, IPPROTO_TCP hoặc 
IPPROTO_UDP 
3.3 Lập trình WinSock 
66 
SOCKET socket ( 
 int af, 
 int type, 
 int protocol ); 
• Tạo SOCKET 
– Thí dụ 
3.3 Lập trình WinSock 
67 
SOCKET s1,s2; // Khai báo socket s1,s2 
// Tạo socket TCP 
s1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
// Tạo socket UDP 
s2 = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); 
• Xác định địa chỉ 
– WinSock sử dụng sockaddr_in để lưu địa chỉ của ứng dụng đích 
cần nối đến. 
– Ứng dụng cần khởi tạo thông tin trong cấu trúc này 
3.3 Lập trình WinSock 
68 
struct sockaddr_in{ 
 short sin_family; // Họ giao thức, thường l{ AF_INET 
 u_short sin_port; // Cổng, dạng big-endian 
 struct in_addr sin_addr; // Địa chỉ IP 
 char sin_zero[8]; // Không sử dụng với IPv4 
}; 
• Xác định địa chỉ 
– Sử dụng các hàm hỗ trợ : 
• Chuyển đổi địa chỉ IP dạng xâu sang số nguyên 32 bit 
• Chuyển đổi địa chỉ từ dạng in_addr sang dạng xâu 
• Chuyển đổi little-endian => big-endian (network order) 
• Chuyển đổi big-endian => little-endian (host order) 
3.3 Lập trình WinSock 
69 
// Chuyển 4 byte từ big-endian=>little-endian 
u_long ntohl(u_long netlong) 
// Chuyển 2 byte từ big-endian=>little-endian 
u_short ntohs(u_short netshort) 
unsigned long inet_addr(const char FAR *cp); 
char FAR *inet_ntoa(struct in_addr in); 
// Chuyển đổi 4 byte từ little-endian=>big-endian 
u_long htonl(u_long hostlong) 
// Chuyển đổi 2 byte từ little-endian=>big-endian 
u_short htons(u_short hostshort) 
• Xác định địa chỉ 
– Thí dụ: điền địa chỉ 192.168.0.1:80 vào cấu trúc sockaddr_in 
3.3 Lập trình WinSock 
70 
SOCKADDR_IN InternetAddr; // Khai báo biến lưu địa chỉ 
u_short nPortId = 80; // Khai b|o cổng 
InternetAddr.sin_family = AF_INET;// Họ địa chỉ Internet 
//Chuyển x}u địa chỉ 192.168.0.1 sang số 4 byte dang network-byte 
// order v{ g|n cho trường sin_addr 
InternetAddr.sin_addr.s_addr = inet_addr(“192.168.0.1"); 
//Chuyển đổi cổng sang dạng network-byte order v{ g|n cho trường 
// sin_port 
InternetAddr.sin_port = htons(nPortId); 
• Phân giải tên miền 
– Đôi khi địa chỉ của máy đích được cho dưới dạng tên miền 
– Ứng dụng cần thực hiện phân giải tên miền để có địa chỉ thích hợp 
– Hàm getnameinfo và getaddrinfo sử dụng để phân giải tên miền 
– Cần thêm tệp tiêu đề WS2TCPIP.H 
3.3 Lập trình WinSock 
71 
int getaddrinfo( 
 const char FAR *nodename, // Tên miền hoặc địa chỉ cần ph}n giải 
 const char FAR *servname, // Dịch vụ hoặc cổng 
 const struct addrinfo FAR *hints, // Cấu trúc gợi ý 
 struct addrinfo FAR *FAR *res // Kết quả 
); 
Giá trị trả về 
Thành công: 0 
Thất bại: mã lỗi 
• Phân giải tên miền 
– Cấu trúc addrinfo: danh sách liên kết đơn chứa thông tin về tên 
miền tương ứng 
3.3 Lập trình WinSock 
72 
struct addrinfo { 
 int ai_flags; // Thường l{ AI_CANONNAME 
 int ai_family; // Thường l{ AF_INET 
 int ai_socktype; // Loại socket 
 int ai_protocol; // Giao thứ giao vận 
 size_t ai_addrlen; // Chiều d{i của ai_addr 
 char *ai_canonname; // Tên miền 
 struct sockaddr *ai_addr; // Địa chỉ socket đ~ ph}n giải 
 struct addrinfo *ai_next; // Con trỏ tới cấu trúc tiếp theo 
}; 
• Phân giải tên miền 
– Đoạn chương trình sau sẽ thực hiện phân giải địa chỉ cho tên miền 
www.hut.edu.vn 
3.3 Lập trình WinSock 
73 
addrinfo * result; // Lưu kết quả ph}n giải 
int rc; // Lưu m~ trả về 
sockaddr_in address; // Lưu địa chỉ ph}n giải được 
rc = getaddrinfo(“www.hut.edu.vn”, “http”, NULL, &result); 
// Một tên miền có thể có nhiều địa chỉ IP tương ứng 
// Lấy kết quả đầu tiên 
if (rc==0) 
 memcpy(&address,result->ai_addr,result->ai_addrlen); 
// Xử lý với address... 
• Truyền dữ liệu sử dụng TCP 
– Việc truyền nhận dữ liệu sử dụng giao thức TCP sẽ bao gồm hai 
phần: ứng dụng phía client và phía server. 
– Ứng dụng phía server: 
• Khởi tạo WinSock qua hàm WSAStartup 
• Tạo SOCKET qua hàm socket hoặc WSASocket 
• Gắn SOCKET vào một giao diện mạng thông qua hàm bind 
• Chuyển SOCKET sang trạng thái đợi kết nối qua hàm listen 
• Chấp nhận kết nối từ client thông qua hàm accept 
• Gửi dữ liệu tới client thông qua hàm send hoặc WSASend 
• Nhận dữ liệu từ client thông qua hàm recv hoặc WSARecv 
• Đóng SOCKET khi việc truyền nhận kết thúc bằng hàm 
closesocket 
• Giải phóng WinSock bằng hàm WSACleanup 
3.3 Lập trình WinSock 
74 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
3.3 Lập trình WinSock 
75 
WSAStartup 
socket/ 
WSASocket 
bind 
listen accept 
send/ 
WSASend 
recv/ 
WSARecv 
closesocket 
WSACleanu
p 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm bind: gắn SOCKET vào một giao diện mạng của máy 
3.3 Lập trình WinSock 
76 
int bind( SOCKET s, const struct sockaddr FAR* name, int namelen); 
Trong đó 
s: [IN] SOCKET vừa được tạo bằng hàm socket 
name: [IN] địa chỉ của giao diện mạng cục bộ 
namelen: [IN] chiều dài của cấu trúc name 
Thí dụ 
SOCKADDR_IN tcpaddr; 
short port = 8888; 
tcpaddr.sin_family = AF_INET;// Socket IPv4 
tcpaddr.sin_port = htons(port); // host order => net order 
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY); //Giao diện bất kỳ 
 bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr)); // Bind socket 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm listen: chuyến SOCKET sang trạng thái đợi kết nối 
3.3 Lập trình WinSock 
77 
int listen(SOCKET s, int backlog); 
Trong đó 
s: [IN] SOCKET đã được tạo trước đó bằng socket/WSASocket 
backlog: [IN] chiều dài hàng đợi chấp nhận kết nối 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm accept: chấp nhận kết nối 
3.3 Lập trình WinSock 
78 
SOCKET accept(SOCKET s, struct sockaddr FAR* addr,int FAR* 
addrlen); 
Trong đó 
s: [IN] SOCKET hợp lệ, đã được bind và listen trước đó 
addr: [OUT] địa chỉ của client kết nối đến 
addrlen: [IN/OUT] con trỏ tới chiều dài của cấu trúc addr. Ứng dụng 
cần khởi tạo addrlen trỏ tới một số nguyên chứa chiều dài của addr 
Giá trị trả về là một SOCKET mới, sẵn sàng cho việc gửi nhận dữ liệu trên 
đó. Ứng với mỗi kết nối của client sẽ có một SOCKET riêng. 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm send: gửi dữ liệu trên SOCKET 
3.3 Lập trình WinSock 
79 
int send(SOCKET s, const char FAR * buf, int len, int flags); 
Trong đó 
s: [IN] SOCKET hợp lệ, đã được accept trước đó 
buf: [IN] địa chỉ của bộ đệm chứa dữ liệu cần gửi 
len: [IN] số byte cần gửi 
flags:[IN] cờ quy định cách thức gửi, có thể là 
0,MSG_OOB,MSG_DONTROUTE 
Giá trị trả về 
Thành công: số byte gửi được, có thể nhỏ hơn len 
Thất bại: SOCKET_ERROR 
Thí dụ 
char szHello[]=”Hello Network Programming”; 
send(s,szHello,strlen(szHello),0); 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm recv: nhận dữ liệu trên SOCKET 
3.3 Lập trình WinSock 
80 
int recv(SOCKET s, const char FAR * buf, int len, int flags); 
Trong đó 
s: [IN] SOCKET hợp lệ, đã được accept trước đó 
buf: [OUT] địa chỉ của bộ đệm nhận dữ liệu 
len: [IN] kích thước bộ đệm 
flags:[IN] cờ quy định cách thức nhận, có thể là 0, MSG_PEEK, 
MSG_OOB, MSG_WAITALL 
Giá trị trả về 
Thành công: số byte nhận được, có thể nhỏ hơn len 
Thất bại: SOCKET_ERROR 
Thí dụ 
char buf[100]; 
int len = 0; 
len = recv(s,buf,100,0); 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía server (tiếp) 
• Hàm closesocket: đóng kết nối trên một socket 
3.3 Lập trình WinSock 
81 
int closesocket(SOCKET s ) 
Trong đó 
s: [IN] SOCKET hợp lệ, đã kết nối 
Giá trị trả về 
Thành công: 0 
Thất bại: SOCKET_ERROR 
• Truyền dữ liệu sử dụng TCP 
– Đoạn chương trình minh họa 
3.3 Lập trình WinSock 
82 
#include //Thu vien Winsock 
void main(void) 
{ 
 WSADATA wsaData; 
 SOCKET ListeningSocket; 
 SOCKET NewConnection; 
 SOCKADDR_IN ServerAddr; 
 SOCKADDR_IN ClientAddr; 
 int ClientAddrLen; 
 int Port = 8888; 
 // Khoi tao Winsock 2.2 
 WSAStartup(MAKEWORD(2,2), &wsaData); 
 // Tao socket lang nghe ket noi tu client. 
 ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 // Khoi tao cau truc SOCKADDR_IN cua server 
 // doi ket noi o cong 8888 
 ServerAddr.sin_family = AF_INET; 
 ServerAddr.sin_port = htons(Port); 
 ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
• Truyền dữ liệu sử dụng TCP 
– Đoạn chương trình minh họa (tiếp) 
3.3 Lập trình WinSock 
83 
 // Bind socket cua server. 
 bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)); 
 // Chuyen sang trang thai doi ket noi 
 listen(ListeningSocket, 5); 
 // Chap nhan ket noi moi. 
 ClientAddrLen = sizeof(ClientAddr); 
 NewConnection = accept(ListeningSocket, (SOCKADDR *) 
 &ClientAddr,&ClientAddrLen); 
 // Sau khi chap nhan ket noi, server co the tiep tuc chap nhan them cac ket noi khac, 
 // hoac gui nhan du lieu voi cac client thong qua cac socket duoc accept voi client 
 // Dong socket 
 closesocket(NewConnection); 
 closesocket(ListeningSocket); 
 // Giai phong Winsock 
 WSACleanup(); 
} 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía client 
• Khởi tạo WinSock qua hàm WSAStartup 
• Tạo SOCKET qua hàm socket hoặc WSASocket 
• Điền thông tin về server vào cấu trúc sockaddr_in 
• Kết nối tới server qua hàm connect hoặc WSAConnect 
• Gửi dữ liệu tới server thông qua hàm send hoặc WSASend 
• Nhận dữ liệu từ server thông qua hàm recv hoặc WSARecv 
• Đóng SOCKET khi việc truyền nhận kết thúc bằng hàm 
closesocket 
• Giải phóng WinSock bằng hàm WSACleanup 
3.3 Lập trình WinSock 
84 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía client (tiếp) 
3.3 Lập trình WinSock 
85 
WSAStartup 
socket/ 
WSASocket 
x|c định địa 
chỉ/ph}n giải 
tên miền 
connect/WSA
Connect 
send/ 
WSASend 
recv/ 
WSARecv 
closesocket WSACleanup 
• Truyền dữ liệu sử dụng TCP 
– Ứng dụng phía client (tiếp) 
• Địa chỉ của server xác định trong cấu trúc sockaddr_in nhờ 
hàm inet_addr hoặc theo getaddrinfo 
• Hàm connect: kết nối đến server 
3.3 Lập trình WinSock 
86 
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen); 
Trong đó 
s: [IN] SOCKET đã được tạo bằng socket hoặc WSASocket trước đó 
 ... Kiểm tra xem s có được thiết lập hay không 
 if (FD_ISSET(s, &fdread)) { 
 // Đọc dữ liệu từ s 
 } 
 } 
} 
3.4 C|c phương ph|p v{o ra 
114 
• Các mô hình vào ra của WinSock 
• Mô hình WSAAsyncSelect 
 Cơ chế xử lý sự kiện dựa trên thông điệp của Windows 
 Ứng dụng GUI có thể nhận được các thông điệp từ WinSock qua cửa sổ của ứng 
dụng. 
 Hàm WSAAsyncSelect được sử dụng để chuyển socket sang chế độ bất đồng bộ 
và thiết lập tham số cho việc xử lý sự kiện 
int WSAAsyncSelect( 
 SOCKET s, // [IN] Socket sẽ xử lý sự kiện 
 HWND hWnd, // [IN] Handle cửa sổ nhận sự kiện 
 unsigned int wMsg, // [IN] M~ thông điệp, tùy chọn, thường>=WM_USER 
 long lEvent // [IN] Mặt nạ chứa c|c sự kiện ứng dụng muốn nhận 
 // bao gồm FD_READ, 
 //FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE 
); 
3.4 C|c phương ph|p v{o ra 
115 
• Các mô hình vào ra của WinSock 
• Mô hình WSAAsyncSelect 
 Thí dụ: WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | 
FD_WRITE | FD_CLOSE); 
 Tất cả các cửa sổ đều có hàm callback để nhận sự kiện từ Windows. Khi ứng dụng 
đã đăng ký socket với cửa sổ nào, thì cửa sổ đó sẽ nhận được các sự kiện của 
socket. 
 Nguyên mẫu của hàm callback của cửa số: 
 LRESULT CALLBACK WindowProc( 
 HWND hWnd, 
 UINT uMsg, 
 WPARAM wParam, 
 LPARAM lParam ); 
 Khi cửa sổ nhận được các sự kiện liên quan đến WinSock: 
 uMsg sẽ chứa mã thông điệp mà ứng dụng đã đăng ký bằng WSAAsyncSelect 
 wParam chứa bản thân socket xảy ra sự kiện 
 Nửa cao của lParam chứa mã lỗi nếu có, nửa thấp chứa mã sự kiện có thể là 
FD_READ, FD_WRITE, FD_CONNECT, FD_ACCEPT, FD_CLOSE 
3.4 C|c phương ph|p v{o ra 
116 
• Các mô hình vào ra của WinSock 
• Mô hình WSAAsyncSelect 
 Ứng dụng sẽ dùng hai MACRO: WSAGETSELECTERROR và 
WSAGETSELECTEVENT để kiểm tra lỗi và sự kiện xảy ra trên socket. 
 Thí dụ: 
 BOOL CALLBACK WinProc(HWND hDlg,UINT wMsg, 
 WPARAM wParam, LPARAM lParam) 
{ 
 SOCKET Accept; 
 switch(wMsg) 
 { 
 case WM_PAINT: // Xử lý sự kiện kh|c 
 break; 
 case WM_SOCKET: // Sự kiện WinSock 
 if (WSAGETSELECTERROR(lParam)) // Kiểm tra có lỗi hay không 
 { 
 closesocket( (SOCKET) wParam); // Đóng socket 
 break; 
 } 
3.4 C|c phương ph|p v{o ra 
117 
• Các mô hình vào ra của WinSock 
• Mô hình WSAAsyncSelect 
 Thí dụ (tiếp): 
 switch(WSAGETSELECTEVENT(lParam)) // X|c định sự kiện 
 { 
 case FD_ACCEPT: // Chấp nhận kết nối 
 Accept = accept(wParam, NULL, NULL); 
 . 
 break; 
 case FD_READ: // Có dữ liệu từ socket wParam 
 break; 
 case FD_WRITE: // Có thể gửi dữ liệu đến socket wParam 
 break; 
 case FD_CLOSE: // Đóng kết nối 
 closesocket( (SOCKET)wParam); 
 break; 
 } 
 break; 
 } 
 return TRUE; 
} 
3.4 C|c phương ph|p v{o ra 
118 
• Các mô hình vào ra của WinSock 
• Mô hình WSAAsyncSelect 
 Ưu điểm: xử lý hiệu quả nhiều sự kiện trong cùng một luồng. 
 Nhược điểm: ứng dụng phải có ít nhất một cửa sổ, không nên dồn quá nhiều 
socket vào cùng một cửa sổ vì sẽ dẫn tới đình trệ trong việc xử lý giao diện. 
3.4 C|c phương ph|p v{o ra 
119 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Xử lý dựa trên cơ chế đồng bộ đối tượng sự kiện của Windows: WSAEVENT 
 Mỗi đối tượng có hai trạng thái: Báo hiệu (signaled) và chưa báo hiệu (non-
signaled). 
 Hàm WSACreateEvent sẽ tạo một đối tượng sự kiện ở trạng thái chưa báo hiệu và 
có chế độ hoạt động là thiết lập thủ công (manual reset). 
 WSAEVENT WSACreateEvent(void); 
 Hàm WSAResetEvent sẽ chuyển đối tượng sự kiện về trạng thái chưa báo hiệu 
 BOOL WSAResetEvent(WSAEVENT hEvent); 
 Hàm WSACloseEvent sẽ giải phóng một đối tượng sự kiện 
 BOOL WSACloseEvent(WSAEVENT hEvent); 
3.4 C|c phương ph|p v{o ra 
120 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Hàm WSAEventSelect sẽ tự động chuyển socket sang chế độ non-blocking và gắn 
các sự kiện của socket với đối tượng sự kiện truyền vào theo tham số 
 int WSAEventSelect( 
 SOCKET s, // [IN] Socket cần xử lý sự kiện 
 WSAEVENT hEventObject,// [IN] Đối tượng sự kiện đ~ tạo trước đó 
 long lNetworkEvents // [IN] C|c sự kiện ứng dụng muốn nhận 
 // từ WinSock 
 ); 
 Thí dụ: rc = WSAEventSelect(s, hEventObject, FD_READ|FD_WRITE); 
3.4 C|c phương ph|p v{o ra 
121 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Hàm WaitForMultipleEvent sẽ đợi sự kiện trên một mảng các đối tượng sự kiện 
cho đến khi một trong các đối tượng chuyển sang trạng thái báo hiệu. 
 DWORD WSAWaitForMultipleEvents( 
 DWORD cEvents, // [IN] Số lượng sự kiện cần đợi 
 const WSAEVENT FAR * lphEvents,// [IN] Mảng c|c sự kiện 
 BOOL fWaitAll, //[IN] Có đợi tất cả c|c sự kiện không ? 
 DWORD dwTimeout, //[IN] Thời gian đợi tối đa 
 BOOL fAlertable //[IN] Thiết lập l{ FALSE 
 ); 
 Giá trị trả về 
 Thành công: Số thứ tự của sự kiện xảy ra + WSA_WAIT_EVENT_0. 
 Hết giờ: WSA_WAIT_TIMEOUT. 
 Thất bại: WSA_WAIT_FAILED. 
3.4 C|c phương ph|p v{o ra 
122 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Xác định mã của sự kiện gắn với một đối tượng sự kiện cụ thể bằng hàm 
WSAEnumNetworkEvents. 
 int WSAEnumNetworkEvents( 
 SOCKET s, // [IN] Socket muốn thăm dò 
 WSAEVENT hEventObject, // [IN] Đối tượng sự kiện tương ứng 
 LPWSANETWORKEVENTS lpNetworkEvents// [OUT] Cấu trúc chứa m~ sự kiện 
 ); 
 Mã sự kiện lại nằm trong cấu trúc WSANETWORKEVENTS có khai báo như sau 
 typedef struct _WSANETWORKEVENTS 
 { 
 long lNetworkEvents; // Số lượng sự kiện 
 int iErrorCode[FD_MAX_EVENTS]; // Mảng c|c m~ sự kiện 
 } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS; 
3.4 C|c phương ph|p v{o ra 
123 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Thí dụ 
#include 
#define MAX_EVENTS 64 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
SOCKET SocketArray [MAX_EVENTS]; 
WSAEVENT EventArray [MAX_EVENTS],NewEvent; 
SOCKADDR_IN InternetAddr; 
SOCKET Accept, Listen; 
DWORD EventTotal = 0; 
DWORD Index, i; 
WSADATA wsaData; 
WORD wVersion = MAKEWORD(2,2); 
int rc = WSAStartup(wVersion,&wsaData); 
// Thiết lập TCP socket đợi kết nối ở 8888 
Listen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); 
InternetAddr.sin_family = AF_INET; 
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
InternetAddr.sin_port = htons(8888); 
rc = bind(Listen, (PSOCKADDR) &InternetAddr,sizeof(InternetAddr)); 
3.4 C|c phương ph|p v{o ra 
124 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Thí dụ (tiếp) 
NewEvent = WSACreateEvent(); 
 WSAEventSelect(Listen, NewEvent,FD_ACCEPT | FD_CLOSE); 
rc = listen(Listen, 5); 
WSANETWORKEVENTS NetworkEvents; 
SocketArray[EventTotal] = Listen; 
EventArray[EventTotal] = NewEvent; 
EventTotal++; 
char buffer[1024]; 
int len; 
while(TRUE) 
{ 
 // Đợi tất cả c|c sự kiện 
 Index = WSAWaitForMultipleEvents(EventTotal,EventArray, FALSE, 
 WSA_INFINITE, FALSE); 
 Index = Index - WSA_WAIT_EVENT_0; 
3.4 C|c phương ph|p v{o ra 
125 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Thí dụ (tiếp) 
// Duyệt để tìm ra sự kiện n{o được b|o hiệu 
 for(i=Index; i < EventTotal ;i++) 
 { 
 Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, 
 FALSE); 
 if ((Index == WSA_WAIT_FAILED) || (Index == WSA_WAIT_TIMEOUT)) 
 continue; 
 else 
 { 
 Index = i; 
 WSAResetEvent(EventArray[Index]); 
 WSAEnumNetworkEvents( 
 SocketArray[Index], 
 EventArray[Index], 
 &NetworkEvents); 
3.4 C|c phương ph|p v{o ra 
126 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Thí dụ (tiếp) 
 // Kiểm tra sự kiện FD_ACCEPT 
 if (NetworkEvents.lNetworkEvents & FD_ACCEPT) 
 { 
 if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) 
 { 
 printf("FD_ACCEPT failed with error %d\n", 
 NetworkEvents.iErrorCode[FD_ACCEPT_BIT]); 
 break; 
 } 
 // Chấp nhận kết nối mới 
 // cho v{o danh s|ch socket v{ sự kiện 
 Accept = accept( 
 SocketArray[Index], 
 NULL, NULL); 
3.4 C|c phương ph|p v{o ra 
127 
• Các mô hình vào ra của WinSock 
• Mô hình WSAEventSelect 
 Thí dụ (tiếp) 
if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS) 
 { 
 printf("Too many connections"); 
 closesocket(Accept); 
 break; 
 } 
 NewEvent = WSACreateEvent(); 
 WSAEventSelect(Accept, NewEvent, 
 FD_READ | FD_WRITE | FD_CLOSE); 
 EventArray[EventTotal] = NewEvent; 
 SocketArray[EventTotal] = Accept; 
 EventTotal++; 
 printf("Socket %d connected\n", Accept); 
 } 
... 
• Viết chương trình chat đơn giản (client +server) 
sử dụng mô hình WSAAsyncSelect. Có thể nhập và 
hiển thị tiếng Việt. 
• Viết chương trình chat đơn giản sử dụng mô hình 
WSAEventSelect. Có thể nhập và(client+server) 
hiển thị tiếng Việt. 
Nội dung lưu trong xâu có kiểu wchar_t. Số lượng 
byte gửi đi = chiều dài xâu * 2. 
Bài tập 
128 
3.4 C|c phương ph|p v{o ra 
129 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped 
 Sử dụng cấu trúc OVERLAPPED chứa thông tin về thao tác vào ra. 
 Các thao tác vào ra sẽ trở về ngay lập tức và thông báo lại cho ứng dụng theo một 
trong hai cách sau: 
 Event được chỉ ra trong cấu trúc OVERLAPPED. 
 Completion routine được chỉ ra trong tham số của lời gọi vào ra. 
 Các hàm vào ra sử dụng mô hình này: 
 WSASend 
 WSASendTo 
 WSARecv 
 WSARecvFrom 
 WSAIoctl 
 WSARecvMsg 
 AcceptEx 
 ConnectEx 
 TransmitFile 
 TransmitPackets 
 DisconnectEx 
 WSANSPIoctl 
3.4 C|c phương ph|p v{o ra 
130 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped– Xử lý qua event 
 Cấu trúc OVERLAPPED 
typedef struct WSAOVERLAPPED 
{ 
 DWORD Internal; 
 DWORD InternalHigh; 
 DWORD Offset; 
 DWORD OffsetHigh; 
 WSAEVENT hEvent; 
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED 
Internal, InternalHigh,Offset,OffsetHigh được sử dụng nội bộ trong WinSock 
hEvent là đối tượng event sẽ được báo hiệu khi thao tác vào ra hoàn tất, chương trình 
cần khởi tạo cấu trúc với một đối tượng sự kiện hợp lệ. 
Khi thao tác vào ra hoàn tất, chương trình cần lấy kết quả vào ra thông qua hàm 
WSAGetOverlappedResult 
3.4 C|c phương ph|p v{o ra 
131 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped– Xử lý qua event 
‒ Hàm WSAGetOverlappedResult 
BOOL WSAGetOverlappedResult( 
 SOCKET s, 
 LPWSAOVERLAPPED lpOverlapped, 
 LPDWORD lpcbTransfer, 
 BOOL fWait, 
 LPDWORD lpdwFlags 
); 
s là socket muốn kiểm tra kết quả 
lpOverlapped là con trỏ đến cấu trúc OVERLAPPED 
lpcbTransfer là con trỏ đến biến sẽ lưu số byte trao đổi được 
fWait là biến báo cho hàm đợi cho đến khi thao tác vào ra hoàn tất 
lpdwFlags : cờ kết quả của thao tác 
Hàm trả về TRUE nếu thao tác hoàn tất hoặc FALSE nếu thao tác chưa hoàn tất, có lỗi 
hoặc không thể xác định. 
3.4 C|c phương ph|p v{o ra 
132 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Xử lý qua event 
– Tạo đối tượng event với WSACreateEvent. 
– Khởi tạo cấu trúc OVERLAPPED với event vừa tạo. 
– Gửi yêu cầu vào ra với tham số là cấu trúc OVERLAPPED vừa tạo, tham số 
liên quan đến CompletionRoutine phải luôn bằng NULL. 
– Đợi thao tác kết thúc qua hàm WSAWaitForMultipleEvents. 
– Nhận kết quả vào ra qua hàm WSAGetOverlappedResult 
3.4 C|c phương ph|p v{o ra 
133 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Thí dụ xử lý qua event 
 // Khởi tạo WinSock v{ kết nối đến 127.0.0.1:8888 
 OVERLAPPED overlapped; // Khai b|o cấu trúc OVERLAPPED 
 WSAEVENT receiveEvent = WSACreateEvent(); // Tạo event 
 memset(&overlapped,0,sizeof(overlapped)); 
 overlapped.hEvent = receiveEvent; 
 char buff[1024]; // Bộ đệm nhận dữ liệu 
 WSABUF databuff; // Cấu trúc mô tả bộ đệm 
 databuff.buf = buff; 
 databuff.len = 1024; 
 DWORD bytesReceived = 0; // Số byte nhận được 
 DWORD flags = 0; / Cờ quy định c|ch nhận, bắt buộc phải có 
 while (1) 
 { 
 DWORD flags = 0; 
 // Gửi yêu cầu nhận dữ liệu 
 rc = WSARecv(s,&databuff,1,&bytesReceived,&flags,&overlapped,0); 
3.4 C|c phương ph|p v{o ra 
134 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Thí dụ xử lý qua event 
 if (rc == SOCKET_ERROR) 
 { 
 rc = WSAGetLastError(); 
 if (rc != WSA_IO_PENDING) 
 { 
 printf("Loi %d !\n",rc); 
 continue; 
 } 
 }; 
 rc = WSAWaitForMultipleEvents(1,&receiveEvent,TRUE,WSA_INFINITE,FALSE); 
 if ((rc == WSA_WAIT_FAILED)||(rc==WSA_WAIT_TIMEOUT)) continue; 
 WSAResetEvent(receiveEvent); 
 rc = WSAGetOverlappedResult(s,&overlapped,&bytesReceived,FALSE,&flags); 
 // Kiểm tra lỗi 
 // Hiển thị 
 buff[bytesReceived] = 0; 
 printf(buff); 
 } 
3.4 C|c phương ph|p v{o ra 
135 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Xử lý Completion Routine 
– Hệ thống sẽ thông báo cho ứng dụng biết thao tác vào ra kết thúc thông qua một 
hàm callback gọi là Completion Routine 
– Nguyên mẫu của hàm như sau 
 void CALLBACK CompletionROUTINE( 
 IN DWORD dwError, // M~ lỗi 
 IN DWORD cbTransferred, // Số byte trao đổi 
 IN LPWSAOVERLAPPED lpOverlapped, // Cấu trúc lpOverlapped 
 // tương ứng 
 IN DWORD dwFlags ); // Cờ kết quả thao t|c v{o ra 
– WinSock sẽ bỏ qua trường event trong cấu trúc OVERLAPPED, việc tạo đối tượng 
event và thăm dò là không cần thiết nữa. 
3.4 C|c phương ph|p v{o ra 
136 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Xử lý Completion Routine 
– Ứng dụng cần chuyển luồng sang trạng thái alertable ngay sau khi gửi yêu cầu vào 
ra. 
– Các hàm có thể chuyển luồng sang trạng thái alertable: 
WSAWaitForMultipleEvents, SleepEx 
– Nếu ứng dụng không có đối tượng event nào thì có thể sử dụng SleepEx 
 DWORD SleepEx(DWORD dwMilliseconds, // Thời gian đợi 
 BOOL bAlertable // Trạng th|i alertable 
 ); 
3.4 C|c phương ph|p v{o ra 
137 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Thí dụ Completion Routine 
// Khai b|o c|c cấu trúc cần thiết 
SOCKET s; 
OVERLAPPED overlapped; 
char buff[1024]; 
WSABUF databuff; 
DWORD flags; 
DWORD bytesReceived = 0; 
Int rc = 0; 
void CALLBACK CompletionRoutine( IN DWORD dwError, 
 IN DWORD cbTransferred, 
 IN LPWSAOVERLAPPED lpOverlapped, 
 IN DWORD dwFlags) 
{ 
 if (dwError != 0||cbTransferred==0) // Xử lý lỗi 
 { 
 closesocket(s); 
 return; 
 }; 
3.4 C|c phương ph|p v{o ra 
138 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Thí dụ Completion Routine 
 // Hiển thị x}u ra m{n hình 
 buff[cbTransferred]=0; 
 printf(buff); 
 // Khởi tạo lại cấu trúc overlapped v{ lại gửi tiếp yêu cầu nhận dữ liệu 
 memset(&overlapped,0,sizeof(overlapped)); 
 flags = 0; 
 rc = WSARecv(s, &databuff, 1, &bytesReceived, &flags, &overlapped, 
 CompletionRoutine); 
 if (rc == SOCKET_ERROR) 
 { 
 rc = WSAGetLastError(); 
 if (rc != WSA_IO_PENDING) 
 printf("Loi %d !\n",rc); 
 }; 
 return; 
} 
3.4 C|c phương ph|p v{o ra 
139 
• Các mô hình vào ra của WinSock 
• Mô hình Overlapped – Thí dụ Completion Routine 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
 // Khởi tạo v{ kết nối đến 127.0.0.1:8888 
 // Khởi tạo cấu trúc overlapped 
 memset(&overlapped,0,sizeof(overlapped)); 
 // Khởi tạo bộ đệm dữ liệu 
 databuff.buf = buff; 
 databuff.len = 1024; 
 // Gửi yêu cầu v{o ra 
 rc = WSARecv(s, &databuff,1,&bytesReceived,&flags,&overlapped, 
 CompletionRoutine); 
 // Xử lý lỗi 
 // Chuyển luồng sang trạng th|i alertable 
 while (1) SleepEx(1000,TRUE); 
 getch(); 
 closesocket(s); 
 WSACleanup(); 
 return 0; 
} 

File đính kèm:

  • pdfbai_giang_lap_trinh_mang_chuong_3_windows_socket_luong_anh_h.pdf