windows socket TCP/UDP代碼實現


SOCKET網絡編程

(WINDOWS SOCKET)

1.前言

網上看了很多Socket的資料,將理解的知識總結下,詳細介紹下VCwindows sockets編程,並結合服務器和客戶端的兩個實例(TCP/UDP)講解下。

2.SOCKET相關原理

在網絡編程中最常用的方案便是Client/Server (客戶機/服務器)模型。在這種方案中客戶應用程序向服務器程序請求服務。一個服務程序通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務進程一直處於休眠狀態(簡單的說就是死循環),直到一個客戶向這個服務的地址提出了連接請求。在這個時刻,服務程序被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。

VC中進行WINSOCKAPI編程開發的時候,需要在項目中使用下面的三個文件,否則會出現編譯錯誤。
  1WINSOCK.H: 這是WINSOCK API的頭文件,需要包含在項目中。
  2WSOCK32.LIB: WINSOCK API連接庫文件。在使用中,一定要把它作為項目的非缺省的連接庫包含到項目文件中去。 
  3WINSOCK.DLL: WINSOCK的動態連接庫,位於WINDOWS的安裝目錄下。

3.SOCKET API

下面講解的都是在實例中使用的接口,了解相關定義后我們結合實例講解。

1.WSAStartup()

此函數在應用程序中初始化Windows Sockets DLL ,只有此函數調用成功后,應用程序才可以再調用其他Windows Sockets DLL中的API函數。

int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
其返回值為整型,調用方式為PASCAL(即標准類型,PASCAL等於__stdcall),參數有兩個,第一個參數為WORD類型,指明了Socket的版本號,第二個參數為WSADATA類型的指針。
若返回值為0,則初始化成功,若不為0則失敗。

在程式中調用該函數的形式如下:WSAStartup(MAKEWORD(1,1), &WSAData),其中MAKEWORD(1,1)表示我們用的是WinSocket1.1版本,WSAata用來存儲系統傳回的關於WinSocket的資料。

(1) WORD類型、MAKEWORDLOBYTEHIBYTE

WORD類型是一個16位的無符號整型,在WTYPES.H中被定義為:
typedef unsigned short WORD;
其目的是提供兩個字節的存儲,在Socket中這兩個字節可以表示主版本號和副版本號。使用MAKEWORD宏可以給一個WORD類型賦值。例如要表示主版本號2,副版本號0,可以使用以下代碼:
WORD wVersionRequested;  

wVersionRequested = MAKEWORD( 2, 0 );
注意低位內存存儲主版本號2,高位內存存儲副版本號0,其值為0x0002。使用宏LOBYTE可以讀取WORD的低位字節,HIBYTE可以讀取高位字節。在后面的實例中會應用。

(2)WSADATA類型和LPWSADATA類型

    WSADATA類型是一個結構,描述了Socket庫的一些相關信息,其結構定義如下:
typedef struct WSAData {
        WORD                  wVersion;
        WORD                 wHighVersion;
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                     szSystemStatus[WSASYS_STATUS_LEN+1];
        unsigned short           iMaxSockets;
        unsigned short          iMaxUdpDg;
        char FAR *              lpVendorInfo;
} WSADATA;
typedef WSADATA FAR *LPWSADATA;
    值得注意的就是wVersion字段,存儲了Socket的版本類型。LPWSADATAWSADATA的指針類型。它們通過Socket的初始化函數WSAStartup讀取出來。

2.WSACleanup()

這是Socket環境的退出函數。返回值為0表示成功,SOCKET_ERROR表示失敗。

3.socket()

SOCKETsocket套接字類型,在WINSOCK2.H中有如下定義:
typedef unsigned int    u_int;
typedef u_int           SOCKET;
可知套接字實際上就是一個無符號整型,它將被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_STREAMSOCK_DGRAM)
protocol 通訊協定(如果使用者不指定則設為0)

如果要建立的是遵從TCP/IP協議的socket,第二個參數type應為SOCK_STREAM,為UDP(數據報)的socket,應為SOCK_DGRAM

l SOCK_STREAM:這個協議是按照順序的、可靠的、數據完整的基於字節流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。

l SOCK_DGRAM:這個協議是無連接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的連接。

套接字將被創建、設置、用來發送和接收數據,最后會被關閉。

4.bind()

接下來要為服務器端定義的這個監聽的Socket指定一個地址及端口(Port),這樣客戶端才知道待會要連接哪一個地址的哪個端口,為此我們要調用bind()函數,該函數調用成功返回0,否則返回SOCKET_ERROR

  int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR *name,int namelen );

參 數: 

sSocket對象名;
nameSocket的地址值,這個地址必須是執行這個程式所在機器的IP地址;
namelenname的長度; 

如果使用者不在意地址或端口的值,那么可以設定地址為INADDR_ANY,及Port0Windows Sockets 會自動將其設定適當之地址及Port (1024 到 5000之間的值)。此后可以調用getsockname()函數來獲知其被設定的值。

5.sockaddr_inin_addr類型,inet_addrinet_ntoa函數,sockaddr

sockaddr_in定義了socket發送和接收數據包的地址,定義:
struct sockaddr_in

{
        short     sin_family;
        u_short  sin_port;
        struct in_addr  sin_addr;
        char     sin_zero[8];
};
其中in_addr的定義如下:
struct in_addr 

{
         union {
                 struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                 struct { u_short s_w1,s_w2; } S_un_w;
                 u_long S_addr;

} S_un;
首先闡述in_addr的含義,很顯然它是一個存儲ip地址的聯合體(union),有三種表達方式:
第一種用四個字節來表示IP地址的四個數字;
第二種用兩個雙字節來表示IP地址;
第三種用一個長整型來表示IP地址。
in_addr賦值的一種最簡單方法是使用inet_addr函數,它可以把一個代表IP地址的字符串賦值轉換為in_addr類型,如
addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
其反函數是inet_ntoa,可以把一個in_addr類型轉換為一個字符串。
sockaddr_in的含義比in_addr的含義要廣泛,其各個字段的含義和取值如下:
第一個字段short   sin_family,代表網絡地址族,如前所述,只能取值AF_INET
第二個字段u_short sin_port,代表IP地址端口,由程序員指定;
第三個字段struct in_addr sin_addr,代表IP地址;
第四個字段char    sin_zero[8],是為了保證sockaddr_inSOCKADDR類型的長度相等而填充進來的字段。

sockaddr類型是用來表示Socket地址的類型,同上面的sockaddr_in類型相比,sockaddr的適用范圍更廣,因為sockaddr_in只適用於TCP/IP地址。Sockaddr的定義如下:
struct sockaddr {
 u_short    sa_family;
 char       sa_data[14];
};  
可知sockaddr16個字節,而sockaddr_in也有16個字節,所以sockaddr_in是可以強制類型轉換為sockaddr的。事實上也往往使用這種方法。

6.listen()

當服務器端的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 );

參數: 

sSocket 對象;
hWnd :接收消息的窗口句柄;
wMsg:傳給窗口的消息;
lEvent:被注冊的網絡事件,也即是應用程序向窗口發送消息的網路事件,該值為下列值FD_READFD_WRITEFD_OOB、 FD_ACCEPTFD_CONNECTFD_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;
 
}

 

7.accept()

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 );
參數:

sSocket的識別碼;

addr:存放來連接的客戶端的地址;
addrlenaddr的長度

8.connect()

Connect用於客戶端想服務器發起連接。

格 式: int PASCAL FAR connect( SOCKET s,const struct sockaddr FAR *name,int namelen );
參 數: s Socket 的識別碼
name 此 Socket 想要連接的對方位址
namelen name的長度
傳回值:成功 - 0
失敗 - SOCKET_ERROR (呼叫WSAGetLastError()可得知原因)
說明: 此函式用來向對方要求建立連接。若是指定的對方位址為 的話,會傳回錯誤值。當連接建立完成後,使用者即可利用此一 Socket 來做傳送或接收資料之用了。

9.sendto()send()函數

Socket中有兩套發送和接收函數,一是sendtorecvfrom;二是sendrecv。前一套在函數參數中要指明地址(UDP協議);而后一套需要先將套接字和一個地址綁定,然后直接發送和接收,不需綁定地址。
sendto的定義如下:
int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
第一個參數就是套接字;
第二個參數是要傳送的數據指針;
第三個參數是要傳送的數據長度(字節數);
第四個參數是傳送方式的標識,如果不需要特殊要求則可以設置為0,其它值請參考MSDN
第五個參數是目標地址,注意這里使用的是sockaddr的指針;
第六個參數是地址的長度;
返回值為整型,如果成功,則返回發送的字節數,失敗則返回SOCKET_ERROR
send函數有四個參數與sendto函數的前四個參數是一樣的。

 

10.recvfrom()recv()函數

int WSAAPI recvfrom(IN SOCKET s,char FAR * buf,int len,int flags,sockaddr FAR * from,int  * fromlen );
第一個參數就是套接字;
第二個參數是要接收消息的char數組的指針;
第三個參數是要接收的數據長度(字節數);
第四個參數是接收方式的標識,如果不需要特殊要求則可以設置為0
第五個參數是接收地址的結構指針,存放接收到對方的的地址信息;
第六個參數是地址的長度的指針(&);
返回值為整型,如果成功,則返回接收的字節數,失敗則返回SOCKET_ERROR
recv函數有四個參數與recvfrom函數的前四個參數是一樣的。

11.sleep()函數

線程掛起函數,表示線程掛起一段時間。Sleep(1000)表示掛起一秒。定義於WINBASE.H頭文件中。WINBASE.H又被包含於WINDOWS.H中,然后WINDOWS.HWINSOCK2.H包含。

12.recvfrom()recv()函數

int WSAAPI recvfrom(IN SOCKET s,char FAR * buf,int len,int flags,sockaddr FAR * from,int  * fromlen );
第一個參數就是套接字;
第二個參數是要接收消息的char數組的指針;
第三個參數是要接收的數據長度(字節數);
第四個參數是接收方式的標識,如果不需要特殊要求則可以設置為0
第五個參數是接收地址的結構指針,存放接收到對方的的地址信息;
第六個參數是地址的長度的指針(&);
返回值為整型,如果成功,則返回接收的字節數,失敗則返回SOCKET_ERROR
recv函數有四個參數與recvfrom函數的前四個參數是一樣的。

13.closesocket()

關閉套接字,其參數為SOCKET類型。成功返回0,失敗返回SOCKET_ERROR


4.TCP/UDP Socket流程圖

1.UDP流程圖

 

2.TCP流程圖

 

5.VC代碼實現

1.UDP實現

(1)udp server代碼

#include <stdio.h>

/* Windows socket頭文件 */

#include <Winsock2.h>

/* 網絡API的動態鏈接庫 */

#pragma comment(lib, "ws2_32.lib")

 

void main()

{

   SOCKET uiFdSocket;

   WSADATA wsaData;

   char szbuffer[1024] = "\0";

   struct sockaddr_in stServerAddr;

   struct sockaddr_in stClientAddr;

   int iAddrlen = sizeof(sockaddr_in);

 

   /* 調用Windows Sockets DLL,成功后才能使用socket系列函數 */

   if (0 != WSAStartup(MAKEWORD(2,1), &wsaData)) 

    {

         printf("Winsock init failed!\r\n");

         WSACleanup();

         return;

    }

   

   memset(&stServerAddr, 0, sizeof(stServerAddr));

   memset(&stClientAddr, 0, sizeof(stClientAddr));

   /* 服務器地址 */

   stServerAddr.sin_family = AF_INET;

   /* 監聽端口 */

   stServerAddr.sin_port = htons(6000); 

   stServerAddr.sin_addr.s_addr = INADDR_ANY;

   

   /* 服務器端創建socket, 報文模式(UDP)*/

   uiFdSocket = socket(AF_INET, SOCK_DGRAM, 0);

   /* 綁定端口號 */

   bind(uiFdSocket, (struct sockaddr*)&stServerAddr, sizeof(sockaddr_in));

   

   

   while(true)

   {

    printf("waiting client send msg now...\r\n");

 

if (SOCKET_ERROR != recvfrom(uiFdSocket,szbuffer,sizeof(szbuffer),0,(struct  ockaddr*)&stClientAddr, &iAddrlen))

{

printf("Received datagram from %s--%s\n", inet_ntoa(stClientAddr.sin_addr),  szbuffer);

 

sendto(uiFdSocket, szbuffer, sizeof(szbuffer), 0, (struct sockaddr*)&stClientAddr,  iAddrlen);

}

   }

   closesocket(uiFdSocket);

}

(2)udp cilent代碼

void main()

{

   SOCKET uiFdsocket;

   WSADATA wsaData;

   struct sockaddr_in stServerAddr;

   int iAddrlen = sizeof(sockaddr_in);

   char szbuffer[1024] = "\0";

 

   if (0 != WSAStartup(MAKEWORD(2,1),&wsaData)) 

   {

   printf("Winsock init faied!\r\n");

       WSACleanup();

       return;

   }

 

   /* 服務器監聽的端口和地址 */

   memset(&stServerAddr, 0, sizeof(stServerAddr));

   stServerAddr.sin_family = AF_INET;

   stServerAddr.sin_port = htons(6000); 

   stServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 

 

   printf("Now connecting the server...\r\n");

   uiFdsocket = socket(AF_INET, SOCK_DGRAM, 0);

 

   while(true)

   {

      

      printf("input message here...\r\n");

      scanf("%s", szbuffer);

      if(strcmp(szbuffer, "bye") == 0)

      {

         printf("exit\r\n");

         Sleep(100);

         closesocket(uiFdsocket);

         break;

  }

      if(SOCKET_ERROR != sendto(uiFdsocket, szbuffer,sizeof(szbuffer), 0, (struct  sockaddr*)&stServerAddr, iAddrlen))

      {

        Sleep(100);

        if (SOCKET_ERROR != recvfrom(uiFdsocket, szbuffer, sizeof(szbuffer), 0, (struct sockaddr*)&stServerAddr, &iAddrlen))

{

printf("recive from server:%s\r\n", szbuffer);

}

      }

    }  

   closesocket(uiFdsocket);

   return;

}

2.TCP實現

(1)tcp server代碼

#include <stdio.h>

/* Windows socket頭文件 */

#include <Winsock2.h>

/* 網絡API的動態鏈接庫 */

#pragma comment(lib, "ws2_32.lib")

 

void main()

{

SOCKET uiFdSerSocket;

SOCKET uiFdConnectSocket;

WSADATA wsaData;

char szSendbuffer[1024] = "\0";

char szRecvbuffer[1024] = "\0";

struct sockaddr_in stServerAddr;

struct sockaddr_in stClientAddr;

int iAddrlen = sizeof(sockaddr_in);

 

/* 調用Windows Sockets DLL,成功后才能使用socket系列函數 */

if (0 != WSAStartup(MAKEWORD(2,1), &wsaData)) 

{

printf("Winsock init failed!\r\n");

WSACleanup();

        return;

}

if (LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 1) 

{

printf("the socket version is error!\r\n");

WSACleanup();

return;

}

memset(&stServerAddr, 0, sizeof(stServerAddr));

memset(&stClientAddr, 0, sizeof(stClientAddr));

/* 服務器地址 */

    stServerAddr.sin_family = AF_INET;

    /* 監聽端口 */

    stServerAddr.sin_port = htons(6000); 

    stServerAddr.sin_addr.s_addr = INADDR_ANY;

   

    /* 服務器端創建socket, 流模式(TCP)*/

    uiFdSerSocket = socket(AF_INET, SOCK_STREAM, 0);

    /* 綁定端口號 */

    bind(uiFdSerSocket, (struct sockaddr*)&stServerAddr, sizeof(sockaddr_in));

    /* 服務器監聽 */

    listen(uiFdSerSocket, 5);

   

    while(true)

    {

    printf("waiting client send msg now...\r\n");

/* 接受客戶端連接,獲取客戶端的ip地址 */

uiFdConnectSocket = accept(uiFdSerSocket, (SOCKADDR*)&stClientAddr, &iAddrlen);

 

/* 組合發送消息,將IP地址整形轉化為字符串 */

sprintf(szSendbuffer, "Welcome %s to here!", inet_ntoa(stClientAddr.sin_addr));

/* 發送消息到客戶端 */

send(uiFdConnectSocket, szSendbuffer, strlen(szSendbuffer)+1, 0);

 

/* 接收客戶端消息 */

recv(uiFdConnectSocket, szRecvbuffer, sizeof(szRecvbuffer), 0);

printf("Received datagram from client %s->%s\n", inet_ntoa(stClientAddr.sin_addr), szRecvbuffer);

 

closesocket(uiFdConnectSocket);

    }

    closesocket(uiFdSerSocket);

}

(2)tcp client代碼

#include <stdio.h>

#include <Winsock2.h>

#pragma comment(lib, "ws2_32.lib")

 

void main()

{

   SOCKET uiFdClientsocket;

   WSADATA wsaData;

   struct sockaddr_in stServerAddr;

   int iAddrlen = sizeof(sockaddr_in);

   char szSendbuffer[1024];

   char szRecvbuffer[1024];

 

   if (0 != WSAStartup(MAKEWORD(2,1),&wsaData)) 

   {

   printf("Winsock init faied!\r\n");

       WSACleanup();

       return;

   }

   if (LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 1) 

   {

       printf("the socket version is error!\r\n");

   WSACleanup();

   return;

   }

 

   /* 服務器監聽的端口和地址 */

   memset(&stServerAddr, 0, sizeof(stServerAddr));

   stServerAddr.sin_family = AF_INET;

   stServerAddr.sin_port = htons(6000); 

   stServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 

 

   while(true)

   {

  szSendbuffer[0] = '\0';

  szRecvbuffer[0] = '\0';

  /* 創建SOCKET */

  uiFdClientsocket = socket(AF_INET, SOCK_STREAM, 0);

  /* 連接服務器 */

      connect(uiFdClientsocket, (SOCKADDR*)&stServerAddr, sizeof(sockaddr_in));

      

  printf("connect to server ok...\r\n");  

  /* 接收服務器端數據 */    

  recv(uiFdClientsocket, szRecvbuffer, sizeof(szRecvbuffer), 0);

  printf("revive data from server->%s\r\nnow input msg here...`\r\n",szRecvbuffer);

 

  scanf("%s", szSendbuffer);

      if(strcmp(szSendbuffer, "bye") == 0)

      {

         printf("exit\r\n");

         Sleep(100);

         closesocket(uiFdClientsocket);

         break;

  }

  /* 向服務器端發送數據 */  

      send(uiFdClientsocket, szSendbuffer, sizeof(szSendbuffer) + 1, 0);

 

  closesocket(uiFdClientsocket);

    }

   

    closesocket(uiFdClientsocket);

    return;

}

 

PS:有好的意見大家一起分享~~~感謝好基友!!

 


免責聲明!

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



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