Professional Documents
Culture Documents
Để lập trình được Winsock chúng ta sẽ khai báo thư viện winsock2.h (chứa các prototypes) và 1 file lib
(chính là file .cpp đã được biên dịch thành .lib) có tên là ws2_2.lib.
#include <stdio.h>
#include <tchar.h>
...
#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib")
Trong đó:
- wVersionRequested là phiên bản thư viện mà mình sử dụng. Ở đây sẽ là giá trị 0x0202 có nghĩa là
phiên bản 2.2. Chúng ta có thể dùng macro MAKEWORD(2,2) để trả về giá trị 0x0202.
- lpWSData là một số thông tin bổ sung sẽ được trả về sau khi gọi khởi tạo Winsock.:
nt WSACleanup (void);
#include "stdafx.h"
WSACleanup();
return 0;
}
2. SOCKET
1. Socket là gì?
“Socket là một cổng logic mà một chương trình sử dụng để kết nối với một chương trình khác chạy trên
một máy tính khác trên Internet. Chương trình mạng có thể sử dụng nhiều Socket cùng một lúc, nhờ đó
nhiều chương trình có thể sử dụng Internet cùng một lúc.”
Ở đây ta hiểu Socket trong Winsock như là một “phương tiện” để ứng dụng mạng có thể trao đổi dữ
liệu. Nghĩa là 1 Server thì sẽ cần một Socket để lắng nghe, chờ đợi các kết nối từ client và Client
thì phải cần có một Socket để kết nối tới Sever.
SOCKET s = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
Trong đó:
* af: Là một con số ID để quyết định Socket của chúng ta sử dụng giao thức (protocol) để kết nối.
- AF_INET : TCP/IP (Phổ biến nhất hiện nay -> dùng địa chỉ IP để truyền dữ liệu)
- AF_NETBIOS: NetBIOS (Giao thức dùng tên máy để truyền dữ liệu)
- AF_APPLETALK: AppleTalk
- AF_ATM: ATM
…
Và ở trong Tut này mình chỉ nghiên cứu tới TCP/IP.
Vậy dùng UDP khi nào? Những ứng dụng cần dữ liệu tức thời như:
- Chương trình nghe nhạc trực tuyến. Vấn đề sai bit (vấp khi nghe nhạc) không quan trọng mấy vì yêu
cầu của nó là đảm bảo tốc độ nhanh.
- Chương trình Chat chẳn hạn.
- Hoặc GameOnline (thỉnh thoảng bạn bị trường hợp LAG chính là do bị mất dữ liệu trên đường truyền
đó)
- SOCK_STREAM: Đây là giao thức TCP. Nó ngược với UDP vì nó đảm bảo giữa bên gửi và bên
nhận dữ liệu phải chính xác. Vì vậy 2 bên sẽ phải bắt tay rất nhiều lần khi truyền được dữ liệu (ví dụ
như bên gửi sẽ gửi n gói tin (packet), bên nhận sẽ kiểm tra có bị mất hay sai gói tin nào hay không, nếu
đủ thì nó sẽ yêu cầu bên gửi gửi tiếp n gói tin tiếp theo, ngược lại thì nó sẽ yêu cầu gửi lại)
=> Ưu điểm: Chất lượng gởi tin cậy.
=> Nhược điểm: Chậm hơn UDP.
Những ứng dụng như WEB, MAIL, FTP,…
- SOCK_RAW:
Là giao thức để kiểm soát mạng, kiểm tra kết nối…
Ví dụ:
Start -> Run -> CMD: “ping diendantinhoc.com”.
Nếu bạn nhận được Reply có nghĩa là giữa máy tính của bạn với máy chủ “diendantinhoc.com” có
“thông mạng” với nhau. Và gói tin mà bạn PING chính là SOCK_RAW (ICMP Packet)
* protocol: Chỉ định rõ lại giao thức mà thôi. Vì SOCK_RAW có 2 protocol là ICMP và RAW nên nó
cần điều này
- SOCK_DGREAM -> protocol là: IPPROTO_UDP
- SOCK_STREAM -> protocol là: IPPROTO_IP
- SOCK_RAW -> protocol có thể là: IPPROTO_RAW hay IPPROTO_ICMP
Các bạn có thể tham khảo thêm bảng thể hiện các thuộc tính của hàm SOCKET:
3. Một số hàm lấy thông tin về mạng
int WSAEnumProtocols (
LPINT lpiProtocols,
LPWSAPROTOCOL_INFO lpProtocolBuffer,
LPDWORD lpdwBufferLength
);
lpiProtocols: NULL
lpProtocolBuffer: Kiểu dữ liệu trả về
lpdwBufferLength: Kích thước của kiểu dữ liệu
Tuy nhiên việc sử dụng hàm này còn hơi rườm rà.
Ví dụ:
Ví dụ:
char lpMyPCName[10];
gethostbyname(lpMyPCName,10)
cout<< lpMyPCName;
Sử dụng inet_addr và inet_ntoa để chuyển đổi qua lại giữa u_long và char*
Trong đó
Ví dụ như:
char lpHostName[100];
hostent *MyPC;
gethostname(lpHostName,100);
MyPC = gethostbyname(lpHostName);
hostent *Yahoo;
u_long YahooAddr = inet_addr("216.109.112.135");
Yahoo = gethostbyaddr((char*)&YahooAddr,4,AF_INET);
Chương trình mẫu khởi tạo Socket:
Thread trước mình đã giới thiệu về SOCKET tuy nhiên mình chỉ nói khởi tạo, còn việc để gửi hay nhận
gói tin cần phải thêm thông tin về địa chỉ IP cho nó nữa.
Ở Thread này mình sẽ tạm gác lại về SOCKET và C/C++ để đi sâu vào kiến thức cơ bản mạng cụ thể
là địa chỉ IP. Bởi vì bạn muốn lập trình được chương trình truyền dữ liệu trên protocol TCP/IP mà
không biết về IP thì hơi vô lý. Do đó cái thread này có phần đi ngoài lề về lập trình 1 xíu.
1. Địa chỉ IP
Một HOST muốn giao dịch trên mạng sử dụng giao thức TCP/IP thì cần phải có 1 địa IP duy nhất để
xác địch chính nó (như địa chỉ nhà bạn) trên môi trường mạng.
Hiện nay có 2 loại địa chỉ IP. Đó là IPv4 (4 bytes) và IPv6. Tuy nhiên IPv6 vẫn chưa thể thay thế cho
IPv4 được bởi vì số thiết bị dùng IPv4 quá nhiều.
a. Lớp A
- Số byte làm Network_id: 1 byte (địa chỉ đầu tiên từ 0 – 127)
- Số byte là Host_id: 3 bytes còn lại (trong 1 Network không được trùng)
- Subnet Mask: 255.0.0.0
- Địa chỉ BroadCast (đại diện cho tất cả các máy trong đường mạng -> Khi gởi tới địa chỉ này thì mọi
host trong mạng sẽ nhận). x x x.255.255.255.
- Địa chỉ Network (đai diện bất kỳ một máy trong mạng – thường dùng để kiểm soát, định tuyến,…) x
x x.0.0.0
Ví dụ: 10.1.1.1
* Trong lớp A có đường mạng 127.0.0.0 là đường mạng không sử dụng, nó được sử dụng để kiểm tra
giao thúc TCP/IP. Ví dụ bạn ping 127.0.0.1 thì luôn nhận reply vì đây là địa chỉ máy tính của bạn cho
dù IP của bạn ở lớp nào đi chăng nữa địa chỉ này chỉ sử dụng tại máy tính của bạn mà thôi.
b. Lớp B
- Số byte làm Network_id: 2 byte (địa chỉ đầu tiên từ 128 – 191)
- Số byte là Host_id: 2 bytes còn lại (trong 1 Network không được trùng)
- Subnet Mask: 255.255.0.0
- Địa chỉ BroadCast (đại diện cho tất cả các máy trong đường mạng -> Khi gởi tới địa chỉ này thì mọi
host trong mạng sẽ nhận). x x x.x x x.255.255.
- Địa chỉ Network (đai diện bất kỳ một máy trong mạng – thường dùng để kiểm soát, định tuyến,…) x
x x.x x x.0.0
Ví dụ: 172.11.22.3
c. Lớp C
- Số byte làm Network_id: 3 byte (địa chỉ đầu tiên từ 192 – 223)
- Số byte là Host_id: 1 bytes còn lại (trong 1 Network không được trùng)
- Subnet Mask: 255.255.255.0
- Địa chỉ BroadCast (đại diện cho tất cả các máy trong đường mạng -> Khi gởi tới địa chỉ này thì mọi
host trong mạng sẽ nhận). x x x.x x x.x x x.255.
- Địa chỉ Network (đai diện bất kỳ một máy trong mạng – thường dùng để kiểm soát, định tuyến,…) x
x x.x x x.x x x.0
Ví dụ: 192.168.23.3
Vấn đề về Subnet Mask bạn cứ chấp nhận như vậy vì mình cũng không muốn đi sâu vào chuyên
môn của mạng, đơn giản là vì đây là BOX C/C++.
Ví dụ mình sẽ có thêm 2 HOST với 2 địa chỉ IP cùng lớp C nữa là: 192.168.10.3 và 192.168.11.2
Như vậy:
- Nếu mình ở tại máy tính có địa chỉ 192.168.10.4 (start -> run -> “cmd”)
+ ping 192.168.10.3 thì mình sẽ nhận được reply tức là 2 địa chỉ này “thông mạng” vì cùng Network là:
192.168.10.0
+ ping 192.168.11.2 thì kết quả sẽ là “request time out”. (Không liên lạc được) bởi vì 2 host này ở 2
mạng khác nhau (192.168.10.0 và 192.168.11.0).
Như vậy:
- Network_id sẽ quy định host trong đó thuộc “đường mạng” nào. Và các host chỉ thấy nhau khi
cùng “đường mạng”
- Host_id: dùng để phân biệt host với tất cả các host khác trong mạng
Tiếp tục… Nếu để 2 đường mạng như trên 192.168.10.0 và 192.168.11.0 có thể “liên lạc” với nhau
thì sao? Người ta cần định tuyến mạng với thiết bị mạng có tên là Router (Modem ADSL cũng là 1
router) hoặc một máy chủ Sever có dịch vụ RAS (routing), hoặc một Proxy….
=> Router là một thiết bị có 2 card mạng và sẽ có ít nhất 2 địa chỉ IP. Trong hình trên thì Card mạng
thứ 2 của hai router phải thiết lập sao cho có cùng đường mạng để “liên lạc với nhau”. Ví dụ như
là 192.168.12.0. Như vậy LAN 1 sẽ liên lạc được với LAN 2 thông qua 2 router trên hình.
Ví dụ ở lớp A:
- Network_id sẽ là 1 bytes (8 bits)
-> Nó sẽ có 2^7 = 127 (đường mạng)
+ Tại sao là 8 bit nhưng lại là 2^7 ? -> Là vì số bit từ làm Subnet là 0 -> 7 (biểu diễn tới 2^7)
- Host_id là (24 bits):
-> 2^24 – 2 = 16.777.214 (Hosts) (trừ 2 địa chỉ Broadcast và Subnet)
=> Ở lớp A số đường mạng ít nhưng số host trên đường mạng nhiều -> thích hợp với các công ty
lớn
Ví dụ ở lớp B:
- Network_id sẽ là 2 bytes (16 bits)
-> Nó sẽ có 2^15 = 16.384 (đường mạng)
- Host_id là (16 bits):
-> 2^16 – 2 = 65.534 (hosts)
=> Số đường mạng lớp B và số host vừa nhau
Ví dụ ở lớp C:
- Network_id sẽ là 3 bytes (24 bits)
-> Nó sẽ có 2^23 = 2.097152 (đường mạng)
- Host_id là (8 bits):
-> 2^8 – 2 = 254 (Hosts)
=> Số đường mạng lớp C rất nhiều những host mỗi đường mạng lại rất ít. Và có lẽ lớp C là lớp
được sử dụng nhiều trong mạng LAN bởi lẽ với con số PC dưới 100 trên phòng máy thì lớp C là
lựa chọn hay nhất.
Hy vọng tới đây các bạn sẽ hình dung 1 chút ít gì đó về địa chỉ IP.
4. IP LAN và IP WAN
- Default Gateway: Cổng ra của gói tin mà ta muốn gửi đi nếu địa chỉ đến của gói tin đó khác
đường mạng. Và đây thường là địa chỉ của những thiết bị làm nhiệm vụ định tuyến
(Routing) như ADSL Modem
Địa chỉ IP 192.168.1.1 (IP LAN) chính là địa chỉ của cái ADSL Moderm. Rõ ràng nó phải cùng
đường mạng với máy tính của mình (đường mạng: 192.168.1.0)
- DNS Servers: Là dịch vụ phân giải tên miền. Khi ta truy cập internet thì nó sẽ chuyển đổi
giữa tên miền -> IP:
DNS SERVER như là một “cơ sở dữ liệu khổng lồ” mà mỗi record sẽ là một tên miền và 1 IP.
Do được phân cấp theo dạng cây “vn -> com -> google” nên khả năng tìm kiếm của DNS rất
nhanh.
DNS của mạng FTP là 203.113.131.1 (IP WAN) và 1 cái dự phòng là 203.113.131.2. Dịch vụ thì
VNN (203.162.4.190, 203.162.4.191, 203.162.0.181,… ) và của Viettel thì khác, cũng có thể
DNS là GateWay nhưng thực chất trong ADSL Router nhà cung cấp ISP đã cài đặt DNS truy vấn
tới các địa chỉ trên rồi.
- Để minh họa hơn về IP,mình sẽ lấy 1 ví dụ về truy cập WEB trên Internet.
- Sau đó host sẽ truy cập tới 72.14.207.99 nhưng thực sự là nó nhờ Gateway (ADSL Router
192.168.1.1) vì khác đường mạng.
- Cuối cùng nó sẽ trả lại cho host mang địa chỉ 192.168.1.254 và trang WEB của google sẽ hiện
ra trên web browser…
Trong Winsock người ta sử dụng cấu trúc sockaddr_in để biểu diễn địa chỉ IP.
struct sockaddr_in{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Trong đó:
- sin_family: AF_INET
- sin_port:
Là port (cổng logic) để lắng nghe kết nối.
-> 1 ứng dụng Server hay Client rất cần 1 port để liên lạc trên mạng. Bởi vì card mạng sẽ thông qua số
hiệu port này để nhận biết được gói tin đang nhận (hay gửi đi) của ứng dụng nào?
- Các port dưới 1024 là những port dành riêng cho các dịch vụ như:
Một số port như: 80 (Web), 20,21 (FTP), Mail (25 SMTP, 993 POP3), 53 (DNS), 22 (SSH), 23
(Telnet)...
Ví dụ như ta gõ http://www.google.com
- http là giao thức WEB => Port 80.
- www.google.com => IP: 72.14.207.99
Như vậy tại Client sẽ khởi tạo 1 port (lớn hơn 1024) kết nối tới địa chỉ 72.14.207.99 qua port 80.
Dựa vào số port người quản trị có thể kiểm soát được mạng ví dụ như chỉ có lướt WEB (mở port 80,
53) nhưng chặn hết các port khác => Bạn không thể chat và gameonline mặc dù có Internet.
Với số nguyên u_short (16bit) có nghĩa có thể chọn tới 2^16 = 65535 port sử dụng giao dịch trên mạng.
à ứng dụng của chúng ta phải chọn port lớn hơn 1024 nếu hông muốn viết 1 Sever như WEB hay FTP,
…
- sin_addr: là một struct để xác định địa chỉ IP (có thể là IP của Server hoặc IP của chính mình,…)
- Nếu sin_addr.s_addr = INADDR_ANY; Thì IP sẽ chính là IP của mình. Các Server thường dùng để
chọn địa chỉ IP của mình và lắng nghe kết nối..
- Nếu sin_addr.s_addr = inet_addr(“x x x.x x x.x x x.xx”); Thì IP sẽ là IP chỉ định theo chuỗi. Client
phải chỉ định được IP của Server mới kế nối được.
Ví dụ:
sockaddr_in ip;
ip.sin_family = AF_INET;
ip.sin_port = htons(99999);
ip.sin_addr.s_addr = inet_addr("10.0.0.2");
Mục tiêu lập trình mạng sẽ đưa ra những ứng dụng dạng Client – Server. Tức là sẽ có 2 loại ứng dụng
chính đó là Client và Server.
Quy trình hoạt động của ứng dụng Server – Client như sau:
- Server có nhiệm vụ của là lắng nghe, chờ đợi kết nối từ Client trên địa chỉ IP của mình với PORT
được quy định sẵn. Khi client gởi dữ liệu tới Server thì nó phải giải quyết một công việc là nhận dữ
liệu đó -> xử lý -> trả kết quả lại cho Client.
- Client là ứng dụng được phục vụ, nó chỉ gởi truy vấn và chờ đợi kết quả từ Server.
Trong mô hình TCP/IP có 2 giao thức là TCP và UDP và 2 giao thức này sẽ quyết định cách thức hoạt
động của Client - Server như thế nào?
a. Hoạt động của Client – Server trong giao thức TCP (SOCK_STREAM)
b. Hoạt động của Client – Server trong giao thức UDP (SOCK_DGRAM)
2. Một số hàm liên quan tới gởi và nhận dữ liệu
a. BIND
int bind(
SOCKET s,
const struct sockaddr FAR* name,
int namelen
);
Tác dụng dụng của BIND là sẽ giúp cho SOCKET của SERVER biết rằng nó sẽ chờ đợi kết nối và
nhận dữ liệu trên IP nào và PORT bao nhiêu?
Ví dụ:
// Thiết lập IP
sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
b. LISTEN
int listen(
SOCKET s,
int backlog
);
Kể từ khi gọi hàm này thì SERVER sẽ bắt đầu lắng nghe kết nối của mình.
c. CONNECT
int connect(
SOCKET s,
struct sockaddr *serv_addr,
int addrlen );
Hàm được gọi từ CLIENT nếu nó muốn kết nối tới SERVER
- SOCKET s: Socket đã được khởi tạo.
- sockaddr *serv_addr: IP và PORT của Server.
- int addrlen: Sizeof của cấu trúc sockaddr.
d. ACCEPT
SOCKET accept(
SOCKET s,
struct sockaddr FAR* addr,
int FAR* addrlen
);
Khi Client kết nối tới Server. Nó phải chờ Server chấp nhận kết nối (nếu ở giao thức TCP) bằng hàm
accept trên.
int recv(
SOCKET s,
char FAR* buf,
int len,
int flags
);
int send(
SOCKET s,
const char FAR * buf,
int len,
int flags
);
- SOCKET s: Là SOCKET được tạo ra khi Server chấp nhận kết nối từ CLIENT
- char FAR* buf: Là dữ liệu (dạng BYTE – char) nhận hay gửi.
- int len: Kích thước của dữ liệu.
- int flags: Một số cờ hiệu đi kèm (thông thường là 0).
f. REVCFROM/SENDTO
int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr *from,
int *fromlen);
int sendto(
SOCKET s,
int flags,
const struct sockaddr* to,
int tolen
);
g. CLOSE/SHUTDOWN
int shutdown(
SOCKET s,
int how
);
Hủy SOCKET sau một kết nối hoặc kết thúc chương trình.
Tham số how của shutdown:
- SD_RECEIVE: Đóng SOCKET, không cho phép NHẬN nhưng cho phép GỬI.
- SD_SEND: Đóng SOCKET, không cho phép GỬI nhưng cho phép NHẬN.
- SD_BOTH: Không cho GỬI và NHẬN (giống gọi hàm closesocket).
Tại sao có chuyện đóng nhưng cho phép gửi và nhận, là do Winsock hỗ trợ rất nhiều giao thức như
ATM, IPX,… do đó nó có những cái phức tạp, mình cũng không hiểu rõ bởi vì khả năng dịch vẫn hạn
chế. Vì vậy có lẽ vẫn nên sử dụng SD_BOTH hoặc closesocket).
5. DEMO MINI WEBSERVER
Thật ra ở đây mình không có tham vọng làm 1 Web Server thực sự như IIS hay Apache đâu? Mình chỉ
muốn dùng Winsock để làm 1 chương trình có thể biến máy tính bạn thành 1 máy chủ Web. Nghĩa là có
ai đó truy cập vào thì nó sẽ trả lại 1 Webpage.
Lúc này thì Client tại người sử dụng sẽ Connect tới Webserver và gởi một nội dung Request như sau:
Nếu khi đã nhận được trang WEB mà trong Web còn có hình ảnh hay âm thanh thì tiếp tục gởi Request
tới Server
Như:
GET /samples/images/IMAGE.jpg HTTP/1.1
Và sau đó nó sẽ căn cứ vào yêu cầu của Client: GET /Page?ID=1 HTTP/1.1 để gửi trả lại 1 trang
HTML hoặc file hình ảnh, âm thanh… tương ứng.
Ví dụ như:
<html>
<head><title>DEMO WEBSERVER - WINSOCKC++</title></head>
<body>
<h1>Hello eXecutve!</h1>
<br>
CopyRight 2007 by eXecutive;
stairwaytoheaven187@yahoo.com
</body>
</html>
#define MY_PORT 80
..
..
int SetupIPWS(SOCKET& ListenSock,sockaddr_in& MyListenIP){
MyListenIP.sin_family = AF_INET;
MyListenIP.sin_port = htons( MY_PORT ); // PORT 80
MyListenIP.sin_addr.s_addr = INADDR_ANY; // IP của mình
return 0;
}
Hàm lắng nghe phục vụ kết nối.
cout << "Dang lang nghe ket noi... tai IP: " <<
inet_ntoa(MyListenIP.sin_addr)
<< " PORT: " << MyListenIP.sin_port << "\nCTRL + C de STOP
SERVER\n\n";
while (1){
nSizeAddr = sizeof(sockaddr); // Quan trọng! Xác
định kích thước sockaddr
if (NewConnection == -1){
cout << "Loi ket noi tu Client\n";
continue;
}
Có lẽ 1 chương trình kỳ cục. While(1) không có điều kiện kết thúc. Nhưng đó là cách hoat động của
SERVER, 1 chương trình có thể chạy quanh năm suốt tháng. Do đó nên không có vấn đề gì?
Download Demo Here (Do Freehost nên có lúc nó bị DIE, phải chờ đợi thôi!)
a. Nhận địa chỉ Website và phân giải tên miền thành địa chỉ IP.
char lpDomain[100];
cout << "\n\nNhap ten mien can truy cap: http://";
cin.getline(lpDomain,100);
hostent *ConnectPC=NULL;
ConnectPC = gethostbyname(lpDomain); // Lay PC theo Tên Domain
if (!ConnectPC){
cout << "DNS khong the phan giai duoc ten mien nay ...\n";
return 1;
}
ConnectIP.sin_family = AF_INET;
ConnectIP.sin_port = htons( MY_PORT );
ConnectIP.sin_addr.s_addr = (*(DWORD*)ConnectPC->h_addr_list[0]);
// Lay IP
Ví dụ người sử dụng nhập là "http://www.google.com" thì phải có Request tối thiểu để máy chủ xử lý
là:
GET / HTTP/1.1
Host: google.com
User-Agent: eXecutive DEMO Client
-> "Host: google.com" rất quan trọng vì có nhiều WEB thuê HOSTING và lúc này có nhiều WEBSITE
cùng trên 1 WEBSERVER. Do đó phải xác định miền truy cập
// GUI REQUEST
send(Sock,GetHTML,strlen(GetHTML),0);
send(Sock,HostRequest,strlen(HostRequest),0);
send(Sock,UserAgent,strlen(UserAgent),0);
char HeaderResponse;
while(!done){
nByteRevc = recv(Sock,&HeaderResponse,1,0);
if(nByteRevc<0) // Loi
done=TRUE;
switch(HeaderResponse) {
case '\r':
break;
case '\n': // Neu gap "\n\n" hay "\n\r\n" thi ket thuc
Header
if (EndHeader == true){
done = TRUE;
}
EndHeader=true;
break;
default:
EndHeader=false;
break;
}
cout << HeaderResponse;
}
char HTMLResponse[513]={0};
do{
nByteRevc = recv(Sock,HTMLResponse,sizeof(HTMLResponse)-1,0);
// Đọc 512 Bytes
1. UDP là gì?
- Thật ra mình đã giới thiệu về UDP ở phần tạo SOCKET rồi nhưng mình nói lại 1 xíu cho nhanh. UDP
là một trong 2 giao thức chính của mô hình TCP/IP để truyền tải dữ liệu. CLIENT - SERVER sử dụng
UDP sẽ không bao giờ quan tâm đến về đề dữ liệu có chính xác hay không? Bên gửi cứ gửi và bên
nhận cứ nhận, nhận không được thì mặc kệ...
- Người ta sử dụng UDP trong những ứng dụng cần truyền dữ liệu nhanh như CHAT, GAME ONLINE,
....
- Và lần này mình sẽ DEMO 1 bộ ứng dụng Client - Server liên lạc với nhau mà Client không cần biết
IP Server sử dụng cơ chế Multicast hay Broadcast
a. Broadcast
-> Các host chỉ thấy nhau (liên lạc được) khi chúng có cùng NET_ID.
-> Muốn host liên lạc khi khác NET_ID thì cần phải có cơ chế định tuyến (sử dụng thiết bị router,
Server có cấu hình Routing ...)
=> Như vậy trong 1 NET_ID phải có 1 địa chỉ nào đó để đại diện cho tất cả các HOST đường mạng đó
và đó chính là địa chỉ Broadcast
Ví dụ:
Lớp A:
Có các IP sau : 10.0.0.100, 10.12.3.1 hay 10.252.252.252
-> Tất cả đều có chung 1 đường mạng 10.0.0.0
-> Và IP đại điện là IP Broadcast: 10.255.255.255.
Nghĩa là khi có 1 gói tin UDP gởi tới địa chỉ này thì tất cả các host trên đều nhận được
b. Multicast
- Địa chỉ Broadcast đại diện cho tất cả các Host nhưng trên cùng 1 đường mạng.
- Địa chỉ Multicast đại diện cho tất cả các HOST trên tất cả các đường mạng khác nhau
Hay chính xác hơn thì địa chỉ "255.255.255.255" chính là địa chỉ Multicast.
Khi gởi 1 gói tin UDP Packet tới địa chỉ này thì tất cả các host có thể liên lạc được với host gửi (khác
đường mạng phải có cơ chế định tuyến) đèu nhận được gói tin này.
Tuy nhiên lưu ý 1 điều là Multicast hay Broadcast chỉ có giá trị trong IPLAN
- Rõ ràng máy Server trong mạng LAN có thể là bất cứ máy tính nào mà Gamer chọn Create Game?
=> Vậy làm sao Client có thể biết được IP máy chủ để Connect.
Và nó phải sử dụng cơ chế tìm IP SERVER sử dụng UDP Broadcast.
a. SERVER
Cũng như TCP. Nhiệm vụ chính của nó là lắng nghe, tuy nhiên chỉ khác chức năng là không cần phải
chấp nhận kết nối từ Client.
// Thiết lập IP PORT
pAddr->sin_family = AF_INET;
pAddr->sin_port = htons(MY_PORT);
pAddr->sin_addr.S_un.S_addr = ADDR_ANY;
// Tìm địa chỉ IP của SERVER <sau này trả lại cho CLIENT>
char lpName[100];
char lpMyIP[100]={0};
gethostname(lpName,sizeof(lpName));
hostent* pMyServer = gethostbyname(lpName);
u_long myIP = *(u_long*)pMyServer->h_addr_list[0];
strcpy(lpMyIP,inet_ntoa(*(in_addr*)&myIP));
//
------------------------------------------------------------------
----------
if (nResult == -1){
cout << "Loi thiet lap IP va PORT\n";
HuyWinsock(sock);
return 1;
}
cout << "Dang lang nghe ket noi tren IP: " << lpMyIP << " port:
" << MY_PORT << "\n\n";
int nAddrLen;
int nRevc;
int nSend;
sockaddr_in IPClient;
char buff[512]={0};
while (1){
buff[nRevc-1] = 0;
b. CLIENT
-> Nhiệm vụ chính của CLIENT là gửi 1 gói tin BROADCAST với PORT quy định sẵn
Trong chương trình của CLIENT có 1 hàm rất quan trọng để cho phép SOCKET gởi tới địa chỉ
BROADCAST
setsockopt(*sock,SOL_SOCKET,SO_BROADCAST,(char*)&b SockBroadcast,sizeof(BOOL));
Hàm này có rất nhiều Option. Các bạn có thể tham khảo thêm MSDN.
setsockopt(*sock,SOL_SOCKET,SO_BROADCAST,
(char*)&bSockBroadcast,sizeof(BOOL));
pServerAddr->sin_family = AF_INET;
pServerAddr->sin_port = htons(MY_PORT);
pServerAddr->sin_addr.S_un.S_addr =
inet_addr("255.255.255.255"); // Địa chỉ đích là MULTICAST
int nSend;
int nRevc;
int nAddrLen;
char buff[512]="IP may chu dau???"; // Thông tin gửi tới máy
chủ
return 0;
}
ci. Xin đưa thêm một ví dụ nhỏ về ứng dụng Server - Client sử dụng UDP.
Server chờ để nhận yêu cầu kết nối từ client. Client cần chỉ địa chỉ IP mà nó cần kết nối, sau đó
nó sẽ gởi 10 dòng dữ liệu theo kiểu UDP đến địa chỉ đó mà không cần quan tâm đến việc server
có nhận được hay không.
Server :
#include<iostream>
#include<Winsock2.h>
#include<stdio.h>
#pragma comment(lib,"Ws2_32")
using namespace std;
const unsigned NPACK=10;
const unsigned BUF_LENGTH=512;
int main(){
WORD wVersionRequested=MAKEWORD(1,1);
WSADATA wsaData;
SOCKET serverSock;
sockaddr_in sockAddr,clientAddr;
int clientAddrLeng=sizeof(clientAddr);
char buf[BUF_LENGTH];
int port=2512;
if(WSAStartup(wVersionRequested,&wsaData)==0){
serverSock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
memset(&sockAddr,0,sizeof(sockAddr));
sockAddr.sin_family=AF_INET;
sockAddr.sin_port=htons(port);
sockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(serverSock,(sockaddr*)&sockAddr,sizeof(sockAddr));
printf("Wait for UDP clients ...\n\n");
//for(int i=1;i<=NPACK;i++){
while(true){
if(recvfrom(serverSock,buf,BUF_LENGTH,0,
(sockaddr*)&clientAddr,&clientAddrLeng)!=0){
printf("Received packet from %s:%d. Data :
%s\n\n",inet_ntoa(clientAddr.sin_addr),
htons(clientAddr.sin_port),buf);
}
else
printf("Missed \n\n");
}
closesocket(serverSock);
WSACleanup();
setsockopt
}
else
{
cout<<"Can not start winsock"<<endl;
}
return 0;
}
Client
#include<iostream>
#include<Winsock2.h>
#include<stdio.h>
#pragma comment(lib,"Ws2_32")
using namespace std;
const unsigned NPACK=10;
const unsigned BUF_LENGTH=512;
int main(){
WORD wVersionRequested=MAKEWORD(1,1);
WSADATA wsaData;
SOCKET clientSock;
sockaddr_in sockAddr,serverAddr;
struct hostent* serverHostent;
char buf[BUF_LENGTH];
char hostName[256];
int port=2512;
if(WSAStartup(wVersionRequested,&wsaData)==0){
clientSock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
cout<<"Enter host name : ";
cin.getline(hostName,256);
serverHostent=gethostbyname(hostName);
if(serverHostent==NULL){
printf("Can not solve host \n");
}
memset(&sockAddr,0,sizeof(sockAddr));
memcpy(&sockAddr.sin_addr,serverHostent-
>h_addr,serverHostent->h_length);
sockAddr.sin_family=serverHostent->h_addrtype;
sockAddr.sin_port=htons(port);
for(int i=1;i<=NPACK;i++){
printf("Send packet %d \n",i);
sprintf(buf,"This is packets %d",i);
if(sendto(clientSock,buf,sizeof(buf),0,
(sockaddr*)&sockAddr,sizeof(sockAddr))==SOCKET_ERROR){
printf("Error send\n");
}
}
closesocket(clientSock);
WSACleanup();
}
else
{
cout<<"Can not start winsock"<<endl;
}
return 0;
}