学习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
;
}