网络编程4-select模型

4.3服务器端IO模型

双十一时,淘宝同时在线用户可达上亿。想处理上亿用户的连接请求,必须要求阿里服务器的性能非常高。而以前12306处理过多用户的需求时,经常出现死机的情况,这说明服务器性能不足,在设计时技术有限而导致优化不足。

因此,在服务器端编程需要构造高性能的IO模型,windows平台常用的IO模型有5种:选择模型(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)、完成端口(Completion Port)。

4.3.1选择模型

//方法1

//TCP服务器端

#include <iostream>

#include <winsock2.h>

#include <windows.h>

#pragma comment(lib,"ws2_32.lib")//引用库文件

using namespace std;

 

int main(int argc, char ** argv)

{

//步骤1:当前应用程序和相应的socket库绑定

WORD wVersionRequested;

WSADATA wsaData;

int err;

wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);

if (err != 0)

{

cout << "WSAStartup Failed!" << endl;

return -1;

}

 

//步骤2:创建TCP网络套接字

SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);

//步骤3:设置服务器端地址结构SOCKADDR_IN

SOCKADDR_IN addr_server;

//3.1 初始化SOCKADDR_IN

memset(&addr_server, 0, sizeof(addr_server));

//3.2 设置地址协议族

addr_server.sin_family = AF_INET;

//3.3 设置客户端可连接的IP

addr_server.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY表示绑定电脑上所有网卡IP

//3.4 设置客户端可连接的端口

addr_server.sin_port = htons(6000);//不能使用公认端口,即端口>= 1024

 

//步骤4:将套接字和地址绑定

bind(sockServer, (SOCKADDR*)&addr_server, sizeof(addr_server));

 

//步骤5:监听

listen(sockServer, 5);

cout << "Start Listen..." << endl;

 

SOCKADDR_IN addr_client;

int len = sizeof(SOCKADDR);

const int maxClientCount = 63;

int curClientCount = 0;

SOCKET sockClient_A[maxClientCount];

 

while (1)

{

//步骤6:将服务器端的socket和客户端的socket放入FD_SET中

FD_SET sock_ReadSet;

FD_ZERO(&sock_ReadSet);

FD_SET(sockServer, &sock_ReadSet);

for (int i = 0; i < curClientCount; ++i)

{

if (sockClient_A[i] != 0)

FD_SET(sockClient_A[i], &sock_ReadSet);

}

timeval tv;

tv.tv_sec = 2;

tv.tv_usec = 0;

//步骤7:等待FD_SET中的socket有数据传输

//int ret = select(0, &sock_ReadSet, NULL, NULL,NULL);//若有数据传输则立即返回,否则一直等待(最后一个参数:NULL表示一直等待)

int ret = select(0, &sock_ReadSet, NULL, NULL, &tv);//若有数据传输则立即返回,否则等待2秒,再返回

if (ret != SOCKET_ERROR)

{

for (int i = 0; i < curClientCount + 1; i++)

{

//步骤8:若是客户端的socket有数据传输

if (FD_ISSET(sockClient_A[i], &sock_ReadSet))

{

char Buf[1024] = "\0";

int nRet = recv(sockClient_A[i], Buf, 1024, 0);

//步骤8.1:客户端和服务器端断开连接:nRet == 0

if (nRet == SOCKET_ERROR || nRet == 0)

{

//删除sock_ReadSet中的socket

closesocket(sockClient_A[i]);

FD_CLR(sockClient_A[i], &sock_ReadSet);

//删除数组sockClient_A[i]中的套接字

for (int a = i; a < curClientCount; a++)

{

sockClient_A[a] = sockClient_A[a + 1];

if (a == curClientCount - 1)

sockClient_A[a + 1] = 0;

}

curClientCount--;

}

//步骤8.2:客户端向服务器端传输数据

else

{

//二次开发

cout << Buf << endl;

strcat(Buf, ":Server Received");

send(sockClient_A[i], Buf, strlen(Buf) + 1, 0);

}

}

//步骤9:若是服务器端的socket有数据传输,则有客户端连接服务器端

//防止连接后,立即传数据,因此把连接的代码放在数据接收代码的后面

if (FD_ISSET(sockServer, &sock_ReadSet))

{

SOCKET sockClient = accept(sockServer, (SOCKADDR*)&addr_client, &len);

if (curClientCount < maxClientCount)

{

printf("连接个数:%d\n", curClientCount + 1);

sockClient_A[curClientCount] = sockClient;

curClientCount++;

break;

}

else

{

cout << "达到最大连接数..." << endl;

send(sockClient, "Client Exit...", 1024, 0);

closesocket(sockClient);

}

}

}

}

}

 

//步骤10:关闭套接字

closesocket(sockServer);

//步骤11:将应用程序和socket库解除绑定

WSACleanup();

return 0;

}

//TCP客户端:代码不变

#include <iostream>

#include <winsock2.h>

#include <windows.h>

#pragma comment(lib,"ws2_32.lib")//引用库文件

using namespace std;

 

int main(int argc, char ** argv)

{

//步骤1:当前应用程序和相应的socket库绑定

WORD wVersionRequested;

WSADATA wsaData;

int err;

wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);

if (err != 0)

{

cout << "WSAStartup Failed!" << endl;

return -1;

}

 

//步骤2:创建TCP网络套接字

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

//步骤3:设置服务器端地址结构SOCKADDR_IN

SOCKADDR_IN addr_server;

//3.1 初始化SOCKADDR_IN

memset(&addr_server, 0, sizeof(addr_server));

//3.2 设置地址协议族

addr_server.sin_family = AF_INET;

//3.3 设置服务器端的IP

addr_server.sin_addr.s_addr = inet_addr("192.168.1.101");

//3.4 设置服务器端的端口号

addr_server.sin_port = htons(1900);//不能使用公认端口,即端口>= 1024

 

//步骤4:客户端连接服务器

int result = connect(sock, (SOCKADDR*)&addr_server, sizeof(addr_server));

if (result == -1)

return -1;

cout << "Connect Seccessed!" << endl;

 

//步骤5:向服务器端发送数据

char Buf[1024] = "\0";

cin.getline(Buf, 1024);

send(sock, Buf, 1024, 0);

recv(sock, Buf, 1024, 0);

printf("%s\n", Buf);

 

//步骤6:关闭套接字

closesocket(sock);

//步骤7:将应用程序和socket库解除绑定

WSACleanup();

return 0;

}

方法1是将服务器端的套接字和客户端的套接字都放入FD_SET结构体中。当服务器端的套接字有数据传输,说明有客户端需要连接;当客户端的套接字有数据传输,要么是断开连接,要么是由数据传送。

知识点1:fd_set结构体

typedef struct fd_set {

        u_int fd_count;               /* 套接字数量 */

        SOCKET  fd_array[FD_SETSIZE];   /* 套接字数组,FD_SETSIZE = 64 */

} fd_set;

知识点2:

select(参数1,参数2,参数3,参数4,参数5):轮循FD_SET中的套接字上是否有数据传输。

参数1:0,忽略;

参数2:FD_SET *,指向一组可读性检查的套接字(有数据可读入;连接关闭、重设、中止)。

参数3:FD_SET *,指向一组可写性检查的套接字。

参数4:FD_SET *,指向一组等待错误检查的套接字。

参数5:超时时间。若有数据,立即返回;否则等待tv_sec秒+ tv_usec毫秒。若timeval为NULL,表示一直等待。

struct timeval {

        long    tv_sec;         /*  */

        long    tv_usec;        /* 毫秒 */

};

注:当客户端有数据传输时,执行select函数后,fd_count会减一,因此在步骤8的上一行代码处,我并未用fd_count来判断套接字数量,而是使用curClientCount + 1。

选择模型的本质是在select函数处,对多个客户端的套接字进行轮循,也因此导致效率低下。

//方法2

//服务器端

#include <winsock.h> 

#include <stdio.h>

#define PORT       6000

#define MSGSIZE    1024

#pragma comment(lib, "ws2_32.lib")

int    g_iTotalConn = 0;

SOCKET g_CliSocketArr[FD_SETSIZE];

DWORD WINAPI WorkerThread(LPVOID lpParameter);

int main()

{

WSADATA     wsaData;

SOCKET      sListen, sClient;

SOCKADDR_IN local, client;

int         iaddrSize = sizeof(SOCKADDR_IN);

DWORD       dwThreadId;

// Initialize Windows socket library

WSAStartup(0x0202, &wsaData);

// Create listening socket

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

 

// Bind

local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

local.sin_family = AF_INET;

local.sin_port = htons(PORT);

bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

// Listen

listen(sListen, 3);

// Create worker thread

HANDLE hHandle = CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

CloseHandle(hHandle);

while (TRUE)

{

// Accept a connection   

sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);

printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

// Add socket to fdTotal   

g_CliSocketArr[g_iTotalConn++] = sClient;

}

return 0;

}

 

DWORD WINAPI WorkerThread(LPVOID lpParam)

{

int            i;

fd_set         fdread;

int            ret;

struct timeval tv = { 1, 0 };

char           szMessage[MSGSIZE];

while (TRUE)

{

FD_ZERO(&fdread);

for (i = 0; i < g_iTotalConn; i++)

{

FD_SET(g_CliSocketArr[i], &fdread);

}

// We only care read event    

ret = select(0, &fdread, NULL, NULL, &tv);

if (ret == 0)

{

// Time expired   

continue;

}

for (i = 0; i < g_iTotalConn; i++)

{

if (FD_ISSET(g_CliSocketArr[i], &fdread))

{

// A read event happened on pfdTotal->fd_array[i]    

ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);

if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))

{

// Client socket closed        

printf("Client socket %d closed.\n", g_CliSocketArr[i]);

closesocket(g_CliSocketArr[i]);

if (i < g_iTotalConn - 1)

{

g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];

}

}

else

{       // We received a message from client       

szMessage[ret] = '\0';

send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);

}

}

}

}

return 0;

}

//TCP客户端:代码不变

#include <iostream>

#include <winsock2.h>

#include <windows.h>

#pragma comment(lib,"ws2_32.lib")//引用库文件

using namespace std;

 

int main(int argc, char ** argv)

{

//步骤1:当前应用程序和相应的socket库绑定

WORD wVersionRequested;

WSADATA wsaData;

int err;

wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);

if (err != 0)

{

cout << "WSAStartup Failed!" << endl;

return -1;

}

 

//步骤2:创建TCP网络套接字

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

//步骤3:设置服务器端地址结构SOCKADDR_IN

SOCKADDR_IN addr_server;

//3.1 初始化SOCKADDR_IN

memset(&addr_server, 0, sizeof(addr_server));

//3.2 设置地址协议族

addr_server.sin_family = AF_INET;

//3.3 设置服务器端的IP

addr_server.sin_addr.s_addr = inet_addr("192.168.1.101");

//3.4 设置服务器端的端口号

addr_server.sin_port = htons(1900);//不能使用公认端口,即端口>= 1024

 

//步骤4:客户端连接服务器

int result = connect(sock, (SOCKADDR*)&addr_server, sizeof(addr_server));

if (result == -1)

return -1;

cout << "Connect Seccessed!" << endl;

 

//步骤5:向服务器端发送数据

char Buf[1024] = "\0";

cin.getline(Buf, 1024);

send(sock, Buf, 1024, 0);

recv(sock, Buf, 1024, 0);

printf("%s\n", Buf);

 

//步骤6:关闭套接字

closesocket(sock);

//步骤7:将应用程序和socket库解除绑定

WSACleanup();

return 0;

}

方法2中,主线程负责连接客户端,工作线程负责处理客户端传来的数据。

方法2中的代码摘自:《Windows网络编程》第八章。

猜你喜欢

转载自blog.csdn.net/Bing_bing_bing_/article/details/83476752