服务端套接字是编程最简单的一个部分,甚至在各种环境下都可以用类似的代码。对于一个服务端套接字而言,他的基本工作包括:
- 初始化
- 创建套接字
- 给套接字捆绑一个本地地址
- 【可选】设置套接字属性
- 调用listen函数
- 进入accept循环,接受来自客户端的请求
- 对PCS进行管理
- 释放服务端套接字所占用的资源
3.1 初始化
对于每个需要处理套接字的程序,在调用任何其他套接字函数之前,必须首先调用WSAStartup函数。如果不调用它,其他函数调用会失败,并返回WSANOTINITIALISED错误码,表示WinSock库还没有初始化。对于除了其他操作系统,初始化的方法会不一样,请参见对应的文档找到初始化方法。
WSAStartup的函数原型为:
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
其中,各个参数的含义如下:
wVersionRequested:
|
本程序所需要的最低版本号。请注意,MSDN中所说的是本程序可能用到的最高版本的函数,其含义和我所说的一样。 |
lpWSAData
|
返回WSADATA结构,说明WinSock库当前的实现细节 |
该函数成功时返回0,否则可能返回如下一些错误码:
WSASYSNOTREADY
|
底层网络系统未准备好
|
WSAVERNOTSUPPORTED
|
所要求版本号本实现不支持
|
WSAEINPROGRESS
|
一个阻塞性套接字操作未完成
|
WSAEPROCLIM
|
达到当前实现所支持的最大任务数
|
WSAEFAULT
|
lpWSAData是一个非法指针 |
一般用户并不关心实现细节,只要当前实现库满足最低版本需求即可。常见代码为:
WORD wVersion = MAKEWORD(2,2);//最低版本2.2
WSADATA WSAData;
int nResult;
if(ERROR_SUCCESS != (nResult = WSAStartup(wVersion,&WSAData)))
{
ReportError("WSAStartup", nResult);
return -1;
}
3.2 创建套接字
每个套接字任务都从创建套接字开始。我们可以用socket函数来创建套接字,该函数的原型为:
SOCKET socket(
int af,
int type,
int protocol
)
其中,各个参数的含义为:
协议号,说明该套接字所处理的协议。他的可选值随前面两个参数不同而不同。似乎在RAW协议中用的比较多,大家可以在ROUTPROT.h中找到类似定义。
af |
地址族,说明该socket支持的地址类型。我们可以在winsock2.h中找到所支持的地址族。不过一般来说,对于TCP/IP编程,我们都会设置为AF_INET |
type
|
协议类型,Winsock2.h中列出了5种类型,我们一般会使用其中的三种,SOCK_STREAM表示流协议,SOCK_DGRAM表示数据报协议,SOCK_RAW表示原始套接字。我会在数据传输部分详细解释这些内容 |
protocol |
用于侦听的套接字需要是流套接字,下面代码会创建这样的套接字:
SOCKET hSocket;
int a = PROTO_ICMP;
hSocket = socket(AF_INET,SOCK_STREAM,0);
if(ERRORHANDLE(hSocket == INVALID_SOCKET))
{
WSACleanup();
return -1;
}
3.3 捆绑本地地址
每个套接字必须有一个地址才能和对方通讯。bind函数用于捆绑地址,它的函数原型为:
int bind(
SOCKET s,
const struct sockaddr* name,
int namelen
);
各个参数含义如下:
s
|
套接字号 |
name
|
地址信息:需要注意的是,套接字接口是给多种协议共享的,sockaddr结构只是一个占位符,不同协议使用不同的地址结构,例如,TCP/IP编程使用的结构是sockaddr_in |
namelen |
地址结构的长度,加入这个参数的原因也就是因为不同协议有不同的结构 |
需要注意的,bind函数只能给socket绑定本机的IP地址,如果你给出的地址信息是其他机器的,则必然会失败。此时该函数返回SOCKET_ERROR,WSAGetLastError则返回WSAEADDRNOTAVAIL。
如果没有特殊需求,应该设置该结构的IP地址为INADDR_ANY。对于端口,服务端套接字需要指定一个端口,而客户端端口最好设置为0,让系统选择一个可用端口。
下面代码初始化一个地址结构:
void InitializeAddress(DWORD ip, UINT port, sockaddr_in & addr)
{
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr= ip;
addr.sin_port = htons(port);
}
下面代码则把一个套接字绑定到本机2000端口上:
if(ERRORHANDLE(SOCKET_ERROR == bind(hSocket, (const sockaddr*) & addr, sizeof(addr))))
{
closesocket(hSocket);
WSACleanup();
return -1;
}
3.4 进入侦听状态
当我们创建一个套接字,并绑定了地址后,我们需要设置这个套接字进入侦听状态,进入侦听状态后,该套接字就可以处理来自客户端的链接请求。
listen函数设置套接字进入侦听状态,其函数原型为:
int listen(
SOCKET s,
int backlog
);
对于刚开始编程的人,最容易误解的是backlog参数。许多人以为这就是该套接字最多能接收的链接的数目。实际上,一个套接字能接受的链接的数目不受这个参数控制,它只受系统资源的限制。例如对于linux,套接字用文件句柄实现,那么它可能受最大文件句柄数的限制。
这个参数的含义是最多未决连接的数目,也就是连接请求已经到了服务端套接字,但是用户还没有调用accept的套接字数目。对于WinSock2,这个参数最大值为5。
下面示例代码说明了如何调用listen:
if(ERRORHANDLE(SOCKET_ERROR == listen(hSocket,5)))
{
closesocket(hSocket);
WSACleanup();
return -1;
}
3.5 accept循环
当一个套接字处于侦听状态以后,我们就可以循环调用accept来接受新连接。accept函数的原型如下:
SOCKET accept(
SOCKET s,
struct sockaddr* addr,
int* addrlen
);
这个函数的参数说明和前面bind的一样。需要说明的是,在MSDN中说后面两个参数都是out参数,经过我的测试,结论并不一样。对于
addrlen参数,应该是一个in/out参数,也就是说,如果第二个参数是一个结构指针,则第三个参数必须是一个整型变量的指针,该整型变量还必须被
设置为该结构的长度。
3.6 PCS管理
由于一个一个服务端套接字可能接收无数个PCS,如何管理这些PCS就成为一个问题。不同的程序员有自己不同的管理方式,在此我就不准备细讲了。
posted on 2008-07-03 15:17
SIMONE 阅读(647)
评论(0) 编辑 收藏 所属分类:
C++