3.3.1 Sử dụng API:
3.3.1.1 Client:
Để sử dụng các hàm của Winsock trong ứng dụng , ta cần phải include Winsock.h vào trong mã nguồn của chúng ta , để ta có thể liên kết với thư viện winsock.lib .
Điều đầu tiên mà chúng ta cần làm để sử dụng được Winsock là cần phải bảo đảm được rằng: phiên bản đúng của winsock.dll được tải vào bộ nhớ. Để làm được chúng ta gọi hàm WSAStartup(), được xác định như sau:
int WSAStartup( WORD wVersionRequired , LPWSADATA lpWSAData )
wVersionRequired xác định phiên bản của Winsock mà ta muốn tải về . Đối với Pocket PC, giá trị này cần thiết cho phiên bản 1.1, được tạo bằng cách dùng macro MAKEWORD(1,1). Tham số lpWSAData là một con trỏ , trỏ đến cấu trúc WSAData mà hàm WSAStartup() sẽ điền vào đó những thông tin về phiên bản của Winsock cần tải xuống.
typedef struct WSAData { WORD wVersion; WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets;
unsigned short iMaxUdpDg; char FAR *lpVendorInfo; } WSADATA;
Cả hai tham số wVersion và wHighVersion sẽ trả về phiên bản hiện hành của Winsock thực sự được tải xuống bộ nhớ, thường được thiết lập là 0x0101. Hai tham số szDescription và szSystemStatus không được sử dụng trên Pocket PC và được thiết lập là NULL. Tham số iMaxSockets chỉ số lượng socket tối đa mà ứng dụng của chúng ta có thể mở ra. Không có sự bảo đảm nào cho ứng dụng của ta có thể mở những socket này. Tham số iMaxUdpDg xác định kích thước tối đa của gói UDP datagram. Nếu giá trị này là 0, thì không có giới hạn trong winsock này. Tham số cuối cùng, lpVendorInfo , là con trỏ, trỏ đến thông tin tùy chọn liên quan đến máy tính.
Trước khi ứng dụng kêt thúc, chúng ta nên gọi hàm WSACleanup(), được xác định như sau :
int WSACleanup(void ) ;
Tuy nhiên, hàm này thực sự không làm gì hết, nó chỉ duy trì sự tương thích với những ứng dụng desktop đã được nối cổng đến Windows CE.
Nếu có lỗi xảy ra khi gọi hàm winsock ( ngoại trừ gọi hàm WSAStartup(), nó sẽ trả về một mã lỗi), hầu hết những hàm sẽ trả về SOCKET_ERROR của Winsock chuẩn ( được xác định trong winsock.h, thường là -1).
Để lấy những thông tin về lỗi đã xảy ra, bạn có thể gọi hàm WSAGetLastError() để tìm nguyên nhân hàm bị lỗi.
int WSAGetLastError( int iError ) ; Tham số iError xác định mã lỗi mới.
Chúng ta có thể tìm những thông tin về mã lỗi riêng của Winsock trong tập tin tiêu đề winsock.h.
3.3.1.1.2 Luồng Socket :
Luồng Socket có lẽ là loại giao thức truyền tin được dùng phổ biến nhất trên TCP/IP. Socket TCP cung cấp cho ta dạng ống dữ liệu (data pipe) đáng tin cậy, gần như không bị lỗi giữa hai máy, hai máy có thể gửi và nhận những luồng byte qua lại với nhau mà không có dữ liệu bị mất hay bị sao lại (dupliacate).
3.3.1.1.3 Tạo socket :
Bước đầu tiên trong việc thiết lập một kết nối mạng dùng winsock là tạo một socke. Socket là một loại dữ loại, tương tự như file handle, xác định descriptor duy nhất cho phép truy cập đến đối tượng mạng của bạn. Những thứ mà descriptor này xác định không được trình bày chi tiết trong winsock specification, mà được xác định rõ trong winsock implementation, cho nên chúng ta không thực sự biết giá trị đó có nghĩa là gì.
Tuy nhiên, nội dung của desciptor là không quan trọng đối với chúng ta, điều quan trọng là chúng ta hiểu được Socket là cái mà chúng ta dùng để truy cập đến kết nối mạng của bạn.
Để tạo một Socket, ta dùng hàm socket(), được xác định như sau : SOCKET socket( int af, int type, int protocol )
Tham số af thể hiện address family của giao thức, xác định loại giao thức của socket sẽ được tạo. Pocket PC hỗ trợ cả socket AF_INET hoặc AF_IRDA. Nếu bạn muốn tạo một socket cho giao tiếp hồng ngoại thì ta dùng AF_IRDA, ngược lại ta dùng AF_INET cho TCP/IP. Tham số type là loại giao tiếp của giao thức, và có thể thiết lập là SOCK_STREAM hay SOCK_DGRAM. Để tạo một Socket TCP ta dùng SOCK_STREAM và SOCK_DGRAM cho UDP. Ta cũng dùng SOCK_STREAM cho việc tạo Socket dùng giao tiếp hồng ngoại. Tham số cuối cùng, protocol, xác định loại giao thức nào dùng với socket này. Nếu bạn muốn sử dụng giao thức TCP, thì hãy dùng giá trị IPPROTO_TCP và IPPROTO_UDP dành cho giao thức UDP.
Giá trị trả về của hàm này là một handle socket mới hoặc lỗi INVALID_SOCKET. Nếu bạn muốn tìm nguyên nhân vì sao bạn không thể tạo ra được socket , bạn hãy dùng hàm WSAGetLastError ().
Đoạn mã sau trình bày cách tạo ra một kết nối socket. // Create a connection-oriented socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket
if(s == INVALID_SOCKET) {
int iSocketError = WSAGetLastError(); return FALSE;
}
Khi bạn đã tạo một socket, bạn có thể dùng nó để thiết lập một kết nối đến server . Ta dùng hàm connect() để thực hiện việc này:
int connect (SOCKET s, const struct sockaddr *name, int namelen ).
Tham số đầu tiên, s, xác định socket descriptor được trả về bởi hàm socket() . Tham số name là socket address structure , SOCKADDR_IN , xác định server mà ta định kết nối .Tham số namelen là chiều dài của bộ đệm dành cho tham số name
Nếu ta thành công trong việc thiết lập một kết nối đến Server xác định bởi tham số name , thì hàm này sẽ trả về giá trị 0, ngược lại lỗi một SOCKET_ERROER sẽ xảy ra. Để tìm xem thông tin vì sao không thiết lập được kết nối ta gọi hàm WSAGetLastError () . Nên nhớ rằng ta không thể gọi hàm connect() trên một socket đã được kết nối.
Một khi một kết nối đã được thiết lập, thì socket sẵn sàng gửi và nhận dữ liệu . Chú ý rằng: Nếu một kết nối đã bị đứt (broken) trong lúc Client và Server đang truyền tin với nhau thì ứng dụng của ta cần phải bỏ socket cũ và tạo một socket mới để thiết lập lại việc truyền tin giữa Client và Server.
Ví dụ sau trình bày cách kết nối đến Server : // First, get the host information
HOSTENT *hostServer = gethostbyname("www.microsoft.com"); if(hostServer == NULL) {
int iSocketError = WSAGetLastError(); return FALSE;
}
// Set up the target device address structure SOCKADDR_IN sinServer;
memset(&sinServer, 0, sizeof(SOCKADDR_IN)); sinServer.sin_family = AF_INET;
sinServer.sin_port = htons(80); sinServer.sin_addr =
*((IN_ADDR *)hostServer->h_addr_list[0]); // Connect with a valid socket
if(connect(s, (SOCKADDR *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) {
int iSocketError = WSAGetLastError(); return FALSE;
}
// Do something with the socket closesocket(s);
3.3.1.1.5 Gửi và nhận dữ liệu :
Chúng ta đã thiết lập một kết nối đến server, đã sẵn sàng cho việc gửi và nhận dữ liệu giữa hai máy trên mạng. Trên một kết nối socket, dữ liệu có thể được truyền cả hai hướng, Server và Client có thể dùng cùng phương thức để truyền tin với nhau.
Để truyền dữ liệu trên một kết nối socket ta dùng phương thức send(), được xác định như sau:
int send (SOCKET s, const char *buf, int len, int flags) ;
Tham số s là socket handle mà ta đã dùng với hàm connect () ở trên, và nó được tạo lúc đầu bằng hàm socket(). Tham số buf là một con trỏ, trỏ đến bộ đệm chứa dữ liệu mà ta muốn gửi, và chiều dài của nó được xác định bởi tham số len. Tham số cuối cùng, flags, được dùng để xác định cách dữ liệu được gửi, có giá trị là 0 hoặc MSG_DONTROUTE. Thông thường, tham số này được thiết lập là 0, và MSG_DONTROUTE chỉ dùng cho việc kiểm tra hoặc định đường đi cho thông điệp.
Giá trị trả về của hàm send() là số byte thực sự được gửi trên mạng hoặc một SOCKET_ERROR nếu có lỗi trong việc truyền dữ liệu.
Để nhận dữ liệu trên một socket, ta dùng hàm recv(): int recv ( SOCKET s , char *buf , int len , int flags ) ;
Tương tự như trên, tham số s chỉ socket mà chúng ta thiết lập để nhận dữ liệu . Tham số thứ hai, buf, là một bộ đệm để nhận dữ liệu, kích thước của nó được xác định bởi tham số len. Cuối cùng, tham số flags, thường được thiết lập là 0.
Giá trị trả về của hàm recv() là số byte nhận được hoặc là 0 nếu kết nối bị đóng . Hàm cũng sẽ trả về một SOCKET_ERROR nếu có lỗi xảy ra .
Chú ý rằng: cả hai hàm send() và recv() không luôn luôn đọc hoặc ghi chính xác
số lượng dữ liệu mà ta đã yêu cầu. Điều này là do TCP/IP cấp phát một số lượng không gian bộ đệm có hạn cho hàng đợi dữ liệu đi và vào, và thường thì bộ đệm này đầy khá nhanh. Ví dụ, nếu chúng ta yêu cầu một file 10 MB từ một trang web, hàng đợi dữ liệu vào của ta sẽ khóa cho đến khi ta đọc được dữ liệu từ hàng đợi này (dùng hàm receive()). Việc truyền dữ liệu cũng tương tự như thế, cho nên chúng ta cần đảm bảo rằng: tất cả dữ liệu ra của chúng ta đã được gửi.
Ví dụ sau thể hiện việc gửi dữ liệu bộ đệm trên TCP: // Send a request to the server
char cBuffer[1024] = ""; int nBytesSent = 0; int nBytesIndex = 0; // Set up the buffer to send
sprintf(cBuffer, "GET / HTTP/1.0\r\n\r\n"); int nBytesLeft = strlen(cBuffer);
// Send the entire buffer while(nBytesLeft > 0) {
nBytesSent = send(s, &cBuffer[nBytesIndex], nBytesLeft, 0); if(nBytesSent == SOCKET_ERROR)
break;
// See how many bytes are left. If we still need to send, loop nBytesLeft -= nBytesSent;
nBytesIndex += nBytesSent; }
3.3.1.1.6 Ví dụ:
Ví dụ sau trình bày cách dùng socket TCP để tạo một client cơ bản để kết nối với một trang web, gửi yêu cầu, và nhận trang web HTML mặc định. Khi thực hiện thành công, nó sẽ nội dung trên Message Box. Bộ đệm thực sự được trả về từ yêu cầu này được trình bày trong hình sau:
Hình 3.40 // Initialize Winsock WSADATA wsaData; memset(&wsaData, 0, sizeof(WSADATA)); if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) return FALSE;
// Create a connection-oriented socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket
if(s == INVALID_SOCKET) {
}
// Get the host information
HOSTENT *hostServer = gethostbyname("www.microsoft.com"); if(hostServer == NULL) {
int iSocketError = WSAGetLastError(); return FALSE;
}
// Set up the target device address structure SOCKADDR_IN sinServer; memset(&sinServer, 0, sizeof(SOCKADDR_IN)); sinServer.sin_family = AF_INET; sinServer.sin_port = htons(80); sinServer.sin_addr = *((IN_ADDR *)hostServer->h_addr_list[0]); // Connect
if(connect(s, (SOCKADDR *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) {
int iSocketError = WSAGetLastError(); return FALSE;
}
// Send a request to the server char cBuffer[1024] = ""; int nBytesSent = 0; int nBytesIndex = 0; // Set up the buffer to send
sprintf(cBuffer, "GET / HTTP/1.0\r\n\r\n"); int nBytesLeft = strlen(cBuffer);
// Send the entire buffer while(nBytesLeft > 0) {
nBytesSent = send(s, &cBuffer[nBytesIndex], nBytesLeft, 0); if(nBytesSent == SOCKET_ERROR)
break;
// See how many bytes are left. If we still need to send, loop nBytesLeft -= nBytesSent;
nBytesIndex += nBytesSent; }
// Get the response
TCHAR tchResponseBuffer[1024] = TEXT("\0"); char cResponseBuffer[1024] = "";
BOOL fBreak = FALSE; int nBytesReceived = 0; while(!fBreak) {
nBytesReceived = recv(s, &cResponseBuffer[0], 1024, 0); if(nBytesReceived == SOCKET_ERROR)
break;
// Convert the data from ANSI to Unicode
mbstowcs(tchResponseBuffer, cResponseBuffer, nBytesReceived); // Show the MessageBox
MessageBox(NULL, tchResponseBuffer, TEXT("Web Output"), MB_OK); // Check to see if this is the end of the HTTP response by
// looking for \r\n\r\n
if(_tcsstr(tchResponseBuffer, TEXT("\r\n\r\n"))) fBreak = TRUE;
memset(tchResponseBuffer, 0, 1024); memset(cResponseBuffer, 0, 1024); } closesocket(s); WSACleanup(); 3.3.1.2 Server:
3.3.1.2.1 Nhận một kết nối vào ( Server ) :
Sự khác nhau cơ bản của việc truyền dữ liệu giữa luồng kết nối Server và Client là cách kết nối được thiết lập (Client thì tạo kết nối, còn Server thì lắng nghe kết nối). Mặt khác, cả hai đều sử dùng phương thức send() và recv() để trao đổi dữ liệu giữa hai máy. Chúng ta đã tìm hiểu ở phía Client, giờ đây ta bắt đầu tìm hiểu cách tạo một ứng dụng theo yêu cầu của những dịch vụ kết nối vào (hình thành do gọi hàm connect()). Điều đầu tiên mà chúng ta cần làm là tạo một socket; giống như ta đã làm ở phía Client bằng cách gọi hàm socket().
Sau khi đã tạo socket rồi, thay vì kết nối đến một Server, ta để socket mới này ở trạng thái lắng nghe kết nối vào. Để làm được việc đó, chúng ta cần kết buộc (bind) socket mới được tạo này với một địa chỉ cục bộ. Để tạo kết buộc này ta dùng hàm bind()
int bind( SOCKET s, const struct sockaddr *addr, int namelen ) ;
Tham số đầu tiên, s, là một handle của socket được tạo bởi hàm socket(), và ta sẽ dùng socket này để chở kết nối. Tham số addr là một con trỏ, trỏ đến address buffer, được xác định bởi giao thức mà ta muốn sử dụng. Nếu ta muốn dùng giao thức TCP/IP chuẩn, thì chúng ta sẽ dùng bộ đệm SOCKADDR_IN. Nếu dùng giao thức hồng ngoại thì sử dùng SOCKADDR_IRDA. Tham số cuối cùng, namelen, chỉ kích thước của cấu trúc địa chỉ (address structure) mà tham số addr đã dùng.
Nếu không có lỗi, hàm bind() sẽ trả về 0, ngược lại, một SOCKET_ERROR sẽ xuất hiện.
Ví dụ sau sẽ kết buộc kết nối TCP trên cổng 80 đến một socket cho tất cả địa chỉ IP trên thiết bị.
SOCKADDR_IN sListener;
memset(&sListener, 0, sizeof(SOCKADDR_IN)); // Set up the port to bind on
sListener.sin_family = AF_INET; sListener.sin_port = htons(80);
sListener.sin_addr.s_addr = htonl(INADDR_ANY); // Create a TCP socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(s == INVALID_SOCKET)
return FALSE; // Bind to the socket
if(bind(s, (SOCKADDR *)&sListener, sizeof(sListener)) == SOCKET_ERROR) {
int iSocketError = WSAGetLastError(); return FALSE;
}
Chú ý rằng: Ta đã dùng địa chỉ IP INADDR_ANY thay vì địa chỉ IP của Adapter .
Dùng INADDR_ANY làm cho chúng ta có thể kết buộc socket vào tất cả những địa chỉ IP có sẵn trên thiết bị của chúng ta, do đó những kết nối vào trên bất kỳ giao diện nào cũng được chấp nhận bởi socket của chúng ta.
Khi socket đã được kết buộc vào địa chỉ (hoặc nhiều địa chỉ), chúng ta cần đặt socket này ở chế độ lắng nghe. Điều này làm cho socket có thể chờ những kết nối vào
int listen( SOCKET s , int backlog) ;
chỉ giới hạn cho hai kết nối). Hàng đợi backlog được dùng khi cùng lúc có nhiều kết nối vào. Khi hàng đợi đầy, tất cả những yêu cầu khác sẽ bị từ chối cho đến khi một yêu cầu kết nối được lấy ra khỏi hàng đợi bởi hàm accept().
Nếu có lỗi hàm listen() sẽ trả về giá trị SOCKET_ERROR, ngược lại là giá trị 0 Cuối cùng để nhận socket của kết nối vào, chúng ta cần gọi hàm accept(), được xác định như sau.
SOCKET accept( SOCKET s, struct sockaddr *addr, int * addrlen);
Tham số s chỉ socket mà chúng ta đã đặt ở chế độ lắng nghe ở trên. Tham số addr chỉ bộ đệm dùng để nhận hoặc là một cấu trúc SOCKADDR_IN hoặc là SOCKADDR_IRDA tùy thuộc vào giao thức mà socket đã dùng, chứa những thông tin về kết nối vào. Tham số cuối cùng, addrlen, chỉ kích thước của cấu trúc addr.
Chú ý rằng : phương thức accept() không trả về giá trị ngay lập tức. Điều này là do accept() là hàm khóa, nghĩa là nó sẽ không trả về giá trị cho đến khi có kết nối từ một client hoặc socket lắng nghe bị hủy (ta cũng có thể thiết lập một tùy chọn socket để đặt nó ở chế độ không khóa). Khi hàm accept() trả về, thì giá trị của nó hoặc là một socket handle mới cho client kết nối vào, hoặc là một lỗi SOCKET_ERROR. Tất cả những thông tin về client kết nối vào sẽ được thể hiện ở socket handle mới này, trong khi socket ban đầu tiếp tục lắng nghe nhiều kết nối khác.
3.3.1.2.2 Ví dụ:
Ví dụ sau thể hiện việc Server lắng nghe kết nối vào của một Client yêu cầu một trang Web dùng giao thức HTTP, và trả về cho Client một hồi đáp.
// Initialize Winsock WSADATA wsaData;
memset(&wsaData, 0, sizeof(WSADATA));
if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) return FALSE;
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket
if(s == INVALID_SOCKET) {
int iSocketError = WSAGetLastError(); return FALSE;
}
SOCKADDR_IN sListener;
memset(&sListener, 0, sizeof(SOCKADDR_IN)); // Setup the port to bind on
sListener.sin_family = AF_INET; sListener.sin_port = htons(80);
sListener.sin_addr.s_addr = htonl(INADDR_ANY); // Bind to the socket
if(bind(s, (SOCKADDR *)&sListener, sizeof(sListener)) == SOCKET_ERROR) {
int iSocketError = WSAGetLastError(); return FALSE;
}
// Listen for incoming connections
if(listen(s, SOMAXCONN) == SOCKET_ERROR) { int iSocketError = WSAGetLastError();
return FALSE; }
// Wait for a connection
SOCKADDR_IN sIncomingAddr;
SOCKET sIncomingSocket = accept(s, (SOCKADDR *) &sIncomingAddr, &iAddrLen);
if(sIncomingSocket == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE;
}
// We have an incoming socket request char cResponseBuffer[1024] = ""; int nBytesReceived = 0;
// Get a basic request. In reality, we would want to check // the HTTP request to see if it's valid, but let's just // send a simple response.