天天看點

【轉】Win32下的socket程式設計

// socket.cpp : 定義控制台應用程式的入口點。
//
//伺服器端


//SOCKET連接配接過程
  //根據連接配接啟動的方式以及本地套接字要連接配接的目标,套接字之間的連接配接過程可以分為三個步驟:伺服器監聽,用戶端請求,連接配接确認。   
//伺服器監聽:是伺服器端套接字并不定位具體的用戶端套接字,而是處于等待連接配接的狀态,實時監控網絡狀态。  
//用戶端請求:是指由用戶端的套接字提出連接配接請求,要連接配接的目标是伺服器端的套接字。
           //為此,用戶端的套接字必須首先描述它要連接配接的伺服器的套接字,指出伺服器端套接字的位址和端口号,然後就向伺服器端套接字提出連接配接請求。   
//連接配接确認:是指當伺服器端套接字監聽到或者說接收到用戶端套接字的連接配接請求,它就響應用戶端套接字的請求,建立一個新的線程,把伺服器端套接字的描述發給用戶端,一旦用戶端确認了此描述,連接配接就建立好了。
              //而伺服器端套接字繼續處于監聽狀态,繼續接收其他用戶端套接字的連接配接請求。
  //如何開發一個Server-Client模型的程式


//開發原理:   
  //伺服器,使用ServerSocket監聽指定的端口,端口可以随意指定(由于1024以下的端口通常屬于保留端口,在一些作業系統中不可以随意使用,是以建議使用大于1024的端口),等待客戶連接配接請求,客戶連接配接後,會話産生;在完成會話後,關閉連接配接。  
  // 用戶端,使用Socket對網絡上某一個伺服器的某一個端口發出連接配接請求,一旦連接配接成功,打開會話;會話完成後,關閉Socket。用戶端不需要指定打開的端口,通常臨時的、動态的配置設定一個1024以上的端口。  
  // Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程式員可以用它們來開發TCP/IP網絡上的應用程式。
    //要學Internet上的TCP/IP網絡程式設計,必須了解Socket接口。Socket接口設計者最先是将接口放在Unix作業系統裡面的。如果了解Unix系統的輸入和輸出的話,就很容易了解Socket了。
  //網絡的Socket資料傳輸是一種特殊的I/O,Socket也是一種檔案描述符。
  //Socket也具有一個類似于打開檔案的函數調用Socket(),該函數傳回一個整型的Socket描述符,随後的連接配接建立、資料傳輸等操作都是通過該Socket實作的。

//常用的Socket類型
  //有兩種:流式Socket(SOCK_STREAM)和資料報式Socket(SOCK_DGRAM)。
    //流式是一種面向連接配接的Socket,針對于面向連接配接的TCP服務應用;
    //資料報式Socket是一種無連接配接的Socket,對應于無連接配接的UDP服務應用。

//Socket 阻塞與非阻塞模式

//Windows套接字在阻塞和非阻塞兩種模式下執行I/O操作。
//在阻塞模式下,在I/O操作完成前,執行的操作函數一直等候而不會立即傳回,該函數所在的線程會阻塞在這裡。
//相反,在非阻塞模式下,套接字函數會立即傳回,而不管I/O是否完成,該函數所在的線程會繼續運作。
#pragma comment(lib, "Ws2_32.lib")
#include "stdafx.h"
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <WS2tcpip.h>
#include <process.h>

using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    WORD wVersionRequested;//typedef unsigned short WORD
    WSADATA wsaData;//這個結構被用來存儲 被WSAStartup函數調用後傳回的 Windows Sockets資料
    int err;

    wVersionRequested = MAKEWORD(,);
//     WORD MAKEWORD(
//         BYTE bLow,
//         BYTE bHigh
//         );
     err = WSAStartup(wVersionRequested,&wsaData);//WSAStartup,即WSA(Windows SocKNDs Asynchronous,Windows異步套接字)的啟動指令。
     //是Windows下的網絡程式設計接口軟體Winsock1 或 Winsock2 裡面的一個指令
     //這個函數是用來加載Winsocket DLL,wVersionRequested是用來存儲你所要申請的Winsocket DLL版本,
     //可以通過MAKEWORD函數擷取,wVersionRequested的高位代表副版本号,低位代表高版本号
     //     int WSAStartup(
     //         __in          WORD wVersionRequested,
     //         __out         LPWSADATA lpWSAData
     //         );

     if(err != )//傳回0表示成功
     {
         return ;
     }
     if(LOBYTE(wsaData.wVersion) !=  || HIBYTE(wsaData.wVersion) != )//高位位元組指出副版本(修正)号,低位位元組指明主版本号。/* Confirm that the Windows Sockets DLL supports 1.1.*/ 
     {
         /* Tell the user that we couldn't find a useable  winsock.dll. */ 
         WSACleanup();//終止Winsock 2 DLL (Ws2_32.dll) 的使用.
         return ;
     }
     SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,);
     //int socket(int domain, int type, int protocol); 該函數如果調用成功就傳回新建立的套接字的描述符,如果失敗就傳回INVALID_SOCKET
     SOCKADDR_IN addrSrv;//sockaddr_in和sockaddr是并列的結構
//      struct sockaddr_in 
//      {
//            short int sin_family; /* Address family */  
//             unsigned short int sin_port; /* Port number */   
//             struct in_addr sin_addr; /* Internet address */   
//             unsigned char sin_zero[8]; /* Same size as struct sockaddr */  
//       }; 
     //該結構體用于指定一個socket的一端【ip+port】
     addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
     //是以local.sin_addr.s_addr是ip位址。作為伺服器,你要綁定【bind】到本地的IP位址上進行監聽【listen】,
     //但是你的機器上可能有多塊網卡,也就有多個IP位址,這時候你要選擇綁定在哪個IP上面,如果指定為INADDR_ANY,那麼系統将綁定預設的網卡【即IP位址】。
     //作為用戶端,你要連接配接【connect】到遠端的伺服器,也是要指定遠端伺服器的(ip, port)對。
     //當然,在這種情況下,不可能将IP位址指定為INADDR_ANY,系統會瘋掉的。
     addrSrv.sin_family = AF_INET;
     //Winsock2.h中   #define AF_INET 0   #define PF_INET AF_INET 
     //AF 表示ADDRESS FAMILY 位址族   
     //PF 表示PROTOCOL FAMILY 協定族   
        // 但這兩個宏定義是一樣的   
         //是以使用哪個都沒有關系 
     addrSrv.sin_port = htons();
     //htons 是把你機器上的整數轉換成“網絡位元組序”, 網絡位元組序是 big-endian,也就是整數的高位位元組存放在記憶體的低位址處
     bind(sockSrv,(SOCKADDR*) &addrSrv,sizeof(SOCKADDR));
     //将套接字綁定于特定位址的特定端口,其中第二個參數可以使用SOCKADDR_IN來代替。
//      int bind(
//          SOCKET s,
//          const struct sockaddr FAR* name,
//          int namelen
//          );
     //參數說明:   socket:是一個套接字。   
     //address:是一個sockaddr結構指針,該結構中包含了要結合的位址和端口号。  
     // address_len:确定address緩沖區的長度。


     listen(sockSrv,);//這個函數一般用于伺服器端,這裡的第二個參數為請求隊列的最大程度,注意,不是最大連接配接數目
//      int listen(
//          __in          SOCKET s,
//          __in          int backlog
//          );


     SOCKADDR_IN addrClient;//與sockaddr等價的資料結構
     int len = sizeof(SOCKADDR);
//      struct sockaddr
//      {
//          unsignedshort sa_family;
//          char sa_data[14];
//      };

    while()
    {
        SOCKET sockConn = accept(sockSrv,(SOCKADDR *) &addrClient,&len);//從連接配接請求隊列中獲得連接配接資訊,建立新的套接字,并傳回該套接字的檔案描述符。新建立的套接字用于伺服器與客戶機的通信,而原來的套接字仍然處于監聽狀态。 

        //accept一樣主要用于伺服器端,第二個參數同樣可以使用SOCKADDR_IN來替代,但是注意,這裡,該參數是用來存儲建立連接配接時候用戶端的相關資訊。

//         SOCKET accept(
//             SOCKET s,
//         struct sockaddr FAR* addr,
//             int FAR* addrlen
//             );
        //服務程式調用accept函數從處于監聽狀态的流套接字s的客戶連接配接請求隊列中取出排在最前的一個客戶請求,
        //并且建立一個新的套接字來與客戶套接字建立連接配接通道,如果連接配接成功,就傳回新建立的套接字的描述符,
        //以後與客戶套接字交換資料的是新建立的套接字;如果失敗就傳回 INVALID_SOCKET。
        //該函數的第一個參數指定處于監聽狀态的流套接字;
        //作業系統利用第二個參數來傳回所連接配接的客戶程序的協定位址(由cliaddr指針所指);
        //作業系統利用第三個參數來傳回該位址(參數二)的大小。
        //如果我們對客戶協定位址不感興趣,那麼可以把cliaddr和addrlen均置為空指針NULL。 
        char sendbuffer[];
        sprintf(sendbuffer,"welcome %s here",inet_ntoa(addrClient.sin_addr));//将一個IP轉換成一個網際網路标準點分格式的字元串。 原型:char FAR * inet_ntoa( struct in_addr in); 
        send(sockConn,sendbuffer,strlen(sendbuffer)+,);//該函數用來互相發送資料,但是需要注意的是,伺服器端使用該函數時候,第一個參數為accept函數所傳回的socket結構值。
//         int send(
//             SOCKET s,
//             const char FAR* buf,
//             int len,
//             int flags
//             );
        char recvchar[];
        recv(sockConn,recvchar,,);//本函數用于已連接配接的資料報或流式套接口s進行資料的接收
        printf("%s\n",recvchar);
        closesocket(sockConn);//本函數關閉一個套接口。更确切地說,它釋放套接口描述字s,以後對s的通路均以WSAENOTSOCK錯誤傳回。
    }
    return ;
}
           
// socket_client.cpp : 定義控制台應用程式的入口點。
//
#pragma comment(lib, "Ws2_32.lib")
#include "stdafx.h"
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <WS2tcpip.h>
#include <process.h>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{

    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(,);
    err = WSAStartup(wVersionRequested,&wsaData);//加載Winsocket DLL
    if(err != )
    {
        return ;
    }

    if(LOBYTE(wsaData.wVersion) !=  || HIBYTE(wsaData.wVersion) != )
    {
        WSACleanup();
        return ;
    }

    SOCKET sockClient = socket(AF_INET,SOCK_STREAM,);//建立套接字
    SOCKADDR_IN addrSrv;//socketAddress socket端口
    //伺服器端口配置
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    作為用戶端,你要連接配接【connect】到遠端的伺服器,也是要指定遠端伺服器的(ip, port)對。
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons();

    connect(sockClient,(SOCKADDR *) &addrSrv,sizeof(SOCKADDR));

    char recvBuffer[];
    recv(sockClient,recvBuffer,,);//接收伺服器資料,存入recvBuffer
    printf("%s\n",recvBuffer);//列印伺服器資料
    send(sockClient,"This is Kary",strlen("This is Kary")+,);//向伺服器發送資料"This is Kary"
    closesocket(sockClient);
    WSACleanup();
    scanf("%d",&err);
    return ;
}