// 文章出处 精通Windows Sockets网络开发:基于Visual C++实现.pdf
// 服务器一般实现过程
nErrCode = WSAStartup(wVersion, &wsaData);// 初始化socket库
m_sServer = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);// 创建socket
ioctlsocket(m_sServer,FIONBIO,&ul);// 设置socket为阻塞模式
getsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*) &uiNewSize, &uiRcvBufLen)
setsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*)&uiBuffSize, uiRcvBufLen)
reVal = bind(m_sServer,(sockaddr*)&m_sServerAddr,sizeof(SOCKADDR_IN));// 绑定socket
reVal = listen(m_sServer,SOMAXCONN);// 监听
hReqAndData = CreateThread(NULL, 0,HandleClient, this, 0, &dwThread);// 创建接收客户连接线程
DWORD WINAPICServerDlg::HandleClient(void *pParam);// 接收客户连接线程
int nRet = select(0, &readfd, &writefd,NULL, NULL);// 采用select模型
closesocket(sHost);
WSACleanup();// 卸载socket库
// 客户端一般实现过程
nErrCode = WSAStartup(wVersion, &wsaData);// 初始化socket库
m_sClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 创建套接字
ioctlsocket(m_sServer,FIONBIO,&ul);// 设置socket为阻塞模式
getsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*) &uiNewSize, &uiRcvBufLen)
setsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*)&uiBuffSize, uiRcvBufLen)
nErrCode = connect(m_sClientSocket,(sockaddr*)&servAddr,sizeof(SOCKADDR_IN));// 连接服务器
closesocket(m_sClientSocket);
WSACleanup();// 卸载socket库
上面是我们写CS模型时,用到的基本函数,这里只是从代码中拷贝出来,函数具体功能以及各参数意义,请详查文档资料。
这里,主要讲下select模型。
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一个参数不要管,会被系统忽略的。
第二个参数是用来检查套接字可读性,也就说检查套接字上是否有数据可读
第三个参数用来检查数据是否可以发出。
第四个参数是检查是否有带外数据可读取。
第五个参数是用来设置select等待时间
struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果将这个结构设置为(0,0),如果模式为非阻塞模式,则立即返回,如果为阻塞模式,则一直等待,直到2,3,4参数中有符合条件的情况。
下面例子是在阻塞模式下完成的。
FD_CLR、FD_ISSET、FD_SET、FD_ZERO,四个宏可以查看文档,得到具体用法。
这里主要讲下select模型怎么判断是否可写情况
FD_SET writefd;// 可写套接字集合
FD_ZERO(&writefd); // 清空集合
FD_SET(sClient, &writefd); // 将客户端套接字加入该集合
int nRet = select(0, 0, &writefd, NULL, NULL);// 检查是否有可写套接字存在
这时候,上面的getsockopt函数就很重要了,函数原型:
WINSOCK_API_LINKAGE
int
WSAAPI
getsockopt(
__in SOCKET s,
__in int level,
__in int optname,
__out_bcount(*optlen) char FAR * optval,
__inout int FAR * optlen
);
getsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*) &uiNewSize, &uiRcvBufLen)
其中uiNewSize为套接字inSocket的缓冲区大小。当然我们可以改变缓冲区的大小(setsockopt(inSocket, SOL_SOCKET, SO_SNDBUF, (char*)&uiBuffSize, uiRcvBufLen))
如果writefd中的套接字还有缓冲区可写,那么此时select会立即返回,我们可以通过可写套接字发送数据。
如果writefd中的套接字没有缓冲区可写,那么此时select阻塞
可能有人会不理解(包括我自己以前)
如果我们不使用select模型,用普通方式建立服务器与客户端的连接,那么我们通过连接套接字向另一端发数据会是什么情况(另一端不接收数据)。
while(TRUE)
{
ulReadSend = send(sLinkSocket, cSendBuf, uSendSize, 0);// sLinkSocket套接字,cSendBuf字符数组,uSendSize发送数据大小
sleep(500);
}
我的测试结果是send阻塞前发送的总数据比getsockopt函数获得的缓冲区大小要大,而且当第一次发送很大的数据时,一般都会成功,且返回值为发送值相等
但第二次发送时,如果服务器没有接收,那么就会阻塞在这,当服务器断开,则立即返回SOCKET_ERROR。
所以,我们采取select模型时,服务器端select只监控可读套接字,来接受客户端连接,然后将每个客户端的连接单独处理,select监控连接套接字的可读、可写状态。
可能有理解不当之处,请指正,共同学习。