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

この記事は、私の記事「SSLをサポートする最も単純なクロスプラットフォームサーバー続きです。

したがって、さらに読むには、前の記事の少なくとも一部を読むことをお勧めします。 しかし、気に入らない場合は、簡単な要約を以下に示します。OpenSSLソースからサンプルファイル「serv.cpp」を取得し、クライアントから1文字を受信できるシンプルなクロスプラットフォームサーバーにしました。

今、私はさらに進んでサーバーを強制したい:

1.ブラウザからhttpヘッダー全体を受信します。

2. httpヘッダーが表示されるブラウザーにhtmlページを送信します。

3.さらに、ソケットがサーバープロセスをブロックしないようにするため、ソケットをいわゆる「非ブロックモード」に転送します。



始めるには、前の記事で修正したserv.cppファイルが必要です。

最初に行うことは、ソケットを非ブロックモードにするクロスプラットフォームマクロを作成することです。



このコード行

#ifndef WIN32 #define closesocket close #endif
      
      







以下に変更します。

 #ifdef WIN32 #define SET_NONBLOCK(socket) \ if (true) \ { \ DWORD dw = true; \ ioctlsocket(socket, FIONBIO, &dw); \ } #else #include <fcntl.h> #define SET_NONBLOCK(socket) \ if (fcntl( socket, F_SETFL, fcntl( socket, F_GETFL, 0 ) | O_NONBLOCK ) < 0) \ printf("error in fcntl errno=%i\n", errno); #define closesocket(socket) close(socket) #endif
      
      







できた! さて、「リスニング」ソケットを非ブロッキングモードにするには、ラインの直後で十分です。

 listen_sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR(listen_sd, "socket");
      
      







行を挿入:

 SET_NONBLOCK(listen_sd);
      
      







これで、「リスニング」ソケットは非ブロッキングになり、accept関数は呼び出しの直後にプログラムに制御を返します。

ソケット記述子の代わりに、acceptは(-1)を返すようになりました。

したがって、非ブロッキングモードでは、ソケットハンドルを返すまで無限ループでaccept関数を呼び出す必要があります



  int sd = -1; while(sd == -1) { Sleep(1); #ifdef WIN32 sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len); #else sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len); #endif }
      
      





プログラムがプロセッサの100%をロードしないように、ループにSleep(1)を追加しました。 Windowsでは、これは1ミリ秒の中断を意味します。 これをLinuxで機能させるには、ファイルの先頭に次を追加します。



 #ifndef WIN32 #define Sleep(a) usleep(a*1000) #endif
      
      







理論的には、無限ループの代わりに、select関数とより強力な対応する関数を使用して、listen_sdソケットが読み取り可能になるまで待機し、acceptを1回だけ呼び出すことができます。 しかし、個人的には、サイクル方法に特別な欠陥はありません。



したがって、クライアントが接続すると、プログラムはループを終了します。 理論上はsdソケットは自動的に非ブロッキングになるはずですが、実際には、信頼性のためにはループの最後にマクロを呼び出す方が良いことが示されています

 SET_NONBLOCK(sd);
      
      







クライアントとの通信用のソケットが非ブロッキングであるため、関数

 err = SSL_accept (ssl);
      
      





プロセスを中断しませんが、値がerr = SSL_ERROR_WANT_READまたはSSL_ERROR_WANT_WRITEの呼び出しの直後に戻ります

暗号化されたメッセージを受信するには、別の無限ループが必要です。



  while(1) { Sleep(1); err = SSL_accept (ssl); const int nCode = SSL_get_error(ssl, err); if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE)) break; } CHK_SSL(err);
      
      







プログラムがこのサイクルを終了する場合にのみ、暗号化された接続が確立され、メッセージの送受信を開始できることを確認できます。

ブラウザーを使用してサーバーに接続するため、クライアントメッセージはhttpヘッダーとリクエスト本文で構成されます。

この場合、httpヘッダーは文字列「\ r \ n \ r \ n」で終わる必要があります。

サーバーが最初の文字だけでなくhttpヘッダー全体を読み取るようにコードを修正します。



コードを短縮するには、素晴らしいSTLライブラリを使用することをお勧めします。

1. 3つのヘッダーファイルを追加します。

 #include <vector> #include <string> #include <sstream>
      
      







2.行を置き換えます



  err = SSL_read (ssl, buf, sizeof(buf) - 1); CHK_SSL(err); buf[err] = '\0'; printf ("Got %d chars:'%s'\n", err, buf);
      
      







次のコードに:



  std::vector<unsigned char> vBuffer(4096); //     memset(&vBuffer[0], 0, vBuffer.size()); //   size_t nCurrentPos = 0; while (nCurrentPos < vBuffer.size()-1) { err = SSL_read (ssl, &vBuffer[nCurrentPos], vBuffer.size() - nCurrentPos - 1); //        if (err > 0) { nCurrentPos += err; const std::string strInputString((const char *)&vBuffer[0]); if (strInputString.find("\r\n\r\n") != -1) //   http ,     break; continue; } const int nCode = SSL_get_error(ssl, err); if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE)) break; }
      
      





このサイクルでは、httpヘッダー「\ r \ n \ r \ n」の末尾の文字を受信するまで、またはバッファスペースがなくなるまで、サーバーはクライアントからデータを読み取ります。

バッファをstd :: vectorとして割り当てると便利です。その長さを覚えるために別の変数が必要ないからです。

ループを終了した後、httpヘッダー全体と、場合によってはリクエスト本文の一部をバッファーに保存する必要があります。



3. htmlページをブラウザーに送信します。ブラウザーには、リクエストのhttpヘッダーが書き込まれます。

文字列を置換

 err = SSL_write (ssl, "I hear you.", strlen("I hear you.")); CHK_SSL(err);
      
      







次のコードに:

  //      const std::string strInputString((const char *)&vBuffer[0]); // html     const std::string strHTML = "<html><body><h2>Hello! Your HTTP headers is:</h2><br><pre>" + strInputString.substr(0, strInputString.find("\r\n\r\n")) + "</pre></body></html>"; //    http  std::ostringstream strStream; strStream << "HTTP/1.1 200 OK\r\n" << "Content-Type: text/html; charset=utf-8\r\n" << "Content-Length: " << strHTML.length() << "\r\n" << "\r\n" << strHTML.c_str(); //    . nCurrentPos = 0; while(nCurrentPos < strStream.str().length()) { err = SSL_write (ssl, strStream.str().c_str(), strStream.str().length()); if (err > 0) { nCurrentPos += err; continue; } const int nCode = SSL_get_error(ssl, err); if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE)) break; }
      
      







ノンブロッキングソケットがあるため、最初に回答が完全に送信される保証はありません。 したがって、SSL_writeをループで呼び出す必要があります。

以上です。 これでサーバーを起動し、ブラウザに入力できます localhost:1111





応答として、ブラウザはhttpリクエストを含むページを表示します。



アーカイブ3_.3s3s.orgの Visual Studio 2012のプロジェクト。

Linuxでコンパイルするには、「ca-cert.pem」および「serv.cpp」ファイルをアーカイブから1つのディレクトリにコピーし、コンパイラーを実行します。「g ++ -L / usr / lib -lssl -lcrypto serv.cpp」



PS: この記事の続きを書いた



All Articles