服务器端利用Select模型通信
Select模型解决传统C/S模型中accpet阻塞等待客户端链接的问题,但其只解决accpet阻塞的问题,不解决send()、recv()执行阻塞的问题。
服务器端
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define PORT 65432
int main()
{
//初始化winsock2.DLL
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
cout << "加载winsock.dll失败!" << endl;
return 0;
}
//创建套接字
SOCKET sock_server,newsock;
if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
//绑定端口和Ip
sockaddr_in addr,client_addr;
int addr_len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//ip地址转网络字节序
if (SOCKET_ERROR == bind(sock_server, (SOCKADDR*)&addr, sizeof(sockaddr_in)))
{
cout << "地址绑定失败!错误代码:" << WSAGetLastError() << endl;
closesocket(sock_server);
WSACleanup();
return 0;
}
//将套接字设为监听状态
int size = listen(sock_server, 0);
if (size != 0)
{
cout << "listen函数调用失败!\n";
closesocket(sock_server);
WSACleanup();
return 0;
}
fd_set fdsock;//套接字集
fd_set fdread;//读取
fd_set fdwrite;//写入
char msgbuffer[500];//接收数据缓冲区
char msg[500];//发送数据缓冲区
FD_ZERO(&fdsock);
FD_SET(sock_server, &fdsock);
timeval tv;//时间计时器
//循环接收连接请求并收发数据
while (1)
{
tv.tv_sec = 1;//select等待两秒后返回,避免被锁死以及马上返回
tv.tv_usec = 0;
fdread = fdsock;
fdwrite = fdsock;//初始化
if (select(0, &fdread, &fdwrite, NULL, &tv) == SOCKET_ERROR)
{
DWORD dw = GetLastError();
if (dw == WSAENETDOWN)
{
printf("The network subsystem has failed\n");
break;
}
}
else
{
cout <<"连接的数目是:"<<fdsock.fd_count << endl;
for (int i = 0; i < (int)fdsock.fd_count; i++)
{
if (FD_ISSET(fdsock.fd_array[i], &fdread))
{
if (fdsock.fd_array[i] == sock_server)//侦听套接字返回一个新的连接则是accpet
{
newsock = accept(sock_server, (struct sockaddr*)&client_addr, &addr_len);
if (newsock == INVALID_SOCKET)
{
cout << "accept函数调用失败!\n";
break;
}
cout << "服务器成功和:" << newsock << "建立连接!" << endl;
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port);
FD_SET(newsock, &fdsock);//将新套接字加入fdsock
continue;
}
}
}
for (int i = 0; i < (int)fdsock.fd_count; i++)//服务器发送数据
{
if (FD_ISSET(fdsock.fd_array[i], &fdwrite))
{
printf("服务器向%d发送数据:\n", fdsock.fd_array[i]);
cin.getline(msg, sizeof(msg));
int size = send(fdsock.fd_array[i], msg, sizeof(msg), 0);//给客户端发送一段信息
if (strcmp(msg, "end\0") == 0)
{
cout << "关闭和" << fdsock.fd_array[i] << "的连接!" << endl;
FD_CLR(fdsock.fd_array[i], &fdsock);
continue;
}
if (size == SOCKET_ERROR || size == 0)
{
cout << "发送信息失败!错误代码:" << WSAGetLastError() << endl;
FD_CLR(fdsock.fd_array[i], &fdsock);
}
}
}
for (int i = 0; i < (int)fdsock.fd_count; i++)//服务器接收消息
{
if (FD_ISSET(fdsock.fd_array[i], &fdwrite))
{
memset((void*)msgbuffer, 0, sizeof(msgbuffer));
size = recv(fdsock.fd_array[i], msgbuffer, sizeof(msgbuffer), 0);//接收客户端的消息
if (size <= 0)
{
if (size == 0) cout << fdsock.fd_array[i] <<"is closed" << endl;
else
{
DWORD dw = GetLastError();//不同的网络错误信息
switch (dw)
{
case WSAENETDOWN: printf("The network subsystem has failed\n");
break;
case WSAENETRESET: printf("The connection has been broken\n");
break;
case WSAESHUTDOWN: printf("The socket has been shut downd\n");
break;
case WSAETIMEDOUT: printf("The connection has been dropped\n ");
break;
case WSAECONNRESET: printf("The virtual circuit was reset\n");
break;
default: printf("program error,please check program\n");
}
}
FD_CLR(fdsock.fd_array[i], &fdsock);
}
else cout << fdsock.fd_array[i] << " 说: " << msgbuffer << endl;
}
}
}
}
for (int i = 0; i < fdsock.fd_count; i++)
{
closesocket(fdsock.fd_array[i]);
FD_CLR(fdsock.fd_array[i], &fdsock);
}
WSACleanup();
return 0;
}
客户端
#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define PORT 65432
int main()
{
//初始化winsock2.DLL
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
cout << "加载winsock.dll失败!" << endl;
return 0;
}
//创建套接字
SOCKET sock_client;
if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
//连接服务器
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//绑定本机的环回地址
int len = sizeof(sockaddr_in);
if (connect(sock_client, (SOCKADDR*)&addr, len) == SOCKET_ERROR) {
cout << "连接失败!错误代码:" << WSAGetLastError() << endl;
return 0;
}
//实现交互部分,客户端先接收后发送数据
while (1)
{
//接收服务端的消息
char msgbuffer[500] = { 0 };
int size = recv(sock_client, msgbuffer, sizeof(msgbuffer), 0);
if (strcmp(msgbuffer, "end\0") == 0)
{
cout << "服务器端已经关闭连接!" << endl;
break;
}
if (size < 0)
{
cout << "接收信息失败!错误代码:" << WSAGetLastError() << endl;
break;
}
else if (size == 0)
{
cout << "对方已经关闭连接" << endl;
break;
}
else cout << "The message from Server:" << msgbuffer << endl;
//从键盘输入一行文字发送给服务器
msgbuffer[499] = 0 ;
cout << "从键盘输入发给服务器的信息:" << endl;
cin.getline(msgbuffer, sizeof(msgbuffer));
if (strcmp(msgbuffer, "end\0") == 0)
{
cout << "关闭连接!" << endl;
break;
}
int ret = send(sock_client, msgbuffer, sizeof(msgbuffer), 0);
if (ret == SOCKET_ERROR || ret == 0)
{
cout << "发送信息失败!错误代码:" << WSAGetLastError() << endl;
break;
}
else cout << "信息发送成功!" << endl;
}
closesocket(sock_client);
WSACleanup();
return 0;
}
我们需要while循环不断轮询遍历所有接收的套接字集合进行收发数据。
我们用建立连接时服务器端接收的客户端套接字来唯一标识该客户端。
通过对比,我们可以看到Select模型的优势,在阻塞模式下Select模型综合了非阻塞的accept()函数不断监听的优势。
运行实例
服务器首先发送消息,之后等待客户端的回应。我们仍使用一方发出"end"结束会话,实例展示了服务器端或客户端交互或结束会话的示例。
实例展示连接数为1其实就是只有监听套接字在套接字集中,我们可以看到accpet()函数不阻塞不断监听新的链接,但send()函数和recv()仍阻塞等待客户端的消息。