日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

高性能服务器 - window篇

發布時間:2024/4/11 windows 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高性能服务器 - window篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最初研究網狐是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篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。