[笔记]windows网络编程之常见模型(下)

简介: [笔记]windows网络编程之常见模型(下)

完成端口模型

是什么?

利用线程池处理异步I/O请求,利用完成端口模型可以管理成百上千Socket。

可以把完成端口看成系统维护的一个队列,操作系统把重叠I/O操作完成的事件通知放到该队列中,因此称其为“完成”端口,当Socket被创建后,可以将其与一个完成端口联系起来。

一个应用程序可以创建多个工作线程用于处理完成端口上的通知事件,通常应该为每个CPU创建一个线程。

一个完成端口实际就是一个通知队列,操作系统把已经完成的重叠I/O请求的通知放到队列中,当某项IO操作完成后,系统会向服务端完成端口发送一个i/o完成数据包,此操作在系统内部完成,应用程序在收到I/o完成数据包后,完成端口队列的一个线程被唤醒,为客户端提供服务,服务完成后,该线程会继续在完成端口上等待.

为什么?

(1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。

(2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不?
    (3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,

怎么用?

服务端:

1.初始化windows socket 环境

2.创建完成端口对象completionPort

3.根据当前计算机CPU的数量创建工作线程,并将新建的完成端口对象CompletionPort作为线程的参数

4.创建监听SocketListen 并将其绑定到本地地址 端口

5.在while循环中处理来自客服端的请求连接,接收连接,并将得到的与客户端进行通信的socketAccept保存到PER_HANDLE_DATA结构体对象PerHandleData中.将SocketAccept与前面的端口completionPort关联

6.在socket accept上调用 WSARecv函数,异步接收socket上来自客户端的数据,WSARecv函数立即返回,此时socketAccept上不一定有客户端发送来的消息,在工作线程中会检测完成端口对象的状态,并接收来自客户端的数据,再将这些数据发送回客户端程序.

// CompletionPortServer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#define PORT 9990             // 监听的端口
#define DATA_BUFSIZE 8192     // 发送和接收消息的最大长度
#pragma comment(lib, "Ws2_32")
// 定义I/O操作的结构体
typedef struct                       
{
   OVERLAPPED Overlapped;         // 重叠结构
   WSABUF DataBuf;                  // 缓冲区对象
   CHAR Buffer[DATA_BUFSIZE];       // 缓冲区数组
   DWORD BytesSEND;                             // 发送字节数
   DWORD BytesRECV;                             // 接收的字节数    
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
// 套接字句柄结构体
typedef struct                      
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
// 服务器端工作线程
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);
int _tmain(int argc, _TCHAR* argv[])
{
  SOCKADDR_IN InternetAddr;             // 服务器地址
  SOCKET Listen;                        // 监听套接字
  SOCKET Accept;                        // 与客户端进行通信的套接字
  HANDLE CompletionPort;                  // 完成端口句柄
  SYSTEM_INFO SystemInfo;               // 获取系统信息(这里主要用于获取CPU数量)
  LPPER_HANDLE_DATA PerHandleData;      // 套接字句柄结构体
  LPPER_IO_OPERATION_DATA PerIoData;    // 定义I/O操作的结构体
  DWORD RecvBytes;                      // 接收到的字节数
  DWORD Flags;                          // WSARecv()函数中指定的标识位
  DWORD ThreadID;                     // 工作线程编号
  WSADATA wsaData;                      // Windows Socket初始化信息
  DWORD Ret;                            // 函数返回值
  // 初始化Windows Sockets环境
  if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
  {
    printf("WSAStartup failed with error %d\n", Ret);
    return -1;
  }
  // 创建新的完成端口
  if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
  {
    printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());
    return -1;
  }
  // 获取系统信息
  GetSystemInfo(&SystemInfo);   
   // 根据CPU数量启动线程
  for(int i = 0; i<SystemInfo.dwNumberOfProcessors * 2; i++)
  {
    HANDLE ThreadHandle;
    // 创建线程,运行ServerWorkerThread()函数            
    if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
     0, &ThreadID)) == NULL)
    {
       printf("CreateThread() failed with error %d\n", GetLastError());
       return -1;
    }      
    CloseHandle(ThreadHandle);
  }
  // 创建监听套接字
  if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
    WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
  {
    printf("WSASocket() failed with error %d\n", WSAGetLastError());
    return -1;
  }
  // 绑定到本地地址的9990端口
  InternetAddr.sin_family = AF_INET;
  InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  InternetAddr.sin_port = htons(PORT);
  if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
  {
    printf("bind() failed with error %d\n", WSAGetLastError());
    return -1;
  }   
  // 开始监听
  if (listen(Listen, 5) == SOCKET_ERROR)
  {
    printf("listen() failed with error %d\n", WSAGetLastError());
    return -1;
  }
   // 监听端口打开,就开始在这里循环,一有socket连上,WSAAccept就创建一个socket,
   // 这个socket 和完成端口联上
  while(TRUE)
  {
    // 等待客户连接
    if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
    {
      printf("WSAAccept() failed with error %d\n", WSAGetLastError());
      return -1;
    }
    // 分配并设置套接字句柄结构体
    if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
    {
      printf("GlobalAlloc() failed with error %d\n", GetLastError());
      return -1;
    }   
    PerHandleData->Socket = Accept;
    // 将与客户端进行通信的套接字Accept与完成端口CompletionPort相关联
    if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
        0) == NULL)
    {
      printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
      return -1;
    }
    // 为I/O操作结构体分配内存空间
    if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)
    {
      printf("GlobalAlloc() failed with error %d\n", GetLastError());
      return -1;
    }
    // 初始化I/O操作结构体
    ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
    PerIoData->BytesSEND = 0;
    PerIoData->BytesRECV = 0;
    PerIoData->DataBuf.len = DATA_BUFSIZE;
    PerIoData->DataBuf.buf = PerIoData->Buffer;
    Flags = 0;
    // 接收数据,放到PerIoData中,而perIoData又通过工作线程中的ServerWorkerThread函数取出,
    if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
      &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
    {
      if (WSAGetLastError() != ERROR_IO_PENDING)
      {
        printf("WSARecv() failed with error %d\n", WSAGetLastError());
        return -1;
      }
    }
  }
  return 0;
}
//  工作线程,循环检测完成端口状态,获取PerIoData中的数据
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
  HANDLE CompletionPort = (HANDLE) CompletionPortID;  // 完成端口句柄   
  DWORD BytesTransferred;                   // 数据传输的字节数
  LPOVERLAPPED Overlapped;                  // 重叠结构体
  LPPER_HANDLE_DATA PerHandleData;          // 套接字句柄结构体
  LPPER_IO_OPERATION_DATA PerIoData;        // I/O操作结构体
  DWORD SendBytes, RecvBytes;               // 发送和接收的数量
  DWORD Flags;                              // WSARecv()函数中的标识位
  while(TRUE)
  {
    // 检查完成端口的状态
    if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
      (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
    {
      printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
      return 0;
    }
    // 如果数据传送完了,则退出
    if (BytesTransferred == 0)
    {
      printf("Closing socket %d\n", PerHandleData->Socket);
      // 关闭套接字
      if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
      {
        printf("closesocket() failed with error %d\n", WSAGetLastError());
        return 0;
      }
      // 释放结构体资源
      GlobalFree(PerHandleData);
      GlobalFree(PerIoData);
      continue;
    }     
    // 如果还没有记录接收的数据数量,则将收到的字节数保存在PerIoData->BytesRECV中
    if (PerIoData->BytesRECV == 0)
    {
      PerIoData->BytesRECV = BytesTransferred;
      PerIoData->BytesSEND = 0;
    }
    else   // 如果已经记录了接收的数据数量,则记录发送数据量
    {
      PerIoData->BytesSEND += BytesTransferred;
    }
    // 将收到的数据原样发送回客户端
    if (PerIoData->BytesRECV > PerIoData->BytesSEND)
    {
      ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); //清0为发送准备
      PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
      PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;
      // 一个字节一个字节发送发送数据出去
      if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
          &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
      {
        if (WSAGetLastError() != ERROR_IO_PENDING)
        {
          printf("WSASend() failed with error %d\n", WSAGetLastError());
          return 0;
        }
      }
    }
    else
    {
      PerIoData->BytesRECV = 0;
      Flags = 0;
      ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
      PerIoData->DataBuf.len = DATA_BUFSIZE;
      PerIoData->DataBuf.buf = PerIoData->Buffer;
      if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
        &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
      {
        if (WSAGetLastError() != ERROR_IO_PENDING)
        {
          printf("WSARecv() failed with error %d\n", WSAGetLastError());
          return 0;
        }
      }
    }
   }
}

具体参照
《windows网络编程》第八章 8.7 基于完成端口模型的服务器编程

完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三

单播,组播,多播

是什么?

单播,1 对 1

组播,1 对 多

广播,1 对 局域网所有

为什么?

单播,基于tcp,建立可靠连接,需要c/s ip;

组播,基于udp,监听同一ip(224.0.0.0至239.255.255.255)的一组都会接 收到消息;

广播,基于udp,监听 地址:xx.xx.xx.255 都会接收到消息

用途

单播用于连接建立后的私密通讯,组播和广播用于通知,建立连接前的通讯,获取ip。

安全套接字协议SSL

是什么?

SSL 可以用来保障在internet上数据传输的安全,利用数据加密技术,可确保数据在网络上的传输过程不会被截取及其监听.

SSL用于在web服务器和浏览器之间建立加密连接的标准安全技术.

SSL协议提供的安全信道有以下三个特性:

私密性。因为在握手协议定义了会话密钥后,所有的消息都被加密。

确认性。因为尽管会话的客户端认证是可选的,但是服务器端始终是被认证的。

可靠性。因为传送的消息包括消息完整性检查(使用MAC)。

主要工作在应用层(http,ftp,telnet)和 传输层(tcp/udp)之间的

参考

图解安全套接字SSL协议的工作原理

安全套接层(SSL)协议

简单了解:Openssl开源安全套接字协议

<<windows网络编程代码>>以及广播代码

百度网盘代码 提取码:72y7

相关文章
|
2月前
|
C++
基于Reactor模型的高性能网络库之地址篇
这段代码定义了一个 InetAddress 类,是 C++ 网络编程中用于封装 IPv4 地址和端口的常见做法。该类的主要作用是方便地表示和操作一个网络地址(IP + 端口)
162 58
|
2月前
|
网络协议 算法 Java
基于Reactor模型的高性能网络库之Tcpserver组件-上层调度器
TcpServer 是一个用于管理 TCP 连接的类,包含成员变量如事件循环(EventLoop)、连接池(ConnectionMap)和回调函数等。其主要功能包括监听新连接、设置线程池、启动服务器及处理连接事件。通过 Acceptor 接收新连接,并使用轮询算法将连接分配给子事件循环(subloop)进行读写操作。调用链从 start() 开始,经由线程池启动和 Acceptor 监听,最终由 TcpConnection 管理具体连接的事件处理。
60 2
|
2月前
基于Reactor模型的高性能网络库之Tcpconnection组件
TcpConnection 由 subLoop 管理 connfd,负责处理具体连接。它封装了连接套接字,通过 Channel 监听可读、可写、关闭、错误等
76 1
|
2月前
|
JSON 监控 网络协议
干货分享“对接的 API 总是不稳定,网络分层模型” 看电商 API 故障的本质
本文从 OSI 七层网络模型出发,深入剖析电商 API 不稳定的根本原因,涵盖物理层到应用层的典型故障与解决方案,结合阿里、京东等大厂架构,详解如何构建高稳定性的电商 API 通信体系。
|
2月前
基于Reactor模型的高性能网络库之Poller(EpollPoller)组件
封装底层 I/O 多路复用机制(如 epoll)的抽象类 Poller,提供统一接口支持多种实现。Poller 是一个抽象基类,定义了 Channel 管理、事件收集等核心功能,并与 EventLoop 绑定。其子类 EPollPoller 实现了基于 epoll 的具体操作,包括事件等待、Channel 更新和删除等。通过工厂方法可创建默认的 Poller 实例,实现多态调用。
198 60
|
2月前
基于Reactor模型的高性能网络库之Channel组件篇
Channel 是事件通道,它绑定某个文件描述符 fd,注册感兴趣的事件(如读/写),并在事件发生时分发给对应的回调函数。
156 60
|
2月前
|
安全 调度
基于Reactor模型的高性能网络库之核心调度器:EventLoop组件
它负责:监听事件(如 I/O 可读写、定时器)、分发事件、执行回调、管理事件源 Channel 等。
176 57
|
2月前
基于Reactor模型的高性能网络库之时间篇
是一个用于表示时间戳(精确到微秒)**的简单封装类
127 57
|
26天前
|
算法 安全 网络安全
【多智能体系统】遭受DoS攻击的网络物理多智能体系统的弹性模型预测控制MPC研究(Simulink仿真实现)
【多智能体系统】遭受DoS攻击的网络物理多智能体系统的弹性模型预测控制MPC研究(Simulink仿真实现)
|
4月前
|
机器学习/深度学习 搜索推荐 PyTorch
基于昇腾用PyTorch实现CTR模型DIN(Deep interest Netwok)网络
本文详细讲解了如何在昇腾平台上使用PyTorch训练推荐系统中的经典模型DIN(Deep Interest Network)。主要内容包括:DIN网络的创新点与架构剖析、Activation Unit和Attention模块的实现、Amazon-book数据集的介绍与预处理、模型训练过程定义及性能评估。通过实战演示,利用Amazon-book数据集训练DIN模型,最终评估其点击率预测性能。文中还提供了代码示例,帮助读者更好地理解每个步骤的实现细节。

热门文章

最新文章