在網(wǎng)絡編程中,最常用和最基礎的就是WINSOCK. 現(xiàn)在我們討論WINDOWS下的SOCKET編程. 大凡在WIN32平臺上的WINSOCK編程都要經過下列步驟: 定義變量->獲得WINDOCK版本->加載WINSOCK庫->初始化->創(chuàng)建套接字->設置套接字選項->關閉套接字->卸載WINSOCK庫->釋放資源 下面介紹WINSOCK C/S的建立過程: 服務器 客戶端 ________________________________________________ 1 初始化WSA 1 初始化WSA ____________________________________________________ 2 建立一個SOCKET 2 建立一個SOCKET _____________________________________________________ 3 綁定SOCKET 3 連接到服務器 _____________________________________________________ 4 在指定的端口監(jiān)聽 4 發(fā)送和接受數(shù)據(jù) _____________________________________________________ 5 接受一個連接 5 斷開連接 ______________________________________________________- 6 發(fā)送和接受數(shù)據(jù) ___________________________________________________ 7 斷開連接 __________________________________________________ 大家注意,在VC中進行WINSOCK編程時,需要引入如下兩個庫文件:WINSOCK.H(這個是WINSOCK API的頭文件,WIN2K以上支持WINSOCK2,所以 可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API連接庫文件). 使用方式如下: #include <winsock.h> #pragma comment(lib,"ws2_32.lib") 下面我們通過具體的代碼演示服務器和客戶端的工作流程: 首先,建立一個WSADATA結構,通常用wsaData WSADATA wsaData; 然后,調用WSAStartup函數(shù),這個函數(shù)是連接應用程序與winsock.dll的第一個調用.其中,第一個參數(shù)是WINSOCK 版本號,第二個參數(shù)是指向 WSADATA的指針.該函數(shù)返回一個INT型值,通過檢查這個值來確定初始化是否成功.調用格式如下:WSAStartup(MAKEWORD(2,2),&wsaData),其中 MAKEWORD(2,2)表示使用WINSOCK2版本.wsaData用來存儲系統(tǒng)傳回的關于WINSOCK的資料. if(iResuit=WSAStartup(MAKEWORD(2,2),&wsaData)!=0) { printf("WSAStartup failed:%d",GetLastError()); //返回值不等與0,說明初始化失敗 ExitProcess(); //退出程序 } 應用程序在完成對請求的SOCKET庫使用后,要調用WSACleanup函數(shù)來接觸SOCKET庫的綁定,并且釋放資源. 注意WSAStartup初始化后,必須建立一個SOCKET結構來保存SOCKET句柄. 下面我們建立一個SOCKET. 首先我們建立一個m_socket的SOCKET句柄,接著調用socket()函數(shù),函數(shù)返回值保存在m_socket中.我們使用AF_INFE,SOCK_STREAM,IPPROTO_TCP 三個參數(shù).第一個表示地址族,AF_INFE表示TCP/IP族,第二個表示服務類型,在WINSOCK2中,SOCKET支持以下三種類型; SOCK_STREAM 流式套接字 SOCK_DGRAM 數(shù)據(jù)報套接字 SOCK_RAW 原始套接字 第三個參數(shù)表示協(xié)議: IPPROTO_UDP UDP協(xié)議 用于無連接數(shù)據(jù)報套接字 IPPROTO_TCP TCP協(xié)議 用于流式套接字 IPPROTO_ICMP ICMP協(xié)議用于原始套接字 m_socket=socket(AF_INFE,SOCK_STREAM,IPPROTO_TCP); //創(chuàng)建TCP協(xié)議 以下代碼用于檢查返回值是否有錯誤: if(m_scoket==INVALID_SOCKET) { prinrf("Error at socket():%d/n",GetLastError()); WSACleanup(); //釋放資源 return; } 說明,如果socket()調用失敗,他將返回INVALID_SOCKET. 為了服務器能接受一個連接,他必須綁定一個網(wǎng)絡地址,下面的代碼展示如何綁定一個已經初始化的IP和端口的Socket.客戶端程序用這個 IP地址和端口來連接服務器. sockaddr_in service; service.sin_family=AF_INET; //INTERNET地址族 service.sin_addr.s_addr=inet_addr("127.0.0.1"); //將要綁定的本地IP地址 service.sin_port=htons(27015); //27015將要綁定的端口 下面我們調用BIND函數(shù),把SOCKET和SOCKADDR以參數(shù)的形式傳入,并檢查錯誤. if(bind(m_socket,(SOCKADDR*)&SERVICE,sizeof(service))==SOCKET_ERROR) { printf("bind() failed./n"); closesocket(m_socket); return; } 當綁定完成后,服務器必須建立一個監(jiān)聽隊列,以接受客戶端的請求.listen()使服務器進入監(jiān)聽狀態(tài),該函數(shù)調用成功返回0,否則返回 SOCKET_ERROR.代碼如下: if(listen(m_socket,1)==SOCKET-ERROR) { printf("error listening on socket./n"); } 服務器端調用完LISTEN()后,如果此時客戶端調用CONNECT()函數(shù),服務器端必須在調用ACCEPT().這樣服務器和客戶端才算正式完成通信程序的 連接動作. 一旦服務器開始監(jiān)聽,我們就要指定一個句柄來表示利用ACCEPT()函數(shù)接受的連接,這個句柄是用來發(fā)送和接受數(shù)據(jù)的表示.建立一個SOCKET句柄 Socket AcceptSocket 然后利用無限循環(huán)來檢測是否有連接傳入.一但有連接請求,ACCEPT()函數(shù)就會被調用,并且返回這次連接的句柄. printf("waitong for a client to connect.../n"); while(1) { AcceptSocket=SOCKET_ERROR; while(AcceptSocket==SOCKET_ERROR) { AcceptSocket=accept(m_socket,NULL,NULL); } } 下面看客戶端端代碼: sockaddr_in clientService; clientService.sin_family=AF_INET; //INTERNET地址族 clientService.sin_addr.s_addr=inet_addr("127.0.0.1"); //將要綁定的本地IP地址 clientService.sin_port=htons(27015); //27015將要綁定的端口 下面調用CONNECT()函數(shù): if ( connect( m_socket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) { printf( "Failed to connect./n" ); WSACleanup(); return; } //如果調用失敗清理退出 //調用成功繼續(xù)讀寫數(shù)據(jù) _________________________________________________________________________________________________ 到這里,服務器和客戶端的基本流程介紹完畢,下面我們介紹數(shù)據(jù)交換. send(): int send { SOCKET s, //指定發(fā)送端套接字 const char FAR?*buf, //指明一個存放應用程序要發(fā)送的數(shù)據(jù)的緩沖區(qū) int len, //實際要發(fā)送的數(shù)據(jù)字節(jié)數(shù) int flags //一般設置為0 }; C/S都用SEND函數(shù)向TCP連接的另一端發(fā)送數(shù)據(jù). recv(): int recv { SOCKET s, //指定發(fā)送端套接字 char FAR?*buf, //指明一個緩沖區(qū) 存放RECC受到的數(shù)據(jù) int len, //指明BUF的長度 int flags //一般設置為0 }; C/S都使用RECV函數(shù)從TCP連接的另一端接受數(shù)據(jù) 下面將完整的程序代碼提供如下,大家可直接編譯運行 首先看客戶端的代碼: #include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") void main() { // 初始化 Winsock. WSADATA wsaData; int iResult = WSAStartup( MAKEWORD(2,2), &wsaData ); if ( iResult != NO_ERROR ) printf("Error at WSAStartup()/n"); // 建立socket socket. SOCKET client; client = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( client == INVALID_SOCKET ) { printf( "Error at socket(): %ld/n", WSAGetLastError() ); WSACleanup(); return; } // 連接到服務器. sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" ); clientService.sin_port = htons( 27015 ); if ( connect( client, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) { printf( "Failed to connect./n" ); WSACleanup(); return; } // 發(fā)送并接收數(shù)據(jù). int bytesSent; int bytesRecv = SOCKET_ERROR; char sendbuf[32] = "Client: Sending data."; char recvbuf[32] = ""; bytesSent = send( client, sendbuf, strlen(sendbuf), 0 ); printf( "Bytes Sent: %ld/n", bytesSent ); while( bytesRecv == SOCKET_ERROR ) { bytesRecv = recv( client, recvbuf, 32, 0 ); if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET ) { printf( "Connection Closed./n"); break; } if (bytesRecv < 0) return; printf( "Bytes Recv: %ld/n", bytesRecv ); } return; } 下面是服務器端代碼: #include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") void main() { // 初始化 WSADATA wsaData; int iResult = WSAStartup( MAKEWORD(2,2), &wsaData ); if ( iResult != NO_ERROR ) printf("Error at WSAStartup()/n"); // 建立socket SOCKET server; server = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( server == INVALID_SOCKET ) { printf( "Error at socket(): %ld/n", WSAGetLastError() ); WSACleanup(); return; } // 綁定socket sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr( "127.0.0.1" ); service.sin_port = htons( 27015 ); if ( bind( server, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) { printf( "bind() failed./n" ); closesocket(server); return; } // 監(jiān)聽 socket if ( listen( server, 1 ) == SOCKET_ERROR ) printf( "Error listening on socket./n"); // 接受連接 SOCKET AcceptSocket; printf( "Waiting for a client to connect.../n" ); while (1) { AcceptSocket = SOCKET_ERROR; while ( AcceptSocket == SOCKET_ERROR ) { AcceptSocket = accept( server, NULL, NULL ); } printf( "Client Connected./n"); server = AcceptSocket; break; } // 發(fā)送接受數(shù)據(jù) int bytesSent; int bytesRecv = SOCKET_ERROR; char sendbuf[32] = "Server: Sending Data."; char recvbuf[32] = ""; bytesRecv = recv( server, recvbuf, 32, 0 ); printf( "Bytes Recv: %ld/n", bytesRecv ); bytesSent = send( server, sendbuf, strlen(sendbuf), 0 ); printf( "Bytes Sent: %ld/n", bytesSent ); return; } 本程序僅僅描述了同步的情況!PS:本文轉自百度貼吧新紅盟吧 轉自:http://blog.csdn.net/LaoWu_/archive/2010/04/08/5461077.aspx關于gethostname函數(shù)失敗的問題調用gethostname之前, 要先調用WSAStartup才可以, 否則gethostname會失敗! 下面是正確的代碼 view plaincopy to clipboardprint?#include <stdio.h> #include <stdlib.h> #include <string.h> #include <Winsock2.h> #include <windows.h> #pragma comment(lib, "Ws2_32") int main() { WSADATA wsData; ::WSAStartup(MAKEWORD(2,2), &wsData); char szIP[32] = {0}; char szHostName[32] = {0}; int iResult = ::gethostname(szHostName, sizeof(szHostName)); if (iResult != 0) { printf("error/n"); return -1; } printf("%s/n", szHostName); hostent *pHost = ::gethostbyname(szHostName); ::WSACleanup(); return 0; }
轉自:http://blog.csdn.net/leesphone/archive/2008/03/02/2138775.aspx gethostbyname用法使用這個東西,首先要包含2個頭文件: #include <netdb.h> #include <sys/socket.h> struct hostent *gethostbyname(const char *name); 這個函數(shù)的傳入值是域名或者主機名,例如"www.google.com","wpc"等等。 傳出值,是一個hostent的結構(如下)。如果函數(shù)調用失敗,將返回NULL。 struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }; 解釋一下這個結構, 其中: char *h_name 表示的是主機的規(guī)范名。例如www.google.com的規(guī)范名其實是www.l.google.com。 char **h_aliases 表示的是主機的別名。www.google.com就是google他自己的別名。有的時候,有的主機可能有好幾個別名,這些,其實都是為了易于用戶記憶而為自己的網(wǎng)站多取的名字。 int h_addrtype 表示的是主機ip地址的類型,到底是ipv4(AF_INET),還是ipv6(AF_INET6) int h_length 表示的是主機ip地址的長度 int **h_addr_lisst 表示的是主機的ip地址,注意,這個是以網(wǎng)絡字節(jié)序存儲的。千萬不要直接用printf帶%s參數(shù)來打這個東西,會有問題的哇。所以到真正需要打印出這個IP的話,需要調用inet_ntop()。 const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) : 這個函數(shù),是將類型為af的網(wǎng)絡地址結構src,轉換成主機序的字符串形式,存放在長度為cnt的字符串中。 這個函數(shù),其實就是返回指向dst的一個指針。如果函數(shù)調用錯誤,返回值是NULL。 下面是例程,有詳細的注釋。 #include <netdb.h> #include <sys/socket.h> int main(int argc, char **argv) { char *ptr,**pptr; struct hostent *hptr; char str[32]; /* 取得命令后第一個參數(shù),即要解析的域名或主機名 */ ptr = argv[1]; /* 調用gethostbyname()。調用結果都存在hptr中 */ if( (hptr = gethostbyname(ptr) ) == NULL ) { printf("gethostbyname error for host:%s/n", ptr); return 0; /* 如果調用gethostbyname發(fā)生錯誤,返回1 */ } /* 將主機的規(guī)范名打出來 */ printf("official hostname:%s/n",hptr->h_name); /* 主機可能有多個別名,將所有別名分別打出來 */ for(pptr = hptr->h_aliases; *pptr != NULL; pptr++) printf(" alias:%s/n",*pptr); /* 根據(jù)地址類型,將地址打出來 */ switch(hptr->h_addrtype) { case AF_INET: case AF_INET6: pptr=hptr->h_addr_list; /* 將剛才得到的所有地址都打出來。其中調用了inet_ntop()函數(shù) */ for(;*pptr!=NULL;pptr++) printf(" address:%s/n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str))); break; default: printf("unknown address type/n"); break; } return 0; }
轉自:http://blog.csdn.net/chollima/archive/2010/06/04/5648065.aspx gethostname和gethostbyname,inet_ntoaview plaincopy to clipboardprint?// socketTest.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <WinSock2.h> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsData = { 0 }; int nRet = ::WSAStartup(MAKEWORD(2, 2), &wsData); char szBuf[MAX_PATH] = { 0 }; ::gethostname(szBuf, MAX_PATH);//獲取計算機名 struct hostent* pStHostent = NULL; strcpy(szBuf, "www.google.com"); pStHostent = ::gethostbyname(szBuf);//傳入域名或者主機名,得到指向host結構 /* pStHostent->h_name; //規(guī)范名 pStHostent->h_addrtype;//地址類型IPV4 or IPV6 pStHostent->h_addr_list;//ip地址 pStHostent->h_length; //ip地址長度 pStHostent->h_aliases;//主機別名 */ cout << pStHostent->h_name << endl; char** pptr = NULL; struct in_addr addr; switch(pStHostent->h_addrtype) { case AF_INET: case AF_INET6: pptr = pStHostent->h_addr_list; for (; *pptr != NULL; pptr++) { //addr.S_un.S_addr = *(u_long*)*pptr; addr.s_addr = *(u_long*)*pptr; //打印出IP地址列表 cout << inet_ntoa(addr) << endl; } break; default: cout << "UnKnow addr type!" << endl; } //打印出域名或者計算機名對應的別名列表 pptr = pStHostent->h_aliases; for (; *pptr != NULL; pptr++) { cout << *pptr << endl; } ::WSACleanup(); return 0; }
|