學習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中建立一個控制台的空項目:
我們着手建構自己的第一個winsock程式。
首先win32下與Linux下的socket API需要包含不同的頭檔案。
在Linux下是這些:
#include < unistd.h >
#include < sys / socket.h >
#include < arpa / inet.h > win32下的winsock有多個版本,我所找到的資料中,老的版本是:
#include < winsock.h > 與之對應的需要的連結庫為:
這可能可以相容非常古老的版本中的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 ;
}