winsock-select模型总结
1 select模型介绍[1]
- select模型的名字来源于select函数,当线程需要调用一个I/O函数的时候,比如recv,此时如果套接字处于阻塞模式,则直接调用recv有可能会使线程阻塞;如果套接字是非阻塞模式,则直接调用recv,又有可能返回WSAEWOULDBOLCK,也就是说,无论套接字是阻塞模式还是非阻塞模式,直接调用recv都有可能出现问题;
- 有了select模型后,当线程需要调用recv时,线程不直接调用它,相反,线程先调用selec函数,由于select函数本身是阻塞的,所以线程将会被挂起.当select函数检测到系统的接受缓冲区有可用数据的时候,也就是能够确保recv成功返回的时候,select将返回,此时线程将被唤醒,进而可以调用recv,此时调用recv就会成功,而不会阻塞和返回WSAEWOULDBOLCK,
- 问题:select函数本身是阻塞的,这和使用阻塞模式的套接字直接调用recv有什么区别呢?
区别在于,如果对阻塞模式的套接字直接调用I/O函数,线程每次只能等待一个套接字的一种I/O操作,而如果线程调用的是select的话,因为selec可以同时检测多个套接字的多种操作,所以只要任一个套接字或者多个套接字上的任意一种或者多种操作就绪,select都会返回,也就是说线程具备了 等待多个套接字上多种操作的能力;由于一个套接字对应一个客户端,也就是线程可以同时服务多个客户连接;
2 server端(以下代码直接粘贴到vs中就可以运行)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <process.h>
#include <winsock.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
const char SERVER[] = "127.0.0.1";
const int PORT = 5555;
const int MSGSIZE = 1024;
int cnt = 0;
SOCKET sockets[FD_SETSIZE];
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
fd_set fdread;
int ret;
struct timeval tv = { 1, 0 };
char szMessage[MSGSIZE];
while (TRUE)
{
FD_ZERO(&fdread);
for (int i = 0; i < cnt; i++)
{
FD_SET(sockets[i], &fdread);//将套接口加入集合中
}
//检查可读性的套接口
ret = select(0, &fdread, NULL, NULL, &tv);
if (ret == 0)
{
cout << "ret == 0?" << endl;
continue;
}
for (int i = 0; i < cnt; i++)
{
if (FD_ISSET(sockets[i], &fdread)) //如果可读, 奶蛋的,写错了。
{
ret = recv(sockets[i], szMessage, MSGSIZE, NULL);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
printf("Client socket %d closed.\n", sockets[i]);
closesocket(sockets[i]);
if (i < cnt - 1)
{
sockets[i--] = sockets[--cnt];
}
}
else
{
szMessage[ret] = '\0';
cout << "The message that client send is :" << szMessage << endl;
char szBuffer[1001];
sprintf(szBuffer, "I have bean received your messages, %s", szMessage);
send(sockets[i], szBuffer, strlen(szBuffer), NULL);
}
}
}
}
return 0;
}
int main()
{
WSADATA wsaData; //WSADATA framework 用于存储 Windows 套接字调用返回的初始化信息
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
char szMessage[MSGSIZE];
int ret;
DWORD dwThreadId;
int iaddrSize = sizeof(SOCKADDR_IN);
WSAStartup(0x0202, &wsaData); //初始化ws2_32.lib
//create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//bind
local.sin_family = AF_INET;
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_port = htons(PORT);
if (bind(sListen, reinterpret_cast<sockaddr*>(&local), sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
cout << "bind failed!" << endl;
}
//listen
if (listen(sListen, 3) == SOCKET_ERROR)
{
cout << "listen failed!" << endl;
}
//create WorkerThread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
cout << "PID = " << dwThreadId << endl;
while (TRUE)
{
//accept a connection
//这里的accept也是个阻塞函数,如果有必要可以将其修改为非阻塞,这样可以不影响自己的主线程的程序;
/*
修改为非阻塞的代码
u_long nNoBlock = 1;
ioctlsocket(sListen, FIONBIO, &nNoBlock);
*/
sClient = accept(sListen, reinterpret_cast<sockaddr*>(&client), &iaddrSize);
if (sClient == INVALID_SOCKET)
{
cout << "accept error!" << endl;
break;
}
else
{
printf("Accept client: %s: %d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
//加入到socket的数组中
sockets[cnt++] = sClient;
}
}
closesocket(sListen);
WSACleanup();
return 0;
}
3 client端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
const char SERVER[] = "127.0.0.1";
const int PORT = 5555;
const int MSGSIZE = 1024;
int main()
{
WSADATA wsaData;
SOCKET sClient;
SOCKADDR_IN server;
char szMessage[MSGSIZE];
int ret;
WSAStartup(0x0202, &wsaData);
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&server, 0, sizeof(SOCKADDR_IN));
server.sin_family = AF_INET;
server.sin_addr.S_un.S_addr = inet_addr(SERVER);
server.sin_port = htons(PORT);
cout << "Waiting for connect!" << endl;
if (connect(sClient, (struct sockaddr*)(&server), sizeof(SOCKADDR_IN)) == 0)
{
cout << "connect!!~" << endl;
while (TRUE)
{
cout << "send:" << endl;
cin >> szMessage;
send(sClient, szMessage, strlen(szMessage), 0);
ret = recv(sClient, szMessage, MSGSIZE, 0);
szMessage[ret] = '\0';
cout << "receive: " << szMessage << endl;
}
}
closesocket(sClient);
WSACleanup();
return 0;
}
4 运行结果
参考文献
[1]唐文超编著,Visual studio C++网络编程2013[M].清华大学出版社
[2]https://blog.csdn.net/aledavvv/article/details/23791179