VC++網絡編程

一、Windows Sockets API簡介
VC++對網絡編程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP網絡環境裏,也是Internet上進行開發最爲通用的API。最早美國加州大學Berkeley分校在UNIX下爲TCP/IP協議開發了一個API,這個API就是著名的Berkeley Socket接口(套接字)。
在桌面操作系統進入Windows時代後,仍然繼承了Socket方法。在TCP/IP網絡通信環境下,Socket數據傳輸是一種特殊的I/O,它也相當於一種文件描述符,具有一個類似於打開文件的函數調用-socket()。

可以這樣理解:Socket實際上是一個通信端點,通過它,用戶的Socket程序可以通過網絡和其他的Socket應用程序通信。Socket存在於一個“通信域”(爲描述一般的線程如何通過Socket進行通信而引入的一種抽象概念)裏,並且與另一個域的Socket交換數據。Socket有三類。第一種是SOCK_STREAM(流式),提供面向連接的可靠的通信服務,比如telnet,http。第二種是SOCK_DGRAM(數據報),提供無連接不可靠的通信,比如UDP。第三種是SOCK_RAW(原始),主要用於協議的開發和測試,支持通信底層操作,比如對IP和ICMP的直接訪問。
二、Windows Socket機制分析
2.1一些基本的Socket系統調用     主要的系統調用包括:socket()-創建Socket;bind()-將創建的Socket與本地端口綁定;connect()與accept()- 建立Socket連接;listen()-服務器監聽是否有連接請求;send()-數據的可控緩衝發送;recv()-可控緩衝接收;closesocket()-關閉Socket。
2.2Windows Socket的啓動與終止   啓動函數WSAStartup()建立與Windows Sockets DLL的連接,終止函數WSAClearup()終止使用該DLL,這兩個函數必須成對使用。
2.3異步選擇機制  Windows是一個非搶佔式的操作系統,而不採取UNIX的阻塞機制。當一個通信事件產生時,操作系統要根據設置選擇是否對該事件加以處理,WSAAsyncSelect()函數就是用來選擇系統所要處理的相應事件。當Socket收到設定的網絡事件中的一個時,會給程序窗口一個消息,這個消息裏會指定產生網絡事件的Socket,發生的事件類型和錯誤碼。
2.4異步數據傳輸機制   WSAAsyncSelect()設定了Socket上的須響應通信事件後,每發生一個這樣的事件就會產生一個WM_SOCKET消息傳給窗口。而在窗口的回調函數中就應該添加相應的數據傳輸處理代碼。
三、WinSock基本定義:
WinSock是Microsoft Windows Socket的簡稱,WinSock爲軟件開發人員提供一套Windows操作系統上的開放的、支持多種協議的網絡編程接口,已成爲在TCP/IP網絡編程和Internet應用開發中通用的解決方案。 WinSock對一些重要的數據類型和結構做了如下定義。
1、套接字的定義
typedef unsigned int u_int;
     typedef u_int Socket;
2、基本數據類型的定義
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
3、網絡地址的數據結構
網絡地址用無符號長整數unsigned long 表示。例如IP地址127.0.0.1可以表示爲:
#define INADDR_LOOPBACK 0x7f000001
用inet_addr()函數可以將分16位IP地址轉換爲unsigned long 類型,其定義爲:
unsigned long inet_addr(const char FAR* cp)
Cp爲點分16位IP地址,如“192.1.8.84”。
4、套接字地址結構
sockaddr結構——通用Socket地址結構,其定義爲:
struct sockaddr{u_short sa_family;char sa_data[14];};
其中sa_family爲網絡地址類型,隨協議的不同而不同,一般爲AF_INET,表示該socket在Internet域中通信。
sockaddr_in結構——專門針對Internet域的Socket地址結構,其定義爲:
struct sockaddr_in
{
     short sin_family;
     u_short sin_port;
     struct in_addr sin_addr;
    char sin_zero[8];
};
其中sin_family必須設定爲AF_INET;sin_port爲服務器端口,如果端口設置爲0,則系統會自動分配一個唯一的端口;sin_addr爲一個unsigned long 的IP地址,若sin_addr爲INADDR_ANY,則表示所有的IP地址;sin_zero爲填充字段,用來保證結構的大小;
5、主機地址
struct hostent
{
     char FAR* h_name;                //主機名字
     char FAR*FAR* h_aliases;     //主機別名列表
     short h_addrtype;                    //地址類型
     short h_length;                        //地址長度
    char FAR*FAR* h_addr_list;    //IP地址
    #define h_addr h_addr_list[0];
};
6、常見TCP/IP協議的定義
#define IPPROTO_IP          0
#define IPPROTO_ICMP    1
#define IPPROTO_IGMP    2
#define IPPROTO_TCP      6
#define IPPROTO_UDP     17
#define IPPROTO_RAW    255
四、WinSock基本函數:
以下介紹幾個最基本的WinSock函數,用這些函數就可以完成從客戶端到服務器端的套接字編程。其基本流程如下圖所示:
基於(TCP/IP面向連接)的socket編程:
服務器端程序: 1.創建套接字(socket)。 2.將套接字綁定到一個本地地址和端口上(bind) 3.將套接字設爲監聽模式,準備接受客戶請求(listen) 4.等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。 5.用返回的套接字和客戶端進行通信(send/recv)。 6.返回,等待另外客戶請求。 7.關閉套接字。
客戶端程序: 1.創建套接字(socket)。 2.向服務器發出連接請求(connect)。 3.和服務器端進行通信(send/recv)。 4.關閉套接字。
基於UDP(面向無連接)的socket編程 :
服務器端(接收端)程序: 1.創建套接字(socket)。 2.將套接字綁定到一個本地地址和端口上(bind)。 3.等待接受數據(recvfrom)。 4.關閉套接字。
客戶端(發送端)程序: 1.創建套接字(socket)。 2.向服務器發送數據(sendto)。 3.關閉套接字。
1、socket()
功能:創建一個新的套接字。
原形:Socket PASCAL FAR socket(int af,int type,int      protocol);
參數:af——通信發生的區域;
               type——要建立的套接字類型;
              procotol——使用的協議類型。

2、Bind()
功能:爲一個新創建未綁定的Socket分配端口和local地址。對客戶機/服務器構架的程序,服務器必須使用此函數爲Socket分配端口號,客戶機不用綁定,而是由系統自動分配。
原形:int PASCAL FAR Bind(Socket s,
                     const struct sockaddr FAR* name,int namelen);
參數:s——由socket()調用返回的並且未作連接的Socket識    別符。Name——WinSock地址結構,因協議類型而不同,對應TCP/IP協議的結構爲 sockaddr_in。namelen——表示name的長度。
3、connect()和accept()
功能:用於在客戶機/服務器構架的程序中建立套接字連接,客戶端用connect()要求連接某一流式套接字到指定服務器,服務器用accept()完成套接字中服務器端Socket,接受客戶端的連接請求。
原形:int PASCAL FAR connect(Socket s,
                     const struct sockaddr FAR* name,int namelen);
               Socket PASCAL FAR accept(Socket s,
                    struct sockaddr FAR* addr,int FAR *addrlen);
參數:name——服務器端Socket將要建立連接的客戶端地址; namelen——表示name的長度。addr——要求與服務器端Socket建立連接的客戶端地址;addrlen——addr的長度。
返回值:connect若成功返回0,否則返回SOCKET_ERROR;
                   accept返回的Socket是與客戶端通信的Socket。
4、listen()
功能:設定Socket狀態爲監聽,監聽客戶端的請求,準備被連接。
原形: int PASCAL FAR listen(Socket s,int backlog);
參數:backlog——未完成連接之前,客戶端連接請求的最大數。
返回值:若成功則返回0,否則返回SOCKET_ERROR。
5、send()與recv()
功能:在已建立連接的流式套接字之間收發數據, send()發送數據, recv()接收數據。
原形:int PASCAL FAR send(Socket s,
                     const char FAR* buf,int len,int flags);
               int PASCAL FAR recv(Socket s,
                     const char FAR* buf,int len,int flags);
參數:s——發送或接受數據的套接字。
               buf——發送或接受數據的緩衝區。
               len——buf的長度;
               flag——函數被調用的方式,其值爲0,MSG_PEEK
                           或MSG_OOB.
6、 sendto()與recvfrom()
功能:若前圖建立的不是流式套接字的話,就要用sendto()與recvfrom()代替send()與recv()來讀寫數據。功能與send()與recv()相同。
原形:int sendto(Socket s,const char FAR* buf,int len,
                    int flags,const struct sockaddr FAR* to,int tolen);
               int recvfrom(Socket s, char FAR* buf,int len,
                  int flags, struct sockaddr FAR* from,int FAR* fromlen);
參數:to——指向目標套接字地址的指針(可選);
               tolen——to的長度;
               from——指向保存着返回的源地址的緩衝區的指針
                            (可選);
               fromlen——指向from緩衝區的指針(可選)。
返回值:若成功返回發送或接受到的數據長度,否則返回
                   SOCKET_ERROR。
7、closesocket()
功能:關閉套接字;
原形: BOOL PASCAL FAR closesocket(Socket s);
參數:s——要關閉的套接字
返回值:若成功則返回0,否則返回SOCKET_ERROR。
五、WinSock擴展:
WinSock對最早的Socket進行了一些擴展,主要是增加了符合Windows消息驅動特性的網絡事件異步選擇機制,擴充了一些異步函數。主要異步函數如下:
1、異步選擇函數
功能:用來註冊應用程序相關的網絡事件,當事件發生時,應用程序窗口函數將接收到一個消息。
原形:int PASCAL FAR WSAAsyncSelect(
                      Socket s,                 
                      HWND hWnd,             //窗口句柄
                     Unsigned int wMsg,   //需要發送的消息
                     Long lEvent );            //事件類型
     事件類型的值可以是:
      FD_READ:當套接字收到數據時接到通知;
      FD_WRITE:當套接字可發送數據時接到通知;
      FD_OOB:當套接字有外來數據到達時接到通知;
      FD_ACCEPT:當套接字有外來連接時接到通知;
FD_CONNECT:當套接字連接建立完成時接到通知;
          FD_CLOSE:當套接字關閉時接到通知;
2、異步請求函數WSAAsyncGetXByY()
3、阻塞處理方法
當一個應用程序的套接字調用處於阻塞時,應能夠使套接字放棄CPU讓其他應用程序運行。WinSock的阻塞處理爲:DLL初始化->循環操作。在循環中發送任何Windows消息,並檢查此WinSock調用是否完成,必要時可以放棄CPU讓其他應用程序執行。可以調用 WSACancelBlockingCall()函數取消此阻塞處理。
在WinSock中,默認的阻塞處理例程BlockingHook()能夠獲取併發送Windows消息。如果要對複雜的程序進行處理, WinSock中的WSASetBlockingHook()使得用戶能夠安裝自己的阻塞處理例程;與該函數相對應的則是 WSAUnhookBlockingHook(),用於刪除先前安裝的任何阻塞處理例程,並重新安裝默認的處理例程。應注意,設計自己的阻塞處理例程時,除了WSACancelBlockingCall()以外不能使用其他的WinSock API函數。調用WSACancelBlockingCall()函數將取消處於阻塞的操作,結束阻塞循環。
4、出錯處理
    WinSock爲了和多線程環境兼容,提供了WSAGetLastError()和WSASetLastError()兩個出錯處理函數來獲取和設置當前線程的最近錯誤代碼。
     WSAGetLastError()函數的功能是返回故障類型,其定義爲:int WSAGetLastError(void);
5、啓動與終止
    使用WSAStartup()和WSACleanup()啓動和終止套接字。
WSAStartup()完成與Winsock.DLL的連接;
WSACleanup()結束對Winsock.DLL的調用。
六、WinSock編程:
WinSock包括開發組件和運行組件兩大部分,開發組件包括WinSock實現文檔、應用程序接口(API)引入庫和一些頭文件。運行組件包括 WinSock應用程序接口的動態連接庫。在VC++6.0中使用WinSock編程時,需要將以下三個文件包含到項目中(以WinSock V2.0爲例)
頭文件: Winsock2.h
庫文件:WS2_32.LIB
動態庫: WS2_32.DLL
    在TCP/IP網絡中兩個主機進行進程間異步通信時採用客戶端/服務器模式。其中服務器工作過程如下:
1、服務器方先開啓,打開一通信通道,公佈接收客戶請求的端口地址;
2、等待客戶端發送請求到該端口;
3、接收到客戶端服務請求,處理該請求併發送應答信號;
4、重複2、3步,處理多個客戶請求;
5、關閉服務器。
客戶端工作過程如下:
1、打開一通信通道,連接到服務器所在主機的開放端口;
2、向服務器發送服務請求,等待應答信號;
3、收到應答信號後,關閉通信通道。
下面看一看WinSock網絡應用程序的詳細步驟。
1、啓動與終止:
    必須首先使用啓動函數WSAStartup(),它指定WinSock API的版本,並獲得Socket技術細節。調用方式爲:
         WORD wVersionRequested; //定義版本信息變量
         WSADATA wsaData;              //定義數據信息變量
         int err;                                      //定義錯誤號變量
        wVersionRequested=MAKEWORD(1,1);
        //給版本信息賦值
        err= WSAStartup(wVersionRequested,&wsaData);
       //獲取錯誤信息
        if(err!=0) return;
    程序可多次調用WSAStartup()函數,但每次調用時wVersionRequested必須相同。
2、創建套接字:
    Socket sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
     //流式套接字
     if(sock==INVALID_Socket)   //錯誤處理
           …..
3、套接字的綁定:
     sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(0);                                    //保證字節順序
    addr.sin_addr.s_addr=addr(“192.168.12.84”);   //指定地址
    int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
4、服務器端套接字的監聽:
    int nResult=listen(s,5);
    //s是已綁定但未連接的套接字,最多監聽5個連接
5、服務器端套接字等待連接:
     sockaddr_in addr;
     Socket s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
6、客戶端將兩個套接字連接起來準備通信:
     sockaddr_in addr;
    addr.sin_family=AF_INET;
     addr.sin_port=htons(0);               //保證字節順序
     addr.sin_addr.s_addr=htonl(INADDR_ANY);
     int nResult=connect(s, (sockaddr*)&addr,sizeof(sockaddr));
7、服務器端向客戶端套接字發送數據:
     char buf[ ]=“einsun”; //指定緩衝區
     int nResult=send(s,buf,strlen(buf));
8、客戶端套接字接收數據:
    char mess[1000];
    int nResult=recv(s,mess,1000,0);
9、服務器端和客戶端中斷套接字連接,通知服務器端或客戶端停止接收和發送數據:
     int nResult=shutdown(s,SD_BOTH);
10、服務器端或客戶端關閉套接字,釋放所佔有的資源:
    int nResult=closesocket(s);//s爲要關閉的套接字
    關閉套接字時,所有已經打開並連接的流式套接字將被複位,但那些已經由closesocket()函數關閉,但仍有未發送數據的套接字不受影響,仍然可發送未發完的數據。
11、出錯處理
    出錯處理函數WSAGetLastError()的調用方式如下:
    len=send(s,lpBuffer,len,0);
    if((len==socket_ERROR)
          &&(WSAGetLastError()==WSAWOULDBLOCK))
    {
         …….
     }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章