この記事は、私の記事
「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);
このサイクルでは、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);
次のコードに:
ノンブロッキングソケットがあるため、最初に回答が完全に送信される保証はありません。 したがって、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:
この記事の続きを書いた