posts - 495,comments - 227,trackbacks - 0

服务端套接字是编程最简单的一个部分,甚至在各种环境下都可以用类似的代码。对于一个服务端套接字而言,他的基本工作包括:

  1. 初始化
  2. 创建套接字
  3. 给套接字捆绑一个本地地址
  4. 【可选】设置套接字属性
  5. 调用listen函数
  6. 进入accept循环,接受来自客户端的请求
  7. 对PCS进行管理
  8. 释放服务端套接字所占用的资源

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 阅读(646) 评论(0)  编辑  收藏 所属分类: C++

只有注册用户登录后才能发表评论。


网站导航: