在網上找了很多的資料,現將這些資料整合起來,詳細介紹一下VC下的socket編程,並提供一個伺服器用戶端具體的執行個體。希望對您有所協助
一、原理部分
(個人覺得這篇寫的可以,所以轉與此,原文地址:http://dev.yesky.com/78/2683078.shtml)
在網路編程中最常用的方案便是Client/Server (客戶機/伺服器)模型。在這種方案中客戶應用程式向伺服器程式請求服務。一個服務程式通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務進程一 直處於休眠狀態,直到一個客戶向這個服務的地址提出了串連請求。在這個時刻,服務程式被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。
為了方便這種Client/Server模型的網路編程,90年代初,由Microsoft聯合了其他幾家公司共同制定了一套WINDOWS下的網路編 程介面,即Windows Sockets規範,它不是一種網路通訊協定,而是一套開放的、支援多種協議的Windows下的網路編程介面。現在的Winsock已經基本上實現了與協議 無關,你可以使用Winsock來調用多種協議的功能,但較常使用的是TCP/IP協議。Socket實際在電腦中提供了一個通訊連接埠,可以通過這個端 口與任何一個具有Socket介面的電腦通訊。應用程式在網路上傳輸,接收的資訊都通過這個Socket介面來實現。
微軟為 Visual C++定義了Winsock類如CAsyncSocket類和派生於CAsyncSocket 的CSocket類,它們簡單易用,讀者朋友當然可以使用這些類來實現自己的網路程式,但是為了更好的瞭解Winsock API編程技術,我們這裡探討怎樣使用底層的API函數實現簡單的 Winsock 網路應用程式設計,分別說明如何在Server端和Client端操作Socket,實現基於TCP/IP的資料傳送,最後給出相關的原始碼。
在VC中進行WINSOCK的API編程開發的時候,需要在項目中使用下面的三個檔案,否則會出現編譯錯誤。
1.WINSOCK.H: 這是WINSOCK API的標頭檔,需要包含在項目中。
2.WSOCK32.LIB: WINSOCK API串連庫檔案。在使用中,一定要把它作為項目的非預設的串連庫包含到專案檔中去。
3.WINSOCK.DLL: WINSOCK的動態串連庫,位於WINDOWS的安裝目錄下。
伺服器端操作 socket(通訊端)
1.在初始化階段調用WSAStartup()
此函數在應用程式中初始化Windows Sockets DLL ,只有此函數調用成功後,應用程式才可以再調用其他Windows Sockets DLL中的API函數。在程式中調用該函數的形式如下:WSAStartup((WORD)((1<<8|1), (LPWSADATA)&WSAData),其中(1<<8|1)表示我們用的是WinSocket1.1版本,WSAata用來存 儲系統傳回的關於WinSocket的資料。
2、建立Socket
初始化WinSock的動態串連庫後,需要在 伺服器端建立一個監聽的Socket,為此可以調用Socket()函數用來建立這個監聽的Socket,並定義此Socket所使用的通訊協定。此函數 調用成功返回Socket對象,失敗則返回INVALID_SOCKET(調用WSAGetLastError()可得知原因,所有WinSocket 的API函數都可以使用這個函數來擷取失敗的原因)。
SOCKET PASCAL FAR socket( int af, int type, int protocol )
參數: af:目前只提供 PF_INET(AF_INET);
type:Socket 的類型 (SOCK_STREAM、SOCK_DGRAM);
protocol:通訊協定(如果使用者不指定則設為0);
如果要建立的是遵從TCP/IP協議的socket,第二個參數type應為SOCK_STREAM,如為UDP(資料報)的socket,應為SOCK_DGRAM。
3、綁定連接埠
接下來要為伺服器端定義的這個監聽的Socket指定一個地址及連接埠(Port),這樣用戶端才知道待會要串連哪一個地址的哪個連接埠,為此我們要調用bind()函數,該函數調用成功返回0,否則返回SOCKET_ERROR。
int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR *name,int namelen );
參 數: s:Socket對象名;
name:Socket的地址值,這個地址必須是執行這個程式所在機器的IP地址;
namelen:name的長度;
如果使用者不在意地址或連接埠的值,那麼可以設定地址為INADDR_ANY,及Port為0,Windows Sockets 會自動將其設定適當之地址及Port (1024 到 5000之間的值)。此後可以調用getsockname()函數來獲知其被設定的值。
4、監聽
當伺服器端的Socket對象綁定完成之後,伺服器端必須建立一個監聽的隊列來接收用戶端的串連請求。listen()函數使伺服器端的Socket 進入監聽狀態,並設定可以建立的最大串連數(目前最大值限制為 5, 最小值為1)。該函數調用成功返回0,否則返回SOCKET_ERROR。
int PASCAL FAR listen( SOCKET s, int backlog );
參 數: s:需要建立監聽的Socket;
backlog:最大串連個數;
伺服器端的Socket調用完listen()後,如果此時用戶端調用connect()函數提出串連申請的話,Server 端必須再調用accept() 函數,這樣伺服器端和用戶端才算正式完成通訊程式的串連動作。為了知道什麼時候用戶端提出串連要求,從而伺服器端的Socket在恰當的時候調用 accept()函數完成串連的建立,我們就要使用WSAAsyncSelect()函數,讓系統主動來通知我們有用戶端提出串連請求了。該函數調用成功 返回0,否則返回SOCKET_ERROR。
int PASCAL FAR WSAAsyncSelect( SOCKET s, HWND hWnd,unsigned int wMsg, long lEvent );
參數: s:Socket 對象;
hWnd :接收訊息的視窗控制代碼;
wMsg:傳給視窗的訊息;
lEvent:被註冊的網路事件,也即是應用程式向視窗發送訊息的網路事件,該值為下列值FD_READ、FD_WRITE、FD_OOB、 FD_ACCEPT、FD_CONNECT、FD_CLOSE的組合,各個值的具體含意為FD_READ:希望在通訊端S收到資料時收到消 息;FD_WRITE:希望在通訊端S上可以發送資料時收到訊息;FD_ACCEPT:希望在通訊端S上收到串連請求時收到訊息;FD_CONNECT: 希望在通訊端S上串連成功時收到訊息;FD_CLOSE:希望在通訊端S上串連關閉時收到訊息;FD_OOB:希望在通訊端S上收到帶外資料時收到訊息。 具體應用時,wMsg應是在應用程式中定義的訊息名稱,而訊息結構中的lParam則為以上各種網路事件名稱。所以,可以在視窗處理自訂訊息函數中使用 以下結構來響應Socket的不同事件:
switch(lParam)
{
case FD_READ:
…
break;
case FD_WRITE:
…
break;
…
} |
5、伺服器端接受用戶端的串連請求
當Client提出串連請求時,Server 端hwnd視窗會收到Winsock Stack送來我們自訂的一個訊息,這時,我們可以分析lParam,然後調用相關的函數來處理此事件。為了使伺服器端接受用戶端的串連請求,就要使用 accept() 函數,該函數建立一Socket與用戶端的Socket相通,原先監聽之Socket繼續進入監聽狀態,等待他人的串連要求。該函數調用成功返回一個新產 生的Socket對象,否則返回INVALID_SOCKET。
SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr,int FAR *addrlen );
參數:s:Socket的識別碼;
addr:存放來串連的用戶端的地址;
addrlen:addr的長度
6、結束 socket 串連
結束伺服器和用戶端的通訊串連是很簡單的,這一過程可以由伺服器或客戶機的任一端啟動,只要調用closesocket()就可以了,而要關閉 Server端監聽狀態的socket,同樣也是利用此函數。另外,與程式啟動時調用WSAStartup()憨數相對應,程式結束前,需要調用 WSACleanup() 來通知Winsock Dll釋放Socket所佔用的資源。這兩個函數都是調用成功返回0,否則返回SOCKET_ERROR。
int PASCAL FAR closesocket( SOCKET s );
參數:s:Socket 的識別碼;
int PASCAL FAR WSACleanup( void );
參數: 無
(二)實現例子
伺服器端:
#include <stdio.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));// 綁定連接埠
listen(sockSrv,5);
SOCKADDR_IN addrClient;// 串連上的用戶端ip地址
int len=sizeof(SOCKADDR);
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);// 接受用戶端串連,擷取用戶端的ip地址
char sendBuf[50];
sprintf(sendBuf,"Welcome %s to here!",inet_ntoa(addrClient.sin_addr));// 組合訊息發送出去
send(sockConn,sendBuf,strlen(sendBuf)+1,0);// 發送訊息到用戶端
char recvBuf[50];
recv(sockConn,recvBuf,50,0);// 接受用戶端訊息
printf("%s\n",recvBuf);
//closesocket(sockConn);//中斷連線
}
}
用戶端代碼
#include <stdio.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
WORD wVersionRequested;
WSADATA wsaData;//WSAata用來儲存系統傳回的關於WinSocket的資料。
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);// AF_INET ..tcp串連
//初始化串連與連接埠號碼
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//本機地址,伺服器在本機開啟
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);// 設定連接埠號碼
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//串連伺服器
char recvBuf[50];
recv(sockClient,recvBuf,50,0);//接受資料
printf("%s\n",recvBuf);
send(sockClient,"hello",strlen("hello")+1,0);//發送資料
closesocket(sockClient);//關閉串連
WSACleanup();
}