windows上的5种网络通信模型示例代码

本文详细介绍了在Windows环境下实现网络通信的五种模型:select模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠IO模型以及完全端口IO Completion Port模型。通过示例代码展示了如何在C++中应用这些模型,包括客户端和服务器端的创建、连接、发送和接收数据。同时,文章提及了不同模型的优缺点和适用场景。
摘要由CSDN通过智能技术生成
一些好设计的经验:

linux网络:

高性能网络编程IO复用和Epoll高效率之处-遍历的集合更小空间换时间/水平触发和边缘触发主动返回。

反应堆的设计模式-避免C风格的一个应用逻辑都需要处理多个对象而是用OO设计模式方式分离。

windows网络:

select模型,WSAAsyncSelect模型,WSAEventSelect模型,重叠Overlapped IO模型,完全端口IO Completion Port模型。

是遵循定期检查,窗口事件通知,事件对象通知,多线程重叠IO和事件对象完成通知,事件对象完成通知和通过完成端口消息队列有效的管理外部和内部的线程池,进化来提高网络通信模型。

0.客户端设计
#include "stdafx.h"
#include <WINSOCK2.H>  
#include <stdio.h>  
#define SERVER_ADDRESS "127.0.0.1"  
#define PORT           5150
#define MSGSIZE        8192 // window操作系统默认socket收发缓存大小是8K
#pragma comment(lib, "ws2_32.lib")  

void CheckBuffer(SOCKET &socket)
{
 //window 7,sock2,默认内核发送缓存和接收缓存都是8K.
 int sendbuflen = 0;  
 int len = sizeof(sendbuflen);  
 getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&sendbuflen, &len);  
 printf("default,sendbuf:%d\n", sendbuflen);
 
 getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&sendbuflen, &len);  
 printf("default,recvbuf:%d\n", sendbuflen);
 /*sendbuflen = 10240;  
 setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);  */
}

bool LoadWSAData(WSADATA &wsaData)
{
 // Initialize Windows socket library  
 WORD wVersionRequested = MAKEWORD(2, 2);
 // MAKEWORD的作用,类似下面
 WORD wHigh = 2;
 WORD wLow = 2;
 WORD wAll = ((wHigh << 8) | wLow);
 // 初始化只需要传入版本号,和WSADATA就可以了
 int reqErr = ::WSAStartup(wVersionRequested, &wsaData);
 if(reqErr != 0)
 {
  printf("加载请求指定版本的windows socket api DLL 失败");
  return false;
 }
 /* Confirm that the WinSock DLL supports 2.2.*/
 /* Note that if the DLL supports versions greater    */
 /* than 2.2 in addition to 2.2, it will still return */
 /* 2.2 in wVersion since that is the version we      */
 /* requested.                                        */
 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
  /* Tell the user that we could not find a usable */
  /* WinSock DLL.                                  */
  printf("Could not find a usable version of Winsock.dll\n");
  ::WSACleanup();
  return false;
 }
 else
 {
  printf("The Winsock 2.2 dll was found okay\n");
  return true;
 }
}

void ReleaseWSAData()
{
 ::WSACleanup();
}

int _tmain(int argc, _TCHAR* argv[])
{

 WSADATA     wsaData;  
 SOCKET      sClient;  
 SOCKADDR_IN server;  
 char        szMessage[MSGSIZE];// 所有整数浮点数,非数值编码类型都可以转换为char/unsigned char的十六进制

 if(!LoadWSAData(wsaData))
 {
  return 0;
 }
 // Create client socket  
 // 返回一个socket描述符,类似文件描述符,指针
 sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 协议族,socket类型,具体协议
 if(INVALID_SOCKET == sClient)
 {
  printf("Get Socket Error: INVALID_SOCKET.\n");
  return 0;
 }
 CheckBuffer(sClient);
 
 // Connect to server  
 memset(&server, 0, sizeof(SOCKADDR_IN));
 // 网络socket三元组,网络类型,地址,端口
 server.sin_family = AF_INET;  
 server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);  
 server.sin_port = htons(PORT);  
 // 协议地址端口,来标识一个server进程,都要转换为网络字节顺序
 int nLen = sizeof(SOCKADDR_IN);// 大小为16 Byte,刚好是内存对齐模式,sockaddr也是16 Byte
 // 阻塞模式connect会阻塞程序,客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
 // 非阻塞会马上返回
 // connect时候会随机分配一个端口和地址给当前客户端网络进程,服务器会收到
 int nConnect = connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
 if(SOCKET_ERROR == nConnect)
 {
  printf("Socket connnect Error.\n");
  return 0;
 }

 while (TRUE)  
 {  
  printf("Send Msg:");  
  gets(szMessage);  
  // Send message  
  // 阻塞模式下:发送前会先检查发送缓存是否在发送数据,是等待,不在发送则检查内核发送缓存,比较大小,
  // 如果小于那么拷贝到发送缓存,否则等待。
  // 非阻塞模式下:先检查发送缓存是否在发送,是等待,不是马上拷贝发送,能拷贝多少就拷贝多少。
  // 拷贝到内核发送缓存出错,那么返回SOCKET_ERROR,等待或者拷贝的过程中网络断开也返回SOCKET_ERROR
  int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字符串长度不包含'\0'
  if(SOCKET_ERROR == nSendRes)
  {
   printf("Send Copy data kernel buffer is too small or network shutdown!\n");
  }

  // Receive message
  // 接收消息前会先检查发送缓存区,如果正在发送,那么等待发送缓冲区的数据发送完毕,期间网络出错返回SOCKET_ERROR.
  // 阻塞模式下:按上面检查,recv收到数据完毕(协议会把一整个TCP包接收完毕,大包会重组后才算完毕)才返回,没收到则一直等待。
  // 非阻塞模式下:按上面检查,recv没收到马上返回不会阻塞,收到等接收完毕才返回。
  // 返回值小于0的SOCKET_ERROR检查是否EAGAIN 接收期间网络断开,非阻塞下没有收到数据的返回10035错误码。
  // 返回值等于0,表示对方socket已经正常断开。
  // 返回值不等于请求的缓存值,那么再次接收。
  int nRecvRes = recv(sClient, szMessage, MSGSIZE, 0);  
  if(nRecvRes > 0)
  {
   szMessage[nRecvRes] = '\0';
   printf("Bytes receive : %s\n", szMessage);
  }
  else if(nRecvRes== 0 || (nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
  {
    printf("Connection Close.\n");
    break; // 调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
  }
  else if(nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
  {
     // 非阻塞类型返回
    continue;
  }
  else
  {
   printf("Unknow recv error code: %d\n", WSAGetLastError());
    break; //  调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
  }    
 }  
 // Clean up  
 closesocket(sClient);
 ReleaseWSAData();
 return 0;
}

1.select模型


#ifndef _SELECTMODEL_H_
#define  _SELECTMODEL_H_
#include <windows.h>
class SelectModel
{
public:
    static DWORD WINAPI  WorkerThread(LPVOID lpParameter);
    int Process();
};
#endif

/*
总结:第一个accept线程阻塞,第二个线程select,FD_ISSET非阻塞的等待时间来处理socket网络IO。
1. 第一线程accept会阻塞,只有一个服务端socket对应多个客户端Socket,服务器需要获得客户端的socket并可关闭它。
2. 第二线程select可以根据传入时间,如果是NULL那么是完全阻塞的,如果是0那么是完全非阻塞的。
注意:处理大于64个的情况,在accept时候分组和开辟多个线程,或者是线程内分组,总是可以处理好的;
     没有连接和延迟导致cpu不停空转情况,虽然不会导致cpu 100%,但是也可以通过延迟sleep来避免或者干脆不处理这种情况。
*/
#include "stdafx.h"
#include <winsock.h>
#include <stdio.h>
#include "SelectModel.h"
#define PORT       5150
#define MSGSIZE    8192
#pragma comment(lib, "ws2_32.lib")

int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];// FD是File Describle文件描述符,也就是socket文件描述符(句柄)

//在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,
//比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
//
//对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,
//在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.
//这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.

int SelectModel::Process()
{
    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);

    int opt =  1;
    if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
    {
        printf("setsockopt Failed.\n");
        return false;
    }
    bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
    // Listen
    listen(sListen, 3);

    // Create worker thread
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
    while (TRUE)
    {

        // Accept a connection,接收到连接才存放到数组里面,否则一直阻塞
        // 这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。
        // 一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。
        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 g_CliSocketArr
        g_CliSocketArr[g_iTotalConn++] = sClient;
    }

    return 0;
}


 DWORD  SelectModel::WorkerThread(LPVOID lpParam)
{

    int            i = 0;
    fd_set         fdread;
    int            ret = 0;
    struct timeval tv = {1, 0};// 1是阻塞等待1秒钟返回一次,后面的0是0毫秒
    char           szMessage[MSGSIZE];

    while (TRUE)
    {

        FD_ZERO(&fdread);//将fdread初始化空集
        for (i = 0; i < g_iTotalConn; i++)// 可以在这里分段处理64个,用以支持多于64个的连接select.
        {

            FD_SET(g_
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值