ノンブロッキングソケットを備えたクロスプラットフォーム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: この記事の続きを書いた

Source: https://habr.com/ru/post/J211661/


All Articles