精选 select 模型:实现服务器端和客户端代码 (select模型服务器端和客户端代码)

精选 SELECT 模型:实现服务器端和客户端代码

作为现代计算机应用开发中最广泛使用的两个平台,服务器和客户端是解决客户和服务交互的重要基础。而实现这两个平台的代码,则必须是高度灵活和功能强大的,以满足不同应用的需要。为此,开发人员需要使用一种可靠,可拓展并且强大的模型。

SELECT模型是一种高效的模型,它是开发人员用于创建高度可扩展,可伸缩和底层 I/O 传输的关键工具。通过数据流聚合输入为套接字的事件,SELECT模型允许开发人员以非阻止或异步方式获取数据,并向客户端提供可用。

在本文中,我们将深入研究SELECT模型的原理,并演示如何使用它来构建服务器和客户端代码。

SELECT模型的原理

SELECT模型是Linux和Windows操作系统中很常见的系统调用。该模型通过一种集中的单线程事件循环机制来管理多个套接字连接。当SELECT模型开始监听套接字时,它会等待客户端发送数据,而不会阻塞服务列表中的事件。

选择模型涉及使用select系统调用来侦听I/O事件,如读取和写入。从而使一个I/O流被处理,而不需要使线程一直阻塞在该操作上,同时它还可进行轮询等操作。

SELECT模型是管理现代计算机应用程序中多个连接的一种有效方式。换句话说,它使一个进程能够同时监听多个套接字,实现跨网络连接的数据传输。

如下所示,SELECT模型的主要组件:

1. 文件描述符列表:用于存储套接字描述符。

2. 输入类型:用于存储套接字的输入类型(读取,写入,错误)。

3. 时间值:指示SELECT模型应该等待事件的时间戳,以避免频繁检查描述符表。

SELECT模型有优秀的伸缩性,因为其设计支持处理许多并发的网络连接。 它的工作原理如下:

SELECT模型将套接字添加到描述符列表中。然后,它轮流等待事件,并选择发生I/O事件的描述符。一旦有一个连接事件发生,接收数据的套接字就准备好处理数据。然后,开发人员可以执行必要的操作来处理数据。SELECT模型更新时间值并检查下一个事件。

使用SELECT模型构建服务器端代码

在这个案例中,我们将使用SELECT模型来开发一个简单的Web服务器。 我们声明必要的库:

“`

#include

#include

#include

#include

#include

#include

#include

#include

“`

声明主要变量

创建描述符表fd_set来保存所有Socket,并建立一个监听套接字serverfd:

“`

fd_set master_fds, fds;

int max_sockfd, clientfd [1024], max_client = -1, sockfd, len;

char buffer[1025];

struct sockaddr_in serveraddr, clientaddr;

“`

初始化描述符表fd_set

使用FD_ZERO初始化描述符表fd_set,并将serverfd添加到主描述符表中:

“`

FD_ZERO(&master_fds);

FD_SET(serverfd, &master_fds);

“`

监听套接字serverfd

使用listen函数将serverfd绑定在给定的IP地址和端口号上。

“`

serverfd = socket(AF_INET, SOCK_STREAM, 0);

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

serveraddr.sin_family = AF_INET;

serveraddr.sin_port = htons(PORT);

serveraddr.sin_addr.s_addr = htons(INADDR_ANY);

bind(serverfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

listen(serverfd, 5);

“`

使用fd_set描述符来接受传入套接字连接

使用FD_ISSET函数检查用于接收连接的描述符和主描述符。然后,使用accept函数接受连接,将连接套接字添加到connfd中。在客户端连接列表中存储该客户端套接字。

“`

while (true) {

fds = master_fds;

if (select(max_sockfd + 1, &fds, NULL, NULL, NULL) == -1) {

perror(“select”);

exit(EXIT_FLURE);

}

for (sockfd = 0; sockfd

if (FD_ISSET(sockfd, &fds)) {

if (sockfd == serverfd) {

len = sizeof(clientaddr);

clientfd[max_client + 1] = accept(serverfd, (struct sockaddr *)&clientaddr, &len);

if(clientfd[max_client + 1] == -1) {

perror(“accept”);

} else {

FD_SET(clientfd[max_client + 1], &master_fds);

if (clientfd[max_client + 1] > max_sockfd) {

max_sockfd = clientfd[max_client + 1];

}

if (max_client

max_client = max_sockfd;

}

printf(“Accept connection from %s on port %d\n”, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

}

} else {

if ((len = recv(sockfd, buffer, sizeof(buffer), 0))

if (len == 0) {

printf(“Connection closed with Client with server FD: %d\n”, sockfd);

} else {

perror(“recv”);

}

close(sockfd);

FD_CLR(sockfd, &master_fds);

if (sockfd == max_sockfd) {

while (FD_ISSET(max_sockfd, &master_fds) == false) {

max_sockfd -= 1;

}

}

} else {

buffer[len] = ‘\0’;

printf(“Received data from Client with Server FD: %s\n”, buffer);

}

}

}

}

}

“`

使用SELECT模型构建客户端代码

在这个案例中,我们将使用SELECT模型来开发一个简单的客户端,它连接到同一SERVER FD并发送字符串。

声明必要的库:

“`

#include

#include

#include

#include

#include

#include

#include

#include

#include

“`

声明变量

然后,声明需要的变量,包括套接字描述符clientfd和描述符列表fds:

“`

int sockfd, len;

struct sockaddr_in serveraddr;

char buffer[1025];

fd_set fds;

“`

建立套接字

通过socket函数建立TCP套接字。

“`

clientfd = socket(AF_INET, SOCK_STREAM, 0);

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

“`

设置服务器地址

使用inet_pton函数将IP地址转换为网络字节序,并将其存储在服务器地址结构体中。

“`

serveraddr.sin_family = AF_INET;

serveraddr.sin_addr.s_addr = inet_addr(ipaddr); //IP地址

serveraddr.sin_port = htons(portno); //端口号

“`

与服务器建立连接

使用connect函数与服务器建立连接。

“`

if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))

perror(“Connect()”);

exit(EXIT_FLURE);

}

“`

发送消息

使用FD_ZERO,FD_SET和FD_ISSET等函数,通过SELECT模型检查描述符列表。在建立连接后,使用write函数将要发送的消息存储在buffer中,并将其发送给服务器。

“`

while(strncmp(buffer, “END”, strlen(buffer)-1) != 0) {

FD_ZERO(&fds);

FD_SET(clientfd, &fds);

FD_SET(STDIN_FILENO, &fds);

select(clientfd + 1, &fds, NULL, NULL, NULL);

if (FD_ISSET(STDIN_FILENO, &fds)) {

fgets(buffer, sizeof(buffer), stdin);

len = strlen(buffer);

if (write(clientfd, buffer, len) != len ) {

perror(“Write() error\n”);

exit(EXIT_FLURE);

}

}

}

“`

关闭连接

使用close函数关闭客户端套接字。

“`

printf(“Exiting client.\n”);

close(clientfd);

return 0;

“`

结论

相关问题拓展阅读:

  • 如何将select模型改为异步选择模型
  • 服务器端,在WSAEventSelect模型下,怎么判断收到的FD

如何将select模型改为异步选择模型

异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,

接收以 Windows 消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。

该模型的核心即是WSAAsyncSelect函数。

█ 要想使用 WSAAsyncSelect 模型,在应用程序中,首先必须用CreateWindow函数创建一个窗口,再为该窗口提供一个窗口例程函数(WinProc)。

█ WSAAsyncSelect 的函数原型如下:

   view plain copy

  int WSAAsyncSelect(

  __inSOCKET s,

  __inHWND hWnd,

  __inunsigned int wMsg,

  __inlong lEvent

  );

  ● s 参数指定的是我们感兴趣的那个套接字。

● hWnd 参数指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。

● wMsg 参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。

(通常,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突)

● lEvent 参数指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括:

FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,

要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,

然后将它们分配给lEvent就可以了,例如:

WSAAsyncSeltct(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

解释说明:我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。

█ 注意 ①:

多个事件务必在套接字上一次注册!

另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,

或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,

事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

█ 注意 ②:

若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。

这样一来,如果调用了像WSARecv这样的Winsock函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。

为防止这一点,歼迟应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据

FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据

FD_ACCEPT 应用程序想接收与进入连接有关的通知

FD_CONNECT 应用程序想接收与一次连接完成的通知

FD_CLOSE 应用程序想接收与套接字关闭的通知

█ 应用程序在一个套接字上成功调用了闭改御WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网轿岩络事件通知。

窗口例程通常定义如下:

   view plain copy

  LRESULT CALLBACK WindowProc(

  HWND hwnd,

  UINT uMsg,

  WPARAM wParam,

  LPARAM lParam

  );

  ● hWnd 参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。

● uMsg 参数指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。

● wParam 参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。

● lParam参数中,包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。

█ 步骤:网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。

这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。

若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。

此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

█ 注意 ③:应用程序如何对 FD_WRITE 事件通知进行处理。

只有在三种条件下,才会发出 FD_WRITE 通知:

■ 使用 connect 或 WSAConnect,一个套接字首次建立了连接。

■ 使用 accept 或 WSAAccept,套接字被接受以后。

■ 若 send、WSASend、sendto 或 WSASendTo 操作失败,返回了 WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。

因此,作为一个应用程序,自收到首条 FD_WRITE 消息开始,便应认为自己必然能在一个套接字上发出数据,

直至一个send、WSASend、sendto 或 WSASendTo 返回套接字错误 WSAEWOULDBLOCK。

  经过了这样的失败以后,要再用另一条 FD_WRITE 通知应用程序再次发送数据。

  用例:

   view plain copy

  WSAAsyncSelect(pThis->m_SockListen, pThis->GetSafeHwnd(), WM_SOCKET, FD_ACCEPT | FD_CLOSE);//WM_SOCKET为自定义消息#define WM_SOCKET WM_USER+100

   view plain copy

  //override窗口过程函数

  LRESULT CServerDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

  {

  switch(message)

  {

  case WM_SYSCOMMAND:

  if (wParam == SC_CLOSE) {

  closesocket(m_SockClient);

  closesocket(m_SockListen);

  WSACleanup();

  }

  break;

  case WM_SOCKET:

  if (WSAGETSELECTERROR(lParam)) {

  –m_ClientNums;

  closesocket(wParam);

  break;

  }

  switch(WSAGETSELECTEVENT(lParam))

  {

  case FD_ACCEPT:

  {

  if (m_ClientNums >= 1) {

  break;

  }

  m_SockClient = accept(wParam, NULL, NULL);

  WSAAsyncSelect(m_SockClient, m_hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);

  ++m_ClientNums;

  break;

  }

  case FD_READ:

  {

  TCHAR szBuf = {0};

  recv(wParam, (char *)szBuf, MAX_BUF_SIZE, 0);

  ShowMsg(szBuf);

  break;

  }

  case FD_WRITE:

  wParam = wParam;

  break;

  case FD_CLOSE:

  –m_ClientNums;

  closesocket(wParam);

  break;

  }

  default:break;

  }

  

  return CDialog::WindowProc(message, wParam, lParam);

服务器端,在WSAEventSelect模型下,怎么判断收到的FD

——解决方案——

WSAWaitForMultipleEvents

——解决方案——

TCP客户端用WSAWaitForMultipleEvents等待所有数据连接的FD_READ、FD_CLOSE

TCP服务器端用WSAWaitForMultipleEvents等待监听的FD_ACCEPT和数据连接的FD_READ、FD_CLOSE

不管是客户端还是服塌弯务器端,每建立一个SOCKET就WSACreateEvent一个EVENT并WSAEventSelect,在WSAWaitForMultipleEvents里面等这些EVENT,EVENT与SOCKET一一对应,更具WSAWaitForMultipleEvents的返回值来确定是哪个SOCKET

——解决方案——

你要支耐衫陵持更昌戚多的客户端就用完成端口。。

——解决方案——

WSAWaitForMultipleEvents对每64个Event分组进行查询的方式达到支持> 64客户端的支持

缺点就是效率比完成端口低

关于select模型服务器端和客户端代码的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

版权声明:本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
文章名称:《精选 select 模型:实现服务器端和客户端代码 (select模型服务器端和客户端代码)》
文章链接:https://zhuji.vsping.com/201513.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。