最初研究网狐是14年的时候,一转眼已经是18年了,这几年也做了写乱七八糟的开发,期间也做了些网络层的开发,自我感觉良好,最近做的项目主要负责服务器方面,CS架构的。一开始写了个CSocket简单的服务器,就是网上常见的结构,封装下,在继承下,记得13年在深蓝培训时候老师就是这样写的,测试的时候发现批量登录导出是BUG,经不起大规模的登录。估计主要是自己对MFC封装的CSocket了解不深,唉,这能换网络内核,之前也学了很多IOCP理论知识,也看了很多DEMO,可惜的是这些封装的都不是很好。这个时候想起国内知名VC++写的远控gh0st,也就是红狼远控,据说他的网络内核写的不错,自己也看过他的源码,3.6和3.78版本,像现在市面上常见的远控,DDOS管理端都是抄写gh0st的。我把他的网络层用到自己的项目上,刚开始还好,后来也发现了不少致命BUG,
1.他是自己写的CBuffer管理内存,其实这个类不是很安全,CopyMemery的时候就没有检查,出现了偶现的拷贝内存出界
2.有的时候莫名其妙的进入某个锁里面出不来了,导致服务器卡死
以上两个BUG很可能是自己不正确使用人家的IOCP模块导致,因为用人家的自己的项目的时候稳定的一B啊,用到自己项目就偶现崩溃能,花了1-2天时间不论自己怎么改都没解决以上两个BUG,也有可能这个模型他写的本身就有BUG,只是他自己的项目没有触发而已,因为gh0st这个项目服务器只会下发简单的指令数据,数据量很小,我自己的项目登录的时候服务器会下发几十K的数据给登录端,可能这样就会触发BUG了吧,只能这样帅锅了。由于项目还是挺紧的,没有足够时间查找原因只能赶紧换网络内核。这个时候我想到了网狐6603的IOCP,这个东西就是写的太规范了,导致简单的IOCP代码很大,附加的辅助类很多。我花了两天时间把它压缩成了一个精小版,

下面这个链接是我缩减之后的代码:

网狐IOCP压缩版-网络基础文档类资源-CSDN下载

注意:
1.由于不太会使用去掉了网络事件(收发数据、网络接受、网络断开)进队列,发的时候直接发送,接收的时候直接回调。不知道原作者都放进队列里面有哪些确切的好处。

暂时先这样,后续更新。。。

----------15:41 2018/6/27---------------

今天使用网狐的时候出现了死锁,偶现现象,真他妈吓我一跳。后经过定位,死锁在 pServerSocketItem->GetSignedLock()上面,一个线程收数据,一个线程发数据,两个都要获取这个锁,导致了死锁。如果把所有的收发数据都放进一个队列里面,让一个线程去处理收发数据肯定不会出现类似的错误,因为就一个线程嘛,很难死锁的。那为什么CServerSocketItem要有个锁呢?多线程都是操作一个Item,这些操作会访问Item有成员变量属性,肯定会乱,所以锁还有必要的。

----------10:13 2018/6/28----------------

想来想去还是加上了消息队列,去掉消息队列的话肯定是不稳定的。同时也加上了心跳。也公布出来吧,供大家使用。。

IOCP网络模型-网络基础代码类资源-CSDN下载

2.经过实验测试,这个服务器模型到1400个左右客户端,消耗内存140兆,就到达了上线,新上来的客户端登录不上去。唉,这个效果还没有gh0st上限高,gh0st上线3000个客户端松松的没压力,但是gh0st就是不稳定,看来还要寻找其他高效服务器。研究下boost的asio吧。

----------18:25 2018/6/23---------------

突发的猜想,网狐IOCP能接收的客户端不止1400多个,1400多个主要的原因是自己写的测试客户端开了1400多个线程,达到了上限,具体性能有待进一步测试。。。

----------9:46 2018/6/25---------------

经过实际测试,网狐IOCP一秒钟可以接受1000左右的链接,成功连接了20000台终端,消耗内存1.4G(由于本机是I3不方便更大量的测试,这个结果已经令人很满意)

3.找到一个不错的boost写的跨平台服务器框架,一秒钟可以接受1000左右的链接,成功连接了30000台终端(可能可以更多,测试机器WIN7+I7)。原文地址:https://blog.csdn.net/wang19840301/article/details/46648559

下载链接:跨平台高性能TCP服务器框架&boost;-其它文档类资源-CSDN下载

由于测试网狐的效果还不错,所以这个网络模型就不做进一步研究了,有需要的时候也是不错的选择。

4.压力测试代码

// TestClient.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"#include<WinSock2.h>
#pragma comment(lib,"WS2_32.lib")int g_id=0;
CRITICAL_SECTION cs;DWORD WINAPI clientfun(LPVOID lp)
{SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  if(s == INVALID_SOCKET)  {  printf("error");  ::WSACleanup(); //释放资源  return 0;  }  sockaddr_in servAddr;  servAddr.sin_family = AF_INET;  servAddr.sin_port = htons(10001);//端口号  servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP  EnterCriticalSection(&cs);  //连接  if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)  {  printf("%d-error\n",g_id);  ::WSACleanup(); //释放资源  return 0;  }else{printf("%d-success\n",g_id);  }g_id++;LeaveCriticalSection(&cs);char sendbuf[10]={0,'a','b',','};send(s,sendbuf,10,0);char buff[156];//缓冲区  int nRecv = ::recv(s, buff, 156, 0);//接收数据  if(nRecv > 0)  {  buff[nRecv] = '\0';  printf("接受数据:%s",buff);  }  return 0;}//单个进程推荐不超过1000个线程。超过1000个的压力测试量推荐启动多个进程
#define     CLIENT_COUNT        1int _tmain(int argc, _TCHAR* argv[])
{WSADATA wsaData;  WORD sockVersion = MAKEWORD(2,0);//指定版本号  ::WSAStartup(sockVersion, &wsaData);//载入winsock的dll  InitializeCriticalSection(&cs);for (int i=0;i<CLIENT_COUNT;i++){HANDLE h =  CreateThread(NULL,0,clientfun,NULL,0,NULL);}Sleep(5*60*1000);return 0;
}

5.更新日期 -------------------11:06 2018/8/14-------------------

有个疑惑:首先我们确定TCP协议是不会丢包的,我们假设接收端处理速度较慢,或者网速较慢,总而言之,发送端势必会造成数据堆积情况,send函数会失败,错误码为10035,亦即常见的错误10035(WSAEWOULDBLOCK),这个时候如果不处理这个错误这次发送肯定是没有达到接收方的,那不就是造成了丢包现象吗?
解答(仅仅代表自己的看法,可能会不太正确或准确):
使用重叠IO时候不会产生错误10035(WSAEWOULDBLOCK),当出现接受方处理数据较慢导致发送缓冲区已经满了的时候,会产生错误WSA_IO_PENDING(997),即他们两个错误是一个意思,只不过前者是非重叠IO的,后者是重叠IO的。TCP不会丢包是针对发送成功而言的(发送成功是指send函数返回非负值,或者返回失败但是错误码是WSAEWOULDBLOCK或者WSA_IO_PENDING),发送失败的数据包肯定不能到达接收端的。那么网狐是怎么处理这个事情的呢?经过阅读源码,总结如下:发送的数据会有一个队列即m_OverLappedSendActive,每次想要发送数据时候先放进队列里面,每次真正发送成功都会触发OnSendCompleted事件(GetQueuedCompletionStatus获取到的),底层发送成功才会调用下次发送,这样保证了上层调用send肯定是发送成功的(无论是网络阻塞或者接收端处理数据太慢都会成功,除非网络链接断开),如果一直没有触发OnSendCompleted事件,则上层调用的send,其实只是放进应用程序自己写的数据队列里面而已。

6.更新日期 -------------------14:11 2021/11/24-------------------

跟心压力客户端代码:

// TestClient.cpp : 定义控制台应用程序的入口点。
// 有个bug,发送数据大小是1000的时候,连接数最终只有几十个#include "stdafx.h"
#include <time.h>
#include <WinSock2.h>
#include <string>
#pragma comment(lib,"WS2_32.lib")std::string ip              ="192.168.10.27";
int         port            =10000;int     SENDSIZE            =1024;
int     CLIENT_COUNT        =1;
int     SENDSLEEP           =1000;int g_id=0;
CRITICAL_SECTION cs;DWORD WINAPI clientfun(LPVOID lp)
{SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  if(s == INVALID_SOCKET)  {  printf("error");  ::WSACleanup(); //释放资源  return 0;  }  sockaddr_in servAddr;  servAddr.sin_family = AF_INET;  servAddr.sin_port = htons(port);//端口号  servAddr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());//IP  EnterCriticalSection(&cs);  //连接  if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)  {  printf("%d-error - %d\n",g_id,WSAGetLastError());  ::WSACleanup(); //释放资源  exit(0);  }else{struct sockaddr_in connAddr;memset(&connAddr, 0, sizeof(struct sockaddr_in));int len = sizeof(connAddr);int ret = ::getsockname(s, (sockaddr*)&connAddr, &len);int port = ntohs(connAddr.sin_port);int opt =1;setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&opt,sizeof(opt));printf("序号%d - success - local port:%d\n",g_id,port);  }g_id++;LeaveCriticalSection(&cs);//mBdT//char sendbuf[10]={'m','B','d','T',0x00,0x00,0x00,0x02,'A','B'};//char sendbuf[10]={'A','B','\r','\n'};sssschar *sendbuf = new char[SENDSIZE];sendbuf[SENDSIZE-2]='\r';sendbuf[SENDSIZE-1]='\n';for(int i=0;i<SENDSIZE-2;i++) {sendbuf[i] = '0' + (i%10);}//小于10一般设置的是0.代表只做连接不发数据while (SENDSIZE >= 10){int sendsize = 0;for(int i=0; i<1; i++){int t = send(s,sendbuf,SENDSIZE,0);if (t <= 0){printf("%d-error - %d\n",g_id,WSAGetLastError());  exit(0);  }else{//printf("%d-send size - %d\n",g_id,sendsize+=t);  }}//while (1){char *buff = new char[SENDSIZE];//缓冲区memset(buff,'a',SENDSIZE);int nRecv = ::recv(s, buff, SENDSIZE, 0);//接收数据  if(nRecv > 0)  {  buff[nRecv] = '\0';  //printf("接受数据:%s\n",buff);  }delete [] buff;}Sleep(SENDSLEEP );}if (SENDSIZE >= 10)Sleep(60*60*1000);return 0;
}//可以等待WaitForMultipleObjects多64个的的API
DWORD SyncWaitForMultipleObjs(HANDLE * handles, size_t count)
{  int waitingThreadsCount = count;  int index = 0;  DWORD res = 0;  while(waitingThreadsCount >= MAXIMUM_WAIT_OBJECTS)  {  res = WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, &handles[index], TRUE, INFINITE);  if(res == WAIT_TIMEOUT || res == WAIT_FAILED)  {  puts("1. Wait Failed.");  return res;  }  waitingThreadsCount -= MAXIMUM_WAIT_OBJECTS;  index += MAXIMUM_WAIT_OBJECTS;  }  if(waitingThreadsCount > 0)  {  res = WaitForMultipleObjects(waitingThreadsCount, &handles[index], TRUE, INFINITE);  if(res == WAIT_TIMEOUT || res == WAIT_FAILED)  {  puts("2. Wait Failed.");  }  }  return res;
}  int _tmain(int argc, _TCHAR* argv[])
{if (argc == 6){ip = argv[1];port = atoi(argv[2]);SENDSIZE = atoi(argv[3]);CLIENT_COUNT = atoi(argv[4]);SENDSLEEP = atoi(argv[5]);}WSADATA wsaData;  WORD sockVersion = MAKEWORD(2,0);//指定版本号  ::WSAStartup(sockVersion, &wsaData);//载入winsock的dll  InitializeCriticalSection(&cs);HANDLE *m_hEvent = new HANDLE[CLIENT_COUNT]; clock_t start = clock();  for (int i=0;i<CLIENT_COUNT;i++){m_hEvent[i] =  CreateThread(NULL,0,clientfun,NULL,0,NULL);}SyncWaitForMultipleObjs(m_hEvent, CLIENT_COUNT);clock_t finish = clock();double  duration = (double)(finish - start) / CLOCKS_PER_SEC;  printf( "花费时间:%f 秒\n", duration );  Sleep(60*60*1000);return 0;
}

从以上程序我们可以看出,发送的数据不再任意的了,这是为了和一个叫hany的项目对接。项目地址:https://github.com/yedf/handy

对的,他是非windows高性能网络服务器(我这边主要在linux下做研究)。

在做服务器accept能力测试时,我一直以为这个数据在1000/s左右,后来用两个客户端同时连服务器,两个客户端同时在1秒内连上了1000左右的客户端,这样说来以前的测试就不太准确了。后来我查阅资料说是windows的客户端达到了瓶颈,于是有了下面的linux版本:

//编译命令 : g++ client.cpp -o client -lpthread#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <string>
#include <atomic>using namespace std;string      ip                          ="192.168.10.27";
int         port                        =10000;int         SENDSIZE            =1024;
int         CLIENT_COUNT        =1;
int         SENDSLEEP           =1000;atomic<int> g_thread_id;
void *workthread(void *arg) //线程函数
{//printf("thread %d run start\n", g_thread_id++);int   sockfd, n;struct sockaddr_in  servaddr;if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);exit(0);  }memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port);if( inet_pton(AF_INET, ip.c_str(), &servaddr.sin_addr) <= 0){printf("inet_pton error for %s\n",ip);exit(0);  }if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){printf("connect error: %s(errno: %d)\n",strerror(errno),errno);exit(0);  }if (SENDSIZE >= 10){char *sendbuf = new char[SENDSIZE];sendbuf[SENDSIZE-2]='\r';sendbuf[SENDSIZE-1]='\n';for(int i=0;i<SENDSIZE-2;i++) {sendbuf[i] = '0' + (i%10);}//小于10一般设置的是0.代表只做连接不发数据while (SENDSIZE >= 10){int sendsize = 0;for(int i=0; i<1; i++){int t = send(sockfd,sendbuf,SENDSIZE,0);if (t <= 0){printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);exit(0);  }else{//printf("%d-send size - %d\n",g_id,sendsize+=t);  }}//while (1){char *buff = new char[SENDSIZE];//缓冲区memset(buff,'a',SENDSIZE);int nRecv = ::recv(sockfd, buff, SENDSIZE, 0);//接收数据  if(nRecv > 0)  {  buff[nRecv] = '\0';  //printf("接受数据:%s\n",buff);  }delete [] buff;}sleep(SENDSLEEP);}}if (SENDSIZE >= 10)sleep(60*60);printf("thread %d run over\n", g_thread_id++);return 0;
}int main(int argc, char** argv)
{g_thread_id = 0;if (argc == 6){ip = argv[1];port = atoi(argv[2]);SENDSIZE = atoi(argv[3]);CLIENT_COUNT = atoi(argv[4]);SENDSLEEP = atoi(argv[5]);}clock_t start = clock();pthread_t id[CLIENT_COUNT] = {0};for (int i=0; i<CLIENT_COUNT; i++){int ret= pthread_create(&id[i],NULL,workthread,(void *)&i);if(ret){printf("pthread_create error: %s(errno: %d)\n", strerror(errno), errno);return 1;}pthread_detach(id[i]);}/*如果不调用pthread_detach,即可调用pthread_join,但是有个问题,连续创建1万5千个左右的线程就会pthread_create: Resource temporarily unavailable (errno = 11)for (int i=0; i<CLIENT_COUNT; i++){pthread_join(id[i], NULL);}clock_t finish = clock();double  duration = (double)(finish - start) / CLOCKS_PER_SEC;printf( "花费时间:%f 秒 run thread count %d\n", duration, g_thread_id++ );
*/sleep(60*60);return 0;
}

由于我在linux下写socket熟练度不是很高,当客户端达到2-3W时候总是报错:thread_create: Resource temporarily unavailable (errno = 11)

我看网上说是创建了进程没有及时进行jion或者detach,稍微改了下代码,依然没有解决问题。有的说是系统资源限制的原因,调了下系统参数,还是没搞定,知道的朋友麻烦给点信息。这个问题大概就是线程个数创建的有点多导致的,于是有了下面的go版本,不创建线程了,用协程~)~。

//go build client.go
package mainimport ("fmt""net""os""strconv""time"
)var       ip           string
var       port              int
var       SENDSIZE          int
var       CLIENT_COUNT      int
var       SENDSLEEP         intfunc work_coroutine() {var server stringserver = ipserver += ":"server += strconv.Itoa(port)conn, err := net.Dial("tcp", server)if err != nil {fmt.Println("net.Dial err : ", err)return}defer conn.Close()if (SENDSIZE >= 10) {sendbuf := make([]byte, SENDSIZE)sendbuf[SENDSIZE-2]='\r'sendbuf[SENDSIZE-1]='\n'i := 0for i < SENDSIZE-2 {sendbuf[i] = '0' + (byte)(i%10)i++}for {_, err := conn.Write([]byte(sendbuf))if err != nil {os.Exit(1)}recvbuf := make([]byte, SENDSIZE)n, err := conn.Read(recvbuf[:])if err != nil {fmt.Println("recv failed, err:", err, n)os.Exit(1)}time.Sleep(time.Duration(SENDSLEEP)*time.Millisecond)}}time.Sleep(time.Duration(10000)*time.Second)
}func main() {ip = os.Args[1]port,_ = strconv.Atoi(os.Args[2])SENDSIZE,_ = strconv.Atoi(os.Args[3])CLIENT_COUNT,_ = strconv.Atoi(os.Args[4])SENDSLEEP,_ = strconv.Atoi(os.Args[5])index := 0for index < CLIENT_COUNT {go work_coroutine()index++}time.Sleep(time.Duration(1)*time.Hour)
}

经过简单测试,linux的服务器epoll,一秒钟可以接收8000个左右的客户端。

上图为证(可能大部人不知道这是什么,这是handy运行日志,每31条日志,看到第四条到第五条增加了24000个连接,经历3秒,平均下来就是8000个/秒)

高性能服务器 - window篇相关推荐

  1. 构建高性能.NET应用之配置高可用IIS服务器-第二篇 IIS请求处理模型

    在IIS 中,Http监听者(http.sys)和请求处理者由两个系统服务在控制着.一个是WWW 服务,另外一个就是Windows Process Activation. 对于WWW服务,它主要是监控 ...

  2. Linux高性能服务器开发——进程篇

    本文主要是学习Linux高性能服务器开发需要提前了解的知识,后续还会涉及到虚拟内存方面的内容,各位看官可以多了解了解,看到文章内有将的不清楚或者讲错的地方请各位一定留言,我看到后会第一时间验证并修正的 ...

  3. 高性能服务器开发之C++定时器

    高性能服务器开发之C++定时器 来源: https://www.cnblogs.com/junye/p/5836552.html 写这篇文章前搜了下网上类似的文章,有很多,所以笔者的这篇文章就不对定时 ...

  4. Linux 高性能服务器编程——多线程编程

    问题聚焦:     在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:     1 创建线程和结束线程:     2 读取和设置线程属性:     3 线程同步方式:POSIX信号量,互斥锁和条 ...

  5. 完成端口与高性能服务器程序开发

    早在两年前我就已经能很熟练的运用完成端口这种技术了,只是一直没有机会将它用在什么项目中,这段时间见到这种技术被过分炒作,过分的神秘化,就想写一篇解释它如何工作的文章.想告诉大家它没有传说中的那么高深难 ...

  6. 高性能服务器中的C10K问题

    原文: The C10K problem 本文中文版翻译仅供我自己学习使用,翻译工作并未获得原作者的许可,请不要转载,感谢. ------------------------------------- ...

  7. 高性能服务器架构(High-Performance Server Architecture)

    原文: https://blog.csdn.net/marising/article/details/5186643 High-Performance Server Architecture 高性能服 ...

  8. 高性能服务器架构(High-Performance Server Architecture) .

    //  http://blog.csdn.net/bsplover/article/details/7498718 High-Performance Server Architecture 高性能服务 ...

  9. 【 C++ 技术】 C++ 高性能服务器网络框架设计细节

    作者:范蠡  原文:C++ 高性能服务器网络框架设计细节 前言 这篇文章我们将介绍服务器的开发,并从多个方面探究如何开发一款高性能高并发的服务器程序.需要注意的是一般大型服务器,其复杂程度在于其业务, ...

最新文章

  1. 使用Nginx-rtmp-module搭建hls直播
  2. Real VNC 5.1.1新增实用的技能:VNC Address Book
  3. Opne GL ES 学习心得!
  4. 使用python画图表_利用Python绘制数据的瀑布图的教程
  5. nga能查服务器状态吗,NGAA
  6. Android如何实现NoActionBar以及Theme.NoTitleBar.Fullscreen效果
  7. Android.View.InflateException: Binary XML File Line #异常的解决
  8. Centos 安装配置gerrit
  9. Nacos Spring 快速开始
  10. RecyclerView的ItemAnimator
  11. React Ant Design UI 图片上传组件 代码片段
  12. composer全局 linux_Linux下全局安装composer方法
  13. php中global与$_GLOBALS[]的区别
  14. 01. Django基础:Django介绍
  15. 【多媒体封装格式详解】---MKV【3】完
  16. 素数II题解(素数筛)
  17. 解决:Please either set ERLANG_HOME to point to your Erlang installation or place the RabbitMQ server d
  18. devicemapper介绍
  19. 机器学习常用算法的优缺点总结
  20. Autodesk Alias AutoStudio 2022 x64

热门文章

  1. SpringMVC的请求-文件上传-单文件上传的代码实现1
  2. vue指令-单向和双向绑定
  3. IDEA与tomcat相关配置
  4. springboot整合servlet
  5. SpringBoot新版
  6. 单例设计模式-静态内部类-基于类初始化的延迟加载解决方案及原理解析
  7. oracle xdb插件报错,注册XML Schema到ORACLE XDB并对XML进行验证
  8. leetcode-125-Valid Palindrome
  9. Jenkins Gitlab持续集成打包平台搭建
  10. SSIS技巧--优化数据流缓存