[windows網絡編程]tcp/udp編程初步詳解


       如你所知,簡單的網絡編程就是稱為客戶端和服務器的兩台主機進行通信。顯然通信雙方要有一個統一的標識,電話機的比方就很好。這個標識不僅僅是IP地址或者端口號,我們可以將二者結合起來。稱之為套接字,socket。在網絡編程中socket無疑是關鍵的部分,因此網絡編程也常常被叫做socket編程。

       叫什么不重要,重要的是原理。本文的目的也正是這樣,我們試圖搞懂它。

       為了減少篇幅,關於協議,TCP,UDP,ISO七層模型等,這些基礎知識這里就不做說明了。我們采用的是客戶/服務器的模式。又客戶對服務器做出通信請求,而服務器對其響應。另外本文的所有程序都可以在vc6.0中實現。

       首先我們先大體文字介紹一下雙方的通信流程。比如服務器端:

       1,打開通信通道並告知本地主機,服務器端可在某一地址和端口接收客戶端請求。

       2,等待客戶請求到達該端口。

       3,接收到重復的服務請求,處理並發送應答信號。如果是並發的請求,那么就啟動一個新進程。服務完成后,關閉此進程與客戶的通信鏈路,並終止。

       4,返回第二步,也就是繼續等待新的請求

       5,關閉服務器

      客戶端所做的要更為簡單

       1,打開一個通信通道,並連接到服務器所在主機的特定端口

       2,向服務器端發送請求報文,等待並接受應答,然后可繼續提出請求  

       3,通信結束,關閉通信通道並終止

       為了更接近程序實現,可以吧流程簡化一下。當然TCP和UDP的實現方法略有不同。事實上windows socket程序只是在伯克利的基礎上加了一下異步函數,和符合wondows消息驅動特性的網絡事件異步選擇機制。根據TCP和UDP的協議遠離的區別,可以分為流式套接字(SOCK_STREAM)和數據包套接字(SOCK_DGRAM)

       流式套接字服務器端可分為七步

       1,創建套接字(socket函數)

       2,將套接字綁定到指定端口上(bind函數)

       3,將套接字設定為監聽模式,准備接受客戶請求(listen函數)

       4,等待客戶請求的到來,當請求到來后接收連接請求,返回一個新的對應於此連接的套接字(accept函數)

       5,用返回的套接字和客戶進行通信(send/recv函數)

       6,返回,等待一個新的客戶請求

       7,關閉套接字(closesocket函數)

       客戶端程序可分為四步

        1,創建套接字

        2,向服務器發出鏈接請求(connect函數)

        3, 和服務器進行通信(send/recv函數)

        4, 關閉套接字

        基於UDP的套接字服務器端稍有不同,創建,綁定,接收,關閉即可因為是無連接的少去了監聽和等待的步驟。同樣在客戶端也少去了請求連接的過程。

        TCP代碼實現:首先要建兩個工程,不妨設為tcpsrv和tcpclient,分別為客戶端和服務器端

        tcpsrv.cpp

       

/*第一步:加載套接字庫 WSAstartup函數,另外也可對套接字庫進行版本協商,兩個參數,wVersionRequested指定請求的版本號,需要注意的是高字節指定副版本,低字節指定主版本。第二個參數是一個指向wsadata的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中,接收windows socket */
    WORD wVersionRequested;//定義一個word類型的變量
    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( );//終止對winsock庫的使用
        return; 
    }


/* 1.創建套接字,socket函數,返回一個套接字的描述符,三個參數,一個參數af指定地址族,第二個參數指定套接字的類型也就是TCP或UDP,第三個參數是與特定的地址家族相關的協議,如果指定為0,那么他就根據地址格式和套接字的類別, 自動為你設置一個合適的協議 如果該函數調用成功,他將返回一個新的SOCKET數據類型的套接字描述符。如果失敗則返回一個 INVALID_SOCKET錯誤信息通過WSAGetLastID_SOCKET函數返回。  */
      

      SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);

/*2.綁定套接字函數bind,接收三個參數,第一個指定要綁定的套接字,第二個參數指定該套接字的本地地址信息,是一個指向sockaddr
結構的指針變量,由於地址結構是為所有地址家族准備的,這個結構可能隨所擁有網絡協議不同而不同。所以要用第三個參數指定該地址結構的長度,顯然要事先定義sockaddr結構體。
另外,因為實際要求的是內存區,所以對於不同的協議家族,用不同的結構來替換。常用的兩個函數
inet_addr(),將點十進制的IP地址轉換為適合分配給s_addr的u_long類型的數值
inet_ntoa()函數起相反的作用*/

      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));

    
//4.將套接字設置為監聽模式listen函數,第一個參數指定套機字,第二個參數設置等待連接隊列的長度,此處設為5
      listen(socaksrv,5);

/*5.設置循環,不斷地連接請求的到來,
   首先定義一個地址結構體變量來接受客戶的地質結構信息。然后定一個整形變量存儲地址長度,為防止調用失敗設置一個初始值

*/


     SOCKADDR_IN addrClient;
     int len=sizeof(SOCKADDR);

/*
    6.在while循環中,用一個函數來等待客戶的連接到來並接受客戶的連接請求。accept函數,成功后會返回一個新的連接套接字的描述符,而原來的套接字則繼續監聽客戶的連接請求

*/

     while(1)
   {
        SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);

       /*7.進行通信,向客戶端發送數據,send函數

       */
        char sendBuf[100];
        sprintf(sendBuf,"welcome %s to MFC",inet_ntoa(addrClient.sin_addr));//ip地址
        send(sockConn,sendBuf,strlen(sendBuf)+1,0);
        /*8.同時可以從客戶端接受數據用recv函數*/
         
        
        char recvBuf[100];
        recv(sockConn,recvBuf,100,0);
        printf("%s\n",recvBuf);//打印接收到的數據


        //9.關閉套接字


          closesocket(sockConn);
      

   }

     

 

         相應的客戶端程序,tcpclient.cpp

          

void main()
{
    WORD wVersionRequested;//定義一個word類型的變量
    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 sockClient=socket(AF_INET,SOCK_STREAM,0);
    //第二步,因為客戶端不需要綁定,可直接去連接服務器端, //connect函數
        
        SOCKADDR_IN addrsrv;
        addrsrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1")
        //127.0.0.1因為只在本機測試,故此處用本機的IP地址
        addrsrv.addr_family=AF_INET;
        addrsrv.addr_port=htons(6000);
        connect(sockClient,(SOCKADDR*)&addrsrv,sizeof(SOCKADDR));



       //第三步接收數據


       char recvBuf[100];
       recv(sockClient,recvBuf,100,0);
       printf("%s\n",recvBuf);//將接受到的數據打印出來
       send(sockClient,"this is client",strlen("this is client")+1,0);


        //第四步 關閉套接字
        closesocket(sockClient);

        WSAClenup();
}

 

          UDP的相對來說比較簡單

          客戶端:

void main()
{
    WORD wVersionRequested;//定義一個word類型的變量
    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 sockClient=socket(AF_INET,SOCK_DGRAM,0);
    //第二步直接發送數據 用sendto函數
    /*
    int sendto(  
    SOCKET s,                          
    const char FAR *buf,            
    int len,                           
    int flags,                       
    const struct sockaddr FAR *to,  設定目的套接字的地址結構體信息  
    int tolen        地址結構體長度                );
    
    
    */
    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);



    sendto(sockClient,"hello",strlen("hello")+1,0,
        (SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
    closesocket(sockClient);
    WSACleanup();


}

      

 

       服務器端:

        

void main()
{
    WORD wVersionRequested;//定義一個word類型的變量
    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_DGRAM,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));
    /*接收數據 此處與TCP不同
    int recvfrom(  
    SOCKET s,                     
    char FAR* buf,  接收數據的buf            
    int len,                     長度 
    int flags,                  0
    struct sockaddr FAR *from,   地址結構體指針,表明是一個返回izhi 
    int FAR *fromlen      設置初始值,並且有返回值      );
    
    
    
    首先定義地址結構的變量    
    */
    SOCKADDR_IN addrClient;
    int len=sizeof(SOCKADDR);
    char recvBuf[100];


    recvfrom(socksrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
    printf("%s \n",recvBuf);
    closesocket(socksrv);
    WSACleanup();


}

       另外加貼一個具有聊天功能的小程序,udp實現

       服務器端:

       

void main()
{
    WORD wVersionRequested;//定義一個word類型的變量
    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_DGRAM,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));
    
    char recvBuf[100];
    char sendBuf[100];
    char tempBuf[200];


    SOCKADDR_IN addrClient;

    int len=sizeof(SOCKADDR);
    while(1)
    {
        recvfrom(socksrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
        if('q'==recvBuf[0])//判斷如果第一個字符是Q則退出
        {
            sendto(socksrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
            printf("chat end !\n");
            break;
        }
        sprintf(tempBuf,"%s say:%s",inet_ntoa(addrClient.sin_addr),recvBuf);
        //如果不是Q則將客戶端的IP地址和發送的數據格式化完放到tempBUf中
        printf("%s\n",tempBuf);
        printf("please input data:\n");
        //等待用戶輸入
        gets(sendBuf);//從標准輸入流中獲取一行數據
        sendto(socksrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);
        //將用戶輸入的數據發送到客戶端
    }
     closesocket(socksrv);
     WSACleanup();


}

 

       客戶端:

         

void main()
{
    WORD wVersionRequested;//定義一個word類型的變量
    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 sockClient=socket(AF_INET,SOCK_DGRAM,0);
    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);


    char recvBuf[100];
    char sendBuf[100];
    char tempBuf[200];


    int len=sizeof(SOCKADDR);
    while(1)
    {
        printf("please input data:\n");
        gets(sendBuf);
        sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,
            (SOCKADDR*)&addrsrv,len);


        //等待服務器端的回應
        recvfrom(sockClient,recvBuf,100,0,
            (SOCKADDR*)&addrsrv,&len);
        if('q'==recvBuf[0])
        {
            sendto(sockClient,"q",strlen("q")+1,0,
                (SOCKADDR*)&addrsrv,len);
            printf("chat end!\n");
            break;
        
        
        
        }
        sprintf(tempBuf,"%s say :%s",inet_ntoa(addrsrv.sin_addr),recvBuf);
        printf("%s\n",tempBuf);
    
    
    
    
    }
     closesocket(sockClient);
     WSACleanup();
}

 

         

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM