[笔记]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

相关文章
|
1月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
113 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
1月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
63 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
1月前
|
机器学习/深度学习 数据可视化 Windows
深度学习笔记(七):如何用Mxnet来将神经网络可视化
这篇文章介绍了如何使用Mxnet框架来实现神经网络的可视化,包括环境依赖的安装、具体的代码实现以及运行结果的展示。
54 0
|
1月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于BP神经网络的苦瓜生长含水量预测模型matlab仿真
本项目展示了基于BP神经网络的苦瓜生长含水量预测模型,通过温度(T)、风速(v)、模型厚度(h)等输入特征,预测苦瓜的含水量。采用Matlab2022a开发,核心代码附带中文注释及操作视频。模型利用BP神经网络的非线性映射能力,对试验数据进行训练,实现对未知样本含水量变化规律的预测,为干燥过程的理论研究提供支持。
|
11天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
36 2
|
12天前
|
运维 网络协议 算法
7 层 OSI 参考模型:详解网络通信的层次结构
7 层 OSI 参考模型:详解网络通信的层次结构
33 1
|
1月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
66 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
1月前
|
网络协议 前端开发 Java
网络协议与IO模型
网络协议与IO模型
网络协议与IO模型
|
1月前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
59 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
|
1月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
74 1
目标检测笔记(一):不同模型的网络架构介绍和代码
下一篇
无影云桌面