网络模型是为了更好的利用资源,优化服务器
因为一个连接产生一个socket,所以维护大量的客户端必须得用多线程处理不同的客户端,而用以前的一个客户端创建一个线程去维护处理又会有新问题
线程最大是一个2个字节的数量(65535),如果超过的话这种就处理不了,所以就希望一个线程处理多个客户端,所以可以建立一个socket的数组去处理
而一个线程处理多少个客户端合适呢,经过科学计算,处理64个是最好的,所以每次来一个客户端连接就把它放进数组中存储,每一个线程处理自己数组
中的socket,而recv是阻塞的,如果线程轮询数组的话,如果前面连接的socket没有数据那线程就会阻塞在这里(也称同步),后面的socket就处理不了
所以要把socket设置为非阻塞模式(也称作异步),这样的话线程处就能处理每一个socket了
而标准网络编程中就提供了这种处理模式,这就是select服务器模型(经过科学计算,一个线程同时处理64个客户端的连接是效率最高的,所有select模型轮询的socket数组时64个)
select模型简单代码如下
// Server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <tchar.h>
#include <WS2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
struct select_info
{
select_info()
{
m_nCount = 0;
memset(clients, 0, sizeof(clients));
}
int m_nCount;
SOCKET clients[FD_SETSIZE];
};
DWORD WINAPI workThread(LPVOID lpParameter)
{
select_info *info = (select_info*)lpParameter;
printf("workThread:%d run...\n");
fd_set set;
while (true)
{
//初始化
FD_ZERO(&set);
for (int i = 0; i < info->m_nCount; i++)
{
FD_SET(info->clients[i], &set);
}
//轮询
timeval tv = { 2 };
int recv_count = select(0, &set, NULL, NULL, &tv);
if (recv_count == 0)
{
continue;
}
else if (recv_count < 0)
{
break;
}
//轮询判断那个连接是可以读
for (int i = 0; i < set.fd_count; i++)
{
if (FD_ISSET(set.fd_array[i], &set))
{
char msg[260] = { 0 };
int bytes = recv(set.fd_array[i], msg, sizeof(msg), 0);
if (bytes > 0)
{
printf("socket:%d bytes:%d msg:%s\n", set.fd_array[i], bytes, msg);
}
else
{
//数组移除socket
}
}
}
}
delete info;
printf("workThread:%d exit...\n");
return 0;
}
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s <= 0) {
return 0;
}
puts("socket ok");
//绑定端口
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(58888);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (bind(s, (sockaddr*)&addr, sizeof(addr)) < 0) {
return 0;
}
puts("bind ok");
//监听
if (listen(s, SOMAXCONN) < 0) {
return 0;
}
puts("listen ok");
puts("accept...");
//接受客户端连接
select_info* pSelectInfo = NULL;
int nIndex = 0;
int nAllCount = 0;
while (true)
{
sockaddr_in caddr;
int nlen = sizeof(addr);
int cs = accept(s, (sockaddr*)&caddr, &nlen);
int n = WSAGetLastError();
if (cs < 0)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
break;
}
else
{
//如果是64的倍数就创建一个线程去管理这个客户端连接的数组连接
if (nAllCount % 64 == 0)
{
nIndex = 0;
pSelectInfo = NULL;
pSelectInfo = new select_info();
pSelectInfo->clients[nIndex] = cs;
++pSelectInfo->m_nCount;
CreateThread(NULL, 0, workThread, (LPVOID)pSelectInfo, 0, NULL);
}
else//否则就往这个轮询的数组添加连接socket
{
++nIndex;
++pSelectInfo->m_nCount;
pSelectInfo->clients[nIndex] = cs;
}
++nAllCount;
}
printf("%d\n", nAllCount);
}
puts("server close");
closesocket(s);
WSACleanup();
return 0;
}