天天看點

Win32下使用Socket:WinSock

學習socket最好能有兩台以上聯網的電腦,以及能獲得公網IP的網絡接入方式。兩年前,我主要使用的是一台win2k3和Debain Linux雙系統的電腦,例外有台99年的老機器裝着win98,而且沒有裝VC,測試相當的麻煩。現在買了筆記本,使用的是Vista的win32環境(32位),可以直接和老電腦的Linux聯網進行測試。另外,網絡環境也換成了電信的ADSL,貴了很多,為的就是能有一個公網IP。接下來的教程我會兼顧winsock的代碼,這主要是因為winsock本身對socket幾乎是相容的。是以,這裡有必要先說明在VC環境中使用socket的一些簡單設定,以及與Linux環境下的細微差别。

我的VC環境依然是2008 Express,在寫這篇教程的時候,微軟已經釋出了VC 2010,目前在微軟的官方首頁,提供了VC 2010的下載下傳,同時保留着VC 2008的下載下傳。

我們在VC中建立一個控制台的空項目:

Win32下使用Socket:WinSock
Win32下使用Socket:WinSock
Win32下使用Socket:WinSock

我們着手建構自己的第一個winsock程式。

首先win32下與Linux下的socket API需要包含不同的頭檔案。

在Linux下是這些:

#include  < unistd.h >

#include  < sys / socket.h >

#include  < arpa / inet.h > win32下的winsock有多個版本,我所找到的資料中,老的版本是:

#include  < winsock.h > 與之對應的需要的連結庫為:

Win32下使用Socket:WinSock
Win32下使用Socket:WinSock

這可能可以相容非常古老的版本中的winsock,比如win98,而微軟官方所推薦的是:

#include  < winsock2.h > 連結庫是:ws2_32.lib,這樣就可以使用高版本的winsock。

那麼,什麼是winsock的版本?這就涉及到winsock的初始化函數WSAStartup:

http://msdn.microsoft.com/en-us/library/ms742213(v=VS.85).aspx

上面是微軟的官方說明,我這裡建構一個簡單的類,希望每次使用的時候引入一個類對象就可以了。

class  WinsockAPI{

private :

    WSADATA wsaData;

public :

    WinsockAPI( int  low_byte  =   2 ,  int  high_byte  =   2 );

     ~ WinsockAPI();

     void  showVersion()  const ;

}; WSADATA是記錄着winsock資訊的結構。

// class WinsockAPI

WinsockAPI::WinsockAPI( int  low_byte,  int  high_byte)

{

     const  WORD wVersionRequested  =  MAKEWORD(low_byte, high_byte);

     int  wsa_startup_err  =  WSAStartup(wVersionRequested,  & wsaData);

     if  (wsa_startup_err  !=   0 ) {

        std::cerr  <<   " WSAStartup() failed. "   <<  std::endl;

         throw  wsa_startup_err;

    }

}

WinsockAPI:: ~ WinsockAPI()

{

    WSACleanup();

}

void  WinsockAPI::showVersion()  const

{

    std::cout     <<   " The version of Winsock.dll is  "  

                 <<   int (LOBYTE(wsaData.wVersion)) 

                 <<   " . "  

                 <<   int (HIBYTE(wsaData.wVersion)) 

                 <<   " . "  

                 <<  std::endl;

     return ;

}

首先,宏MAKEWORD()将兩個int轉換為winsock形式的版本号,我這裡預設是是2.2,就隻需要MAKEWORD(2, 2),如果是老版本的,最低應該是1.0。WSAStartup()将winsock的初始化資訊寫入一個WSADATA結構(我們這裡的wsaData),如果成功傳回0,失敗将傳回一個int的錯誤代碼。這個錯誤代碼直接表示了錯誤資訊,微軟官方建議不使用winsock的通用異常資訊擷取函數WSAGetLastError()擷取WSAStartup()的錯誤資訊,這可能是因為如果WSAStartup()失敗,那麼winsock的錯誤資訊不一定能夠正确的建構出來的緣故。

最後,winsock結束後用WSACleanup()清理。

因為socket本身的複雜性,異常資訊提示非常重要。WSAGetLastError()的官方說明如下:

http://msdn.microsoft.com/en-us/library/ms741580(VS.85).aspx

錯誤代碼所回報的資訊查詢在這裡:

http://msdn.microsoft.com/en-us/library/ms740668(v=VS.85).aspx

最後,需要注意的問題是,因為socket是建構在UNIX系統下的(BSD socket是當今所有socket的基礎),是以socket很好的利用了UNIX體系“一切都是檔案”的性質,每個socket本身也就是一個UNIX檔案描述符,是以,Linux下的socket是用關閉檔案的函數close()關閉的。但是win32下沒這個性質,是以winsock是另外一種抽象,但是好在同樣用int作為描述符,關閉需要專門為winsock定做的函數closesocket()。

下面重寫了TCP Server的代碼(類的抽象和構造也重新寫了,将在下一章解釋),作為winsock使用的示範。

// Filename: SockClass.hpp

#ifndef SOCK_CLASS_HPP

#define  SOCK_CLASS_HPP

#include  < iostream >

#include  < winsock2.h >

namespace  sockClass

{

void  error_info( const   char *  s);

}

class  WinsockAPI{

private :

    WSADATA wsaData;

public :

    WinsockAPI( int  low_byte  =   2 ,  int  high_byte  =   2 );

     ~ WinsockAPI();

     void  showVersion()  const ;

};

class  BaseSock{

protected :

     int  sockFD;

public :

    BaseSock();

     virtual   ~ BaseSock()  =   0 ;

     const   int &  showSockFD()  const ;

};

class  TCPListenSock:  public  BaseSock{

private :

    sockaddr_in listenSockAddr;

public :

    TCPListenSock(unsigned  short  listen_port);

     ~ TCPListenSock();

     void  TCPListen(

         int  max_connection_requests  =   10 )  const ;

};

class  TCPServerSock:  public  BaseSock{

private :

    sockaddr_in clientSockAddr;

protected :

     char *  preBuffer;

     int  preBufferSize;

    mutable  int  preReceivedLength;

public :

    TCPServerSock(

         const  TCPListenSock &  listen_sock,

         int  pre_buffer_size  =   32 );

     virtual   ~ TCPServerSock();

     int  TCPReceive()  const ;

     int  TCPSend( const   char *  send_data,

             const   int &  data_length)  const ;

};

#endif   // SockClass.hpp

// Filename: SockClass.cpp

#include  " SockClass.hpp "

// sockClass

namespace  sockClass

{

void  error_info( const   char *  s)

{

    std::cerr  <<  s  <<  std::endl;

     throw  WSAGetLastError();

}

}

// class WinsockAPI

WinsockAPI::WinsockAPI( int  low_byte,  int  high_byte)

{

     const  WORD wVersionRequested  =  MAKEWORD(low_byte, high_byte);

     int  wsa_startup_err  =  WSAStartup(wVersionRequested,  & wsaData);

     if  (wsa_startup_err  !=   0 ) {

        std::cerr  <<   " WSAStartup() failed. "   <<  std::endl;

         throw  wsa_startup_err;

    }

}

WinsockAPI:: ~ WinsockAPI()

{

    WSACleanup();

}

void  WinsockAPI::showVersion()  const

{

    std::cout     <<   " The version of Winsock.dll is  "  

                 <<   int (LOBYTE(wsaData.wVersion)) 

                 <<   " . "  

                 <<   int (HIBYTE(wsaData.wVersion)) 

                 <<   " . "  

                 <<  std::endl;

     return ;

}

// class BaseSock

BaseSock::BaseSock():

sockFD( - 1 )

{}

BaseSock:: ~ BaseSock()

{}

const   int &  BaseSock::showSockFD()  const

{

     return  sockFD;

}

// class TCPListenSock

TCPListenSock::TCPListenSock(unsigned  short  listen_port)

{

    sockFD  =  socket(PF_INET,

                    SOCK_STREAM,

                    IPPROTO_TCP);

     if  (sockFD  <   0 ) {

        sockClass::error_info( " socket() failed. " );

    }

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

    listenSockAddr.sin_family  =  AF_INET;

    listenSockAddr.sin_addr.s_addr  =  htonl(INADDR_ANY);

    listenSockAddr.sin_port  =  htons(listen_port);

     if  (bind(    sockFD,

                (sockaddr * ) & listenSockAddr,

                 sizeof (listenSockAddr))  <   0 ) {

        sockClass::error_info( " bind() failed. " );

    }

}

TCPListenSock:: ~ TCPListenSock()

{

    closesocket(sockFD);

}

void  TCPListenSock::TCPListen(

                         int  max_connection_requests)  const

{

     if  (listen(    sockFD,

                max_connection_requests)  <   0 ) {

        sockClass::error_info( " listen() failed. " );

    }

}

// class TCPServerSock

TCPServerSock::TCPServerSock(

                 const  TCPListenSock &  listen_sock,

                 int  pre_buffer_size):

preBufferSize(pre_buffer_size),

preReceivedLength( 0 )

{

    preBuffer  =   new   char [preBufferSize];

     int  clientSockAddrLen  =   sizeof (clientSockAddr);

    sockFD  =  accept(    listen_sock.showSockFD(),

                        (sockaddr * ) & clientSockAddr,

                         & clientSockAddrLen);

     if  (sockFD  <   0 ) {

        sockClass::error_info( " accept() failed. " );

    }

    std::cout     <<   " Client (IP:  "

                 <<  inet_ntoa(clientSockAddr.sin_addr)

                 <<   " ) conneted. "   <<  std::endl;

}

TCPServerSock:: ~ TCPServerSock()

{

    delete [] preBuffer;

    closesocket(sockFD);

}

int  TCPServerSock::TCPReceive()  const

{

    preReceivedLength  =  recv(    sockFD,

                                preBuffer,

                                preBufferSize,

                                 0 );

     if  (preReceivedLength  <   0 ) {

        sockClass::error_info( " recv() failed. " );

    }  else   if  (preReceivedLength  ==   0 ) {

        std::cout  <<   " Client has been disconnected./n " ;

         return   0 ;

    }

     return  preReceivedLength;

}

int  TCPServerSock::TCPSend( const   char *  send_data,

                            const   int &  data_length)  const

{

     if  (data_length  >  preBufferSize) {

         throw   " Data is too large, resize preBufferSize. " ;

    }

     int  sent_length  =  send(    sockFD,

                            send_data,

                            data_length,

                             0 );

     if  (sent_length  <   0 ) {

        sockClass::error_info( " send() failed. " );

    }  else   if  (sent_length  !=  data_length) {

        sockClass::error_info( " sent unexpected number of bytes. " );

    }

     return  sent_length;

}

// Filename AppSock.hpp

#ifndef APP_SOCK_HPP

#define  APP_SOCK_HPP

#include  " SockClass.hpp "

class  TCPEchoServer:  public  TCPServerSock{

public :

    TCPEchoServer(

         const  TCPListenSock &  listen_sock,

         int  pre_buffer_size  =   32 );

     ~ TCPEchoServer();

     bool  handEcho()  const ;

};

#endif   // AppSock.hpp

// Filename: AppSock.cpp

#include  < string >

#include  " AppSock.hpp "

TCPEchoServer::TCPEchoServer( const  TCPListenSock &  listen_sock,  int  pre_buffer_size):

TCPServerSock(listen_sock, pre_buffer_size)

{}

TCPEchoServer:: ~ TCPEchoServer()

{}

bool  TCPEchoServer::handEcho()  const

{

     const  std:: string  SHUTDOWN_CMD  =   " /shutdown " ;

     while  (TCPReceive()  >   0 ) {

        std:: string  cmd(preBuffer, SHUTDOWN_CMD.size());

         if  (cmd  ==  SHUTDOWN_CMD  &&  preReceivedLength  ==  SHUTDOWN_CMD.size()) {

             return   false ;

        }

        TCPSend(preBuffer, preReceivedLength);

    }

     return   true ;

}

// Filename: main.cpp

#include  " SockClass.hpp "

#include  " AppSock.hpp "

int  TCP_echo_server( int  argc,  char *  argv[]);

int  main( int  argc,  char *  argv[])

{

     int  mainRtn  =   0 ;

     try  {

        mainRtn  = TCP_echo_server(argc, argv);

    }

     catch  ( const   char *  s) {

        perror(s);

         return   1 ;

    }

     catch  ( const   int &  err) {

        std::cerr  <<   " Error:  "   <<  err  <<  std::endl;

         return   1 ;

    }

     return  mainRtn;

}

int  TCP_echo_server( int  argc,  char *  argv[])

{

     const  unsigned  short  DEFAULT_PORT  =   5000 ;

    unsigned  short  listen_port  =  DEFAULT_PORT;

     if  (argc  ==   2   &&  atoi(argv[ 1 ])  >   0 ) {

        listen_port  =  atoi(argv[ 1 ]);

    }

    WinsockAPI winsockInfo;

    winsockInfo.showVersion();

    TCPListenSock listen_sock(listen_port);

    listen_sock.TCPListen();

     bool  go_on  =   true ;

     while  (go_on){

        TCPEchoServer echo_server(listen_sock);

        go_on  =  echo_server.handEcho();

    }

     return   0 ;

}

繼續閱讀