ノンブロッキングソケットを備えたクロスプラットフォームhttpsサーバー。 パート3

この記事では、非ブロッキングソケットでのシングルスレッドhttpsサーバーの改善を続けています。 ソースコードへのリンクを含む以前の記事は、次の場所にあります。

SSLをサポートする最もシンプルなクロスプラットフォームサーバー

ノンブロッキングソケットを備えたクロスプラットフォームhttpsサーバー

ノンブロッキングソケットを備えたクロスプラットフォームhttpsサーバー。 パート2



この記事の最後に、Visual Studio 2012(Windows 8 64ビット)、g ++ 4.4(Linux 32ビット)、g ++ 4.6(Linux 64ビット)でテストしたサーバーのソースコードへのリンクがあります。 サーバーは、任意の数のクライアントからの接続を受け入れ、応答として要求ヘッダーを送信します。

しかし、前のコメントに対するいくつかのコメントへの回答から記事を始めます。





まず、コードの異常性について多くの否定的なフィードバックを受け取ったので、これからは「異常なプログラミング」というハブにも記事を掲載することにしました。

第二に、私はもう「チュートリアル」マークを付けないことに決めました。誰かが私の記事で何か新しいものを見つけますが、誰かはそれをアマチュアだと感じます。 私は気にしません...



今、私のプログラミングスタイルについて:

1.いくつかの理由により、ヘッダーファイルにコードを記述し続けます。

a)追加のジェスチャーなしでコードの全行数を知りたいので、私にとってはより便利です。

b)いつでも、テンプレートをクライアントまたはサーバーに固定したい場合がありますが、このためにすべてのコードを書き直したくありません。

私はできないので、最初にstlを教えてクリエイターのプログラミングを後押しし、server.hファイルの名前をserver.cppに変更すれば、すべてがうまくいくと確信している人は...



2. 1つの理由でコンストラクターに無限ループを残します。このアプローチは正しいと思います。 クラスが内部変数を変更する以外の何もしない場合、最も正しいことは、このクラスを1つの関数(コンストラクター)だけで公開することです。

もちろん、この場合はクラスがまったくなくてもかまいませんが、クラスの方が多少馴染みがあり、最初からグローバル関数も必要ありません。



3. 1つの理由で、memcpyの代わりにstd :: copyを使用しません:std :: copy is brake!



最後に、ソースコードをコンパイルするのが面倒ではなかったすべての人に感謝し、いくつかのエラーを指摘します。 私はそれらを考慮して修正しようとしました。



今、主なものについて。

前回の記事からサーバーをリクエストヘッダーの解析とファイルの配布のために最終的に準備するために、もう1つ追加します:無限ループの代わりに、ネットワークイベントを受動的に待機するために特別に設計された関数の使用を開始します。

WindowsとLinuxにはこのような機能がいくつかあります。Windowsではselectを、Linuxではepollを使用することをお勧めします。



epoll関数がWindowsに存在しないという問題があります。 すべてのシステムでコードの一貫性を保つために、epollがWindows上にあるかのようにサーバーコードを記述しましょう。



selectを使用したWindows向けのシンプルなepoll実装

1.「server.h」がある同じディレクトリからVisual Studioプロジェクトに2つの空のファイルを追加します。 ファイル:「epoll.h」および「epoll.cpp」。

2. epollドキュメントからepoll.hファイルに定数、構造、および関数の定義を転送します。

#ifndef __linux__ enum EPOLL_EVENTS { EPOLLIN = 0x001, #define EPOLLIN EPOLLIN EPOLLPRI = 0x002, #define EPOLLPRI EPOLLPRI EPOLLOUT = 0x004, #define EPOLLOUT EPOLLOUT EPOLLRDNORM = 0x040, #define EPOLLRDNORM EPOLLRDNORM EPOLLRDBAND = 0x080, #define EPOLLRDBAND EPOLLRDBAND EPOLLWRNORM = 0x100, #define EPOLLWRNORM EPOLLWRNORM EPOLLWRBAND = 0x200, #define EPOLLWRBAND EPOLLWRBAND EPOLLMSG = 0x400, #define EPOLLMSG EPOLLMSG EPOLLERR = 0x008, #define EPOLLERR EPOLLERR EPOLLHUP = 0x010, #define EPOLLHUP EPOLLHUP EPOLLRDHUP = 0x2000, #define EPOLLRDHUP EPOLLRDHUP EPOLLONESHOT = (1 << 30), #define EPOLLONESHOT EPOLLONESHOT EPOLLET = (1 << 31) #define EPOLLET EPOLLET }; /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */ #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */ #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */ #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */ typedef union epoll_data { void *ptr; int fd; unsigned int u32; unsigned __int64 u64; } epoll_data_t; struct epoll_event { unsigned __int64 events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); #endif
      
      







3. epoll.cppファイルにヘッダーと、ソケットとその状態が保存されるグローバル変数を追加します。

 #include "epoll.h" #include <map> #ifndef WIN32 #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #else #include <io.h> #include <Winsock2.h> #pragma comment(lib, "ws2_32.lib") #endif std::map<int, epoll_event> g_mapSockets;
      
      







4.最初の関数のコードを追加します。

 int epoll_create(int size) { return 1; }
      
      







ここで何が起こっていますか?

ドキュメントからわかる限り、元のLinuxコードは、epoll_createを呼び出すたびにソケット状態のファイルを作成します。 どうやらこれはマルチスレッドプロセスで必要です。

シングルスレッドプロセスがあり、ソケットを格納するために複数の構造は必要ありません。 したがって、私たちと一緒のepoll_createは「スタブ」です。



5. stlを使用して、メモリ内のソケットの追加と削除は基本です。

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { switch(op) { case EPOLL_CTL_ADD: case EPOLL_CTL_MOD: g_mapSockets[fd] = *event; return 0; case EPOLL_CTL_DEL: if (g_mapSockets.find(fd) == g_mapSockets.end()) return -1; g_mapSockets.erase(fd); return 0; } return 0; }
      
      







6.最後に、主なこと:selectを介して待機関数を実装します



 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) { if ((!events) || (!maxevents)) return -1; //      select fd_set readfds, writefds, exceptfds; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); //   int nFDS = 0; for (auto it=g_mapSockets.begin(); it != g_mapSockets.end(); ++it) { if (it->first == -1) continue; if (it->first > nFDS) nFDS = it->first; FD_SET(it->first, &readfds); FD_SET(it->first, &writefds); FD_SET(it->first, &exceptfds); } //   struct timeval tv; tv.tv_sec = timeout/1000; tv.tv_usec = timeout - tv.tv_sec*1000; //  nFDS++; select(nFDS, &readfds, &writefds, &exceptfds, &tv); //     ,     epoll int nRetEvents = 0; for (auto it=g_mapSockets.begin(); (it != g_mapSockets.end() && nRetEvents < maxevents); ++it) { if (it->first == -1) continue; if (!FD_ISSET(it->first, &readfds) && !FD_ISSET(it->first, &writefds) && !FD_ISSET(it->first, &exceptfds)) continue; memcpy(&events[nRetEvents].data, &it->second.data, sizeof(epoll_data)); if (FD_ISSET(it->first, &readfds)) events[nRetEvents].events |= EPOLLIN; if (FD_ISSET(it->first, &writefds)) events[nRetEvents].events |= EPOLLOUT; if (FD_ISSET(it->first, &exceptfds)) events[nRetEvents].events |= EPOLLERR; nRetEvents++; } return nRetEvents; }
      
      







以上です。 Windows用のepoll機能が実装されました!



サーバーへのepollの追加



1.ヘッダーに追加します。

 #ifdef __linux__ #include <sys/epoll.h> #else #include "epoll.h" #endif
      
      







2. CServerクラスに次の行を追加します。

  private: //   struct epoll_event m_ListenEvent; //   vector<struct epoll_event> m_events; int m_epoll;
      
      







3. CServerコンストラクターで、listen関数を変更した後のすべてが次のように変更されます。

  m_epoll = epoll_create (1); if (m_epoll == -1) { printf("error: epoll_create\n"); return; } m_ListenEvent.data.fd = listen_sd; m_ListenEvent.events = EPOLLIN | EPOLLET; epoll_ctl (m_epoll, EPOLL_CTL_ADD, listen_sd, &m_ListenEvent); while(true) { m_events.resize(m_mapClients.size()+1); int n = epoll_wait (m_epoll, &m_events[0], m_events.size(), 5000); if (n == -1) continue; Callback(n); }
      
      







4.古いCServer :: Callback関数を新しいものに置き換えます。

  void Callback(const int nCount) { for (int i = 0; i < nCount; i++) { SOCKET hSocketIn = m_events[i].data.fd; if (m_ListenEvent.data.fd == (int)hSocketIn) { if (!m_events[i].events == EPOLLIN) continue; struct sockaddr_in sa_cli; size_t client_len = sizeof(sa_cli); #ifdef WIN32 const SOCKET sd = accept (hSocketIn, (struct sockaddr*) &sa_cli, (int *)&client_len); #else const SOCKET sd = accept (hSocketIn, (struct sockaddr*) &sa_cli, (socklen_t *)&client_len); #endif if (sd != INVALID_SOCKET) { //      m_mapClients[sd] = shared_ptr<CClient>(new CClient(sd)); auto it = m_mapClients.find(sd); if (it == m_mapClients.end()) continue; //    epoll struct epoll_event ev = it->second->GetEvent(); epoll_ctl (m_epoll, EPOLL_CTL_ADD, it->first, &ev); } continue; } auto it = m_mapClients.find(hSocketIn); //    if (it == m_mapClients.end()) continue; if (!it->second->Continue()) // -   { //   false,     epoll     epoll_ctl (m_epoll, EPOLL_CTL_DEL, it->first, NULL); m_mapClients.erase(it); } } }
      
      







サーバークラスが終了すると、CClientクラスを処理します。

次のコードを追加します。

  private: //   struct epoll_event m_ClientEvent; public: const struct epoll_event GetEvent() const {return m_ClientEvent;}
      
      







そして、epollサポートコードの追加が行われます!



Visual Studioのプロジェクトは次のとおりです。l0.3s3s.org

Linuxでのコンパイルでは、ファイルepoll.hおよびepoll.cppは必要ありません。つまり、すべてが通常どおりです。「ファイルをコピーします:serv.cpp、server.h、ca-cert.pemを同じディレクトリに入れて、次のように入力します。」g ++ -std = c ++ 0x -L / usr / lib -lssl -lcrypto serv.cpp»„



継続



All Articles