楽しい小さな機能がすべて〜
序文
前回の記事では、udpサーバーの実装手順を詳しく解説し、使用する各インターフェースについても詳しく説明しましたが、前回は簡単なネットワーク通信機能のみで、不完全な部分も多かったので、今回は、前の記事で使用したコードを変更して、英語と中国語の翻訳のオンライン バージョンと大規模なオンライン チャット ルームを作成します。
1. udpサーバーによりインターネット上でオンライン英中翻訳を実現
記事ではデータ処理の部分が抜けているので、最初にファイルの処理方法を設計しましょう。
まず、ラッパーを使用して関数の型を定義します。
このコードの意味は、戻り値が void、パラメータが string、uint16_t、string であるラッパーの名前を func_t に変更することであり、データを処理するときにポート番号や IP などを使用することが目的です。 .パラメータ。次に、クラス内に関数型のオブジェクトを定義します。
次に、ユーザーがサーバーを作成するときに関数メソッドを渡す必要があります。目的は、将来サーバーにこのメソッドを実行させることなので、コンストラクターで初期化します。
udpServer(const func_t &cb,const uint16_t& port,const string ip = defaultIp)
:_port(port)
,_ip(ip)
,_sockfd(-1)
,_callback(cb)
{
}
次に、サーバーの起動の最後にサーバーにコールバック メソッドを呼び出させます。現時点では、サーバーにクライアントの IP アドレスとポート番号、および送信されたデータをコールバック メソッドに渡すだけです。
次に、コールバック メソッドをserver.cc ファイルに追加し、サーバーの作成時にこのメソッドをサーバーに渡します。
void handerMessage(string clientip,uint16_t clientport,string message)
{
//就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
}
// ./udpServer port
int main(int argc,char* argv[])
{
if (argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<udpServer> usvr(new udpServer(handerMessage,port));
usvr->InitServer();
usvr->start();
return 0;
}
以下では、map を使用して英語と中国語の間のマッピング関係を確立します。
まず、英語と中国語の対応関係をファイルに保存する辞書を作成しましょう。
テストのためだけだったので、5 つの単語しか書きませんでした。次のステップは、ファイル内のデータをマップに読み取ることです。ファイルを読み取るには C++ ファイルのインターフェイスを使用する必要があるため、最初に静的変数を定義します作成したファイルを保存します。辞書へのパス:
次に、辞書インターフェイスを初期化するコードを記述します。
static void initDict()
{
ifstream in(dictText,ios::binary);
if (!in.is_open())
{
cerr<<" open file "<<dictText<<" error "<<endl;
exit(OPEN_ERR);
}
string line;
while (getline(in,line))
{
cout<<line<<endl;
}
in.close();
}
まず、ファイルのオープン方法をバイナリに設定し、ファイルが正常にオープンしたかどうかを判定し、失敗した場合はエラーを出力し、エラーコードを返します。ファイルも追加されます。
enum
{
USAGE_ERR = 1
,SOCKET_ERROR
,BIND_ERR
,OPEN_ERR
};
次に、文字列オブジェクトを使用してファイルに読み取られたデータを文字列オブジェクトに入れ、getline を使用してファイル内のデータの各行を取得します。getline は行を取得する関数であることに注意してください。次に、ファイルを正しく読み取れるかどうかを実験してみましょう。最初にファイルを印刷し、最後にファイルを閉じることを忘れないでください。
main 関数では、最初にサーバーを起動せず、ファイルが正常に開けるかどうかだけをテストします。問題がないことがわかります。次に、getline で取得した文字列をマップに挿入します。
static bool cutString(string& line, string* s1, string* s2,const string& flag)
{
size_t sz = flag.size();
size_t pos = line.find(flag);
if (pos!=string::npos)
{
*s1 = line.substr(0,pos);
*s2 = line.substr(pos+sz);
return true;
}
return false;
}
static void initDict()
{
ifstream in(dictText,ios::binary);
if (!in.is_open())
{
cerr<<" open file "<<dictText<<" error "<<endl;
exit(OPEN_ERR);
}
string line;
string key,value;
while (getline(in,line))
{
//cout<<line<<endl;
if (cutString(line,&key,&value,":"))
{
_dict.insert(make_pair(key,value));
}
}
in.close();
}
挿入したい場合は、読み取った文字列を英語と中国語に分割する必要があります。辞書に記入するときに「:」を使用して分割したため、文字列を切り取ってパラメータ、キーと値を渡す関数を設計できます。出力パラメータ。空のキーと値を渡すと、インターフェイスは処理されたキーと値を返します。同時にセパレータも渡すことができるため、後で変更するときにパラメータを直接変更できます。次に、マップが正常かどうかをテストする印刷インターフェイスを作成します。
static void debugDict()
{
for (auto& dt:_dict)
{
cout<<dt.first<<" # "<<dt.second<<endl;
}
}
テスト後、問題がないことがわかりました。次に、サーバーがコールバック関数を実行するコードの作成を開始します。
void handerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
//就可以对message进行特定的业务处理,而不关心message怎么来的---------server通信和业务逻辑解耦
auto it = _dict.find(message);
string response_message;
if (it == _dict.end())
{
cout << "Server notfind message" << endl;
response_message = "字典内无此单词的中文";
}
else
{
response_message = it->second;
}
// 构建结构体,把处理过的数据再返回
struct sockaddr_in client;
socklen_t len = sizeof(client);
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
// 构建好结构体后,我们要把处理的数据发给谁呢?当然是客户端了,客户端给我们发数据我们再将处理后的数据发回给客户端
sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, len);
}
まず、サーバーがコールバック関数に入った後、最初にユーザーが送信したメッセージを処理する必要があります。メッセージが見つからない場合は、メッセージが見つからないことを出力します(ここでは、サーバーとクライアントが最初に出力されます)。同時に、サーバーに出力する目的は、プログラマが入力されていない単語を追加できるようにすることです)。それが見つかった場合は、マップ内のメッセージの値を取得し、それから返す必要があります。処理されたメッセージをクライアントに送信します (注: その単語に対応する中国語が見つかるかどうかに関係なく、クライアントにデータを返さなければなりません)。クライアントの IP とポート番号、およびファイル記述子を使用する必要があるため、 sendto 関数は返す必要があるため、コールバック関数はファイル記述子を受け取るための追加パラメーターを追加する必要があります。
次に、前と同じように構造を構築し、その構造にクライアントの IP とポート番号を入力し、sendto インターフェイスを通じてその構造をクライアントに返すことができます。これは、処理されたメッセージが文字列で受信されるためです。そのため、 c_str() インターフェイスと、sendto の 2 番目と 3 番目のパラメータとしての文字列のサイズ。
これでサーバーがクライアントにメッセージを送り返すコードを作成できました。もちろん、クライアントがサーバーからメッセージを受信するコードを作成する必要があるため、クライアント コードを変更します。
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);
string message;
char buffer[1024];
while (!_quit)
{
cout<<"Please Enter# ";
cin>>message;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
if (n > 0)
{
buffer[n] = 0;
}
cout<<"服务器的翻译结果# "<<buffer<<endl;
}
}
上記コードは主にデータ受信部分を変更しておりません データを受信するにはrecvfrom関数が必要です この関数にはファイルディスクリプタ(受信データがどのバッファに存在するか)、受信データの種類、用意した空の構造体が必要です. 、この関数の機能は次のとおりです: サーバーがメッセージを送信すると、このインターフェイスがサーバーのソケット情報を作成した新しい構造に埋め込みます。そのため、構造を埋める必要はありません。stand up を実行しましょう。
実行結果を見ると、プログラムに問題がないことが分かります もちろん、辞書ファイルのホットロードにも対応しています。これは、新しく追加された辞書をプログラムが終了することなくロードできることを意味します。これは、ゲームのノンストップ アップデートに似ています。設計原理も非常にシンプルです。終了信号を直接キャプチャします。終了すると、終了ロジックは実行されなくなり、辞書を初期化するロジックが実行されます。試してみましょう:
まず、main 関数で 2 番の信号をキャプチャし、次に reload メソッドを実行して、再度 reload を記述します。
void reload(int signo)
{
(void)signal;
initDict();
}
まず前のコードを再実行してから、辞書に単語を追加して、正常に翻訳できるかどうかを確認してみましょう。
Goodman を直接追加しましょう。
次に、2 番目のシグナルをサーバーに送信します。2 番目のシグナルは、一般的に使用される Ctrl + C です。
実行結果から、単純なホット ロードが正常に実装されたことがわかります。
2、udpサーバーはネットワーク遠隔操作を実現します
次に、小さな関数を実装します。つまり、Linux でコマンドをサーバーに送信し、サーバーがコマンドを実行して実行結果を返します。実装は難しくありません。コールバック関数を変更するだけです。それでは、試してみましょう。
まず、インターフェイス Popen を認識します。
Popen 関数は、実際には、以前に使用した 3 つのインターフェイス (pipe + fork + exec*) を組み合わせたものです。前に実装した、これら 3 つのインターフェイスで完了したコマンド ライン解析をまだ覚えていますか。以下で Popen について話しましょう:
最初のパラメータは、将来実行したいコマンド文字列です (例: pwd、ls コマンドと同じ、コマンドの実行方法) 実際、popen の最下層は fork 用のサブプロセスを作成します。パイプラインを介して最後にファイルを実行プログラムに置き換え、結果を返します。ファイルの r パラメータや w パラメータなど、開く 2 番目のパラメータのタイプ。
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
string response;
FILE* fp = popen(cmd.c_str(),"r");
if (fp==nullptr) response = cmd + " exec failed";
char line[1024];
while (fgets(line,sizeof(line)-1,fp))
{
response+=line;
}
pclose(fp);
}
まず返すための文字列クラスを作成し、読み取り専用でpopenインターフェイスを呼び出します。呼び出しが失敗した場合は、返された文字列に呼び出し失敗の情報を追加します。成功した場合は、バッファを定義してループします。 Popen 関数によって返されたコマンド操作の結果を読み取り、結果を取得した後にファイルを閉じることができます。以下は前と同じです。結果をクライアントに返す必要があるため、構造体を作成する必要があります。
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
string response;
FILE* fp = popen(cmd.c_str(),"r");
if (fp==nullptr) response = cmd + " exec failed";
char line[1024];
while (fgets(line,sizeof(line)-1,fp))
{
response+=line;
}
pclose(fp);
struct sockaddr_in client;
bzero(&client,sizeof(client));
socklen_t len = sizeof(client);
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str());
sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,len);
}
もちろん、安全のため、先頭にある rm などの危険なコマンドを判断して回避する必要があります。なぜなら、私たちが作成したサーバーには、独自の Linux を操作するために複数のクライアントが存在する可能性があるためです。そのため、悪意のある人が私たちに rm やその他のコマンドを与えることは避けなければなりません。 :
もちろん、テスト後もまだ小さな問題が残っています。つまり、クライアントで入力を行うときに cin を使用しますが、スペースに遭遇すると cin が停止し、ls -a -l のようなコマンドを入力する方法がありません。したがって、元の入力操作を変更します。
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);
string message;
char buffer[1024];
while (!_quit)
{
/* cout<<"Please Enter# ";
cin>>message; */
char commandbuffer[1024];
fgets(commandbuffer,sizeof(commandbuffer)-1,stdin);
message = commandbuffer;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
if (n > 0)
{
buffer[n] = 0;
}
//cout<<"服务器的翻译结果# "<<buffer<<endl;
cout<<"服务器的执行结果# "<<buffer<<endl;
}
}
fgets を使って入力する場合はスペースの問題を考慮する必要がないので、以下のように実行してみます。
実行後、バッファに問題があることがわかりました。ファイルを作成するためにタッチすると情報が返されないはずですが、最後のバッファの情報が返されたので、改善できます。サーバーから送信されない コマンドの結果が表示される場合は、コマンドに結果が無いことを意味します touchと同様、作成後に自分で確認する必要があるため、バイト数かどうかを判断するだけで済みますサーバーから送信されたメッセージが終了すると返される値は 0 です。0 の場合はバッファをクリアします。
上の図では、temp 構造体の埋め込みが間違っています。これは、recvfrom インターフェイスが一時構造体を埋めるためであり、埋め込む必要はありません。
次に、再コンパイルします。
実行結果は問題なく、2 つの単純な小さな機能が実現されていることがわかります。次の記事では、udp サーバーを使用して大規模なネットワーク チャット ルームを作成します。これら 3 つの例を通して、皆さんは次のことを信じています。 udpサーバーを深く把握できます。