目次
TCP ネットワーク サーバーのステータスとポートの使用状況を表示する netstat -nltp
1.inet_aton (inet_addr と同じですが、別の方法です)
(1) strcasecmp - 2 つの文字列を比較しますが、大文字と小文字は区別されません。
(2) int isalpha(int c); ——それが文字であるかどうかにかかわらず、はいの場合は 1 を返し、そうでない場合は 0 を返します。
(3) int is lower(int c);——小文字かどうかに関係なく、小文字の場合は 1 を返し、そうでない場合は 0 を返します。
(4) int toupper(int c);——大文字に変換します。戻り値は変換された文字の値、または変換できなかった場合は c です。
2. サービスをマルチプロセス、マルチスレッドのバージョンとしてカプセル化します。
TCP ネットワーク サーバーのステータスとポートの使用状況を表示する netstat -nltp
[zsh@ecs-78471 vscode]$ netstat -nltp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:25 :::* LISTEN -
[zsh@ecs-78471 vscode]$
1. TCPソケットインターフェース
1.inet_aton (inet_addr と同じですが、別の方法です)
int inet_aton(const char *cp, struct in_addr *inp); (ネットローカル文字列スタイル IP からネットワーク 4 バイト IP へのアドレス) cp: 文字列スタイルの IP アドレス。inp: 変換されたデータは inp に格納されます。
戻り値: 成功した場合は 1 を返し、失敗した場合は 0 を返します。
注: (1) このタイプの関数は、 IP スタイルを変更するときに、ホストのバイト オーダーとネットワークのバイト オーダーの間で自動的に変換します。
(2)ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
手動で ./serverTcpd 8080 0 を渡すと、IP アドレスは 0 になります。その場合、inet_aton(0, &local.sin_addr) は、inet_aton の最初のパラメーターが 0 であり、値 0 を持つローカル文字列スタイルの IP が、 network 4 バイト IP は local.sin_addr に格納されます。INADDR_ANY マクロの値は 0 であるため、0 が文字列スタイルであるかネットワーク スタイルであるかは関係ありません。したがって、これは inet_aton(INADDR_ANY, &local. sin_addr));、これは local.sin_addr.s_addr = INADDR_ANY; でも同等です。
概要: inet_aton(ip_.c_str(), &local.sin_addr) ip_ が 0 の場合、inet_aton(0, &local.sin_addr) は local.sin_addr.s_addr = INADDR_ANY と等しくなります。
例:
struct sockaddr_in local; // 用户栈
memset(&local, 0, sizeof local);
local.sin_family = PF_INET;
local.sin_port = htons(port_);
ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (
inet_aton(ip_.c_str(), &local.sin_addr));
2.listen—ソケットをリッスン状態に設定します。
リスニングソケット、なぜリスニングしたいのですか? ——それは、tcp が接続指向だからです。(指向性の説明: 作業を行う前に何をするかが指向性です。たとえば、オブジェクト指向は、作業を行う前にオブジェクトを定義することです)
接続指向:作業を行う前に接続を確立し、クライアントがいつでもサーバーに接続できるようにします。接続指向にするためには、ソケットをリッスン状態に設定する必要があります。
クライアントがいつでもソケットに接続できるように、ソケットを listen 状態に設定します。
man 3 listen
int listen(int ソケット、int バックログ);
ソケット: 設定するファイル記述子。backlog: それについては後で話します、5 のように今気軽に書いてください。
戻り値: 成功した場合は 0、失敗した場合は -1 が返され、エラー コードが設定されます。
3. サーバーはクライアントの接続承諾を取得します。
man 2 accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
サーバーは特定のソケット sockfd を介して接続を取得します。(デフォルトのブロック待機を受け入れます)
sockfd : ファイル記述子。2 つのパラメータ src_addr と addrlen は、recvfrom (クライアント ソケット) の背後にある 2 つのパラメータとまったく同じです。
addr : (出力パラメータ) サーバーがクライアントから送信されたメッセージを読み取るとき (どのクライアントがメッセージを送信したか)、クライアントのソケット情報を addr に保存します。(addr の型はソケット型ポインター struct sockaddr* であり、受信ネットワーク ソケット型 struct sockaddr_in* はこの型ポインター struct sockaddr* に変換する必要があります。)
addrlen : (入力および出力パラメータ) クライアントのバッファ サイズ。(socklen_t は符号なし整数です)
戻り値: 成功すると新しいsocketfdが返され、エラーが発生した場合は-1が返され、エラーコードが設定されます。
戻り値のソケットとパラメータのソケットの役割:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
小話:景勝地のレストランでは、ゲストを迎える専門の人 (張三) を雇うことになり、張三がゲストをレストランに紹介すると、ウェイターがゲストにサービスを提供します。張三は立ち去り、客人に会いに行った。Zhang San はint sockfd パラメータのソケット-リスニングソケット listensocke_ 、ウェイターはaccept戻り値
リスニングソケット listensocke_ :パラメータ内のソケットのコア作業: 新しい接続を取得するため (もちろん、接続する顧客が必要です!) serviceSock :
戻り値のソケットのコア作業: 主にネットワークサービスを提供するためユーザー向けのソケット、主に顧客に IO サービスを提供するため
4. 使用する機能の一部
(1) strcasecmp - 2 つの文字列を比較しますが、大文字と小文字は区別されません。
大文字と小文字を区別せずに 2 つの文字列を比較します
s1 (またはその最初の n バイト) が s2 より小さいことが判明した場合は、0 より小さい整数を返します。s1 が s2 に等しい場合は、0 を返します。s1 が s2 より大きい場合は、0 より大きい整数を返します。
(2) int isalpha(int c); ——それが文字であるかどうかにかかわらず、はいの場合は 1 を返し、そうでない場合は 0 を返します。
(3) int is lower(int c);——小文字かどうかに関係なく、小文字の場合は 1 を返し、そうでない場合は 0 を返します。
(4) int toupper(int c);——大文字に変換します。戻り値は変換された文字の値、または変換できなかった場合は c です。
(5)strcasecmp
2 つの文字列 s1 と s2 は、文字の大文字と小文字を無視して比較されます。s1 が s2 より小さい、一致する、または大きいことが判明した場合は、それぞれ 0 以下、0 以上、または 0 以上の整数を返します。
if (strcasecmp(message.c_str(), "quit") == 0)
quit = true;
戻り値: s1 (またはその最初の n バイト) が s2 より小さい、一致する、または大きいことが判明した場合は、それぞれサイズが 0 より小さい、0 以上の整数を返します。
5. クライアントが接続を開始します。
connect 機能: クライアントによって指定されたストリーム ソケット sockfd を通じてサーバーへの接続要求を開始します (最初に、接続する必要があるリモート ホストの基本情報を入力します)。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd: ストリームソケット。2 つのパラメータ src_addr と addrlen は、sendto の背後にある 2 つのパラメータとまったく同じです。
addr: (入力パラメータ) メッセージの送信先ホスト、ソケット タイプ ポインタ struct sockaddr*、受信ネットワーク ソケット タイプ struct sockaddr* をこのタイプ ポインタ struct sockaddr* に強制する必要があります。
addrlen: (入力パラメータ) ホストのバッファ サイズ。(socklen t は unsigned int です)
戻り値:接続成功時は0、接続失敗時は-1を返し、エラーコードが設定されます。
(初めて sendto または connect を呼び出すと、自動的にバインドされ、クライアントは自動的に独自の IP とポートをバインドします)
2. TCPソケットコード
1. 注意事項
(1) TCP はストリーミング ソケットであるため、データの読み取りと書き込みには読み取りと書き込みを使用します。
(2) ① クライアント OS は自動的にバインドしますが、バインド自体を表示する必要はありません。
②聞く必要がありますか?必要ありません。監視自体は他のクライアントが接続するのを待機しているため、クライアントには誰も接続しないため、クライアント自体は監視ステータスを設定する必要はありません。
③承諾する必要がありますか?必要がなければ、接続することはおろか、モニターをセットアップすることもできません。
TCP サーバーとクライアントのプロセス:
(3) TCP サーバーの働き:
init(): ①ソケットを作成します。②サーバー情報struct sockaddr_inを記入します。③ ソケットを sockaddr_in にバインドします。④ソケットがリスニング状態に設定されます。
loop(): ⑤リンクの取得を承諾し、クライアントのIPとポートを取得します。⑥サービスを提供し、変換を完了し、内容を読んだ上で書き戻します。
TCPクライアントの仕事: ①ソケットを作成します。② connect サーバー情報を入力し、サーバーへの接続要求を開始します。③データ書き込み後、サーバーで変換されたデータを読み込みます
(4) エラーが発生しやすい: サーバー内
①accpet 失敗ログは警告するように設定され、クライアント接続の再取得を継続します。
③memset(&local,0,sizeof(local)); 省略(書かなくても大丈夫ですが、確実性を高めるために書いた方が良いです)
②inbuffer[s]='\0';省略。
クライアントでは:
①揮発性ブールが欠落していることを終了します
② message.clear(); と message.resize(1024); は省略されています。
2. サービスをマルチプロセス、マルチスレッドのバージョンとしてカプセル化します。
この目的のために、サービスをマルチプロセス、マルチスレッドバージョンとしてカプセル化します。複数のクライアントがアクセスするとき、最初のクライアントがサーバーにアクセスすると、サーバーは子プロセスを通じてクライアントにサービスを提供し、親プロセスは続行できます。 while ループでは、次のブロック メソッドを実行して、次のクライアントのリンクを取得し、そのクライアントにサービスを提供します。これが同時実行です。
ヒント: waitpid() waitpid() を使用しないのはなぜでしょうか; デフォルトではブロックして待機します。マルチプロセスの同時実行性を追求しており、ブロッキングはシリアル化に相当するため、waitpid()は使用できません。WNOHANGはそれができるでしょうか?——回答: はい、しかし非常に面倒です。各子プロセスの pid をベクトルに保存する必要があります。ノンブロッキングを待つたびに、子プロセスの pid を検出するためにポーリングして、子プロセスが子プロセスかどうかを確認する必要があります。プロセスが終了するのは非常に面倒なので、この方法は選択しません。
(1) 単一プロセス (オリジナルバージョン)
サービスを提供できるのは 1 人の顧客のみです。1 人の顧客にサービスを提供し、transService に入ると、transService は無限ループになります。サービスが完了しない限り、関数は戻らず、メインの実行フローは他の顧客にサービスを提供し続けることはできません。 。
void loop()
{
signal(SIGCHLD, SIG_IGN); // only Linux
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4. 获取连接, accept 的返回值是一个新的socket fd ??
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
}
// 4.1 获取客户端基本信息
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
strerror(errno), peerIp.c_str(), peerPort, serviceSock);
// 5 提供服务, echo -> 小写 -> 大写
// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
transService(serviceSock, peerIp, peerPort);
close(serviceSock); //这一步是一定要做的!
}
}
(2) マルチプロセスバージョン 1
signal(SIGCHLD, SIG_IGN) を使用します。親プロセスは signal/sigaction を呼び出して SIGCHLD の処理アクションを SIG_IGN に設定します。これにより、フォークされた子プロセスは終了時に自動的にクリーンアップされ、ゾンビ プロセスは生成されず、親プロセスはプロセスは通知されません。
注: ①親プロセスでオープンしたファイルは子プロセスに引き継がれるため、子プロセス自体では「ゲスト受信」用のlistenSock_を使用しないため、このファイルディスクリプタを閉じることを推奨します。close(listenSock_); //提案 (パイプの不要な読み取り/書き込み端を閉じるのと同様)
②親プロセスacceptが作成したサービス提供ファイル記述子serviceSockは子プロセスが継承して使用するものであり、子プロセスがserviceSockを継承した後は親プロセスは使用せず、親プロセスに対応するserviceSockが必要となります。閉鎖される。close(serviceSock); //このステップは必ず実行する必要があります。(パイプの不要な読み取り/書き込み端を閉じるのと同様)
void loop()
{
signal(SIGCHLD, SIG_IGN); // only Linux
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4. 获取连接, accept 的返回值是一个新的socket fd ??
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
}
// 4.1 获取客户端基本信息
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
strerror(errno), peerIp.c_str(), peerPort, serviceSock);
// 5 提供服务, echo -> 小写 -> 大写
// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
// transService(serviceSock, peerIp, peerPort);
// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
close(listenSock_); //建议
//子进程
transService(serviceSock, peerIp, peerPort);
exit(0); // 进入僵尸
}
// 父进程
close(serviceSock); //这一步是一定要做的!
}
}
監視スクリプト:
(3) マルチプロセスバージョン 2
TCPプロセス:
init(): ①ソケットを作成します。②サーバー情報struct sockaddr_inを記入します。③ ソケットを sockaddr_in にバインドします。④ソケットがリスニング状態に設定されます。
loop(): ⑤リンクの取得を承諾し、クライアントのIPとポートを取得します。⑥祖父プロセスは父親を作成し、父親は孫プロセスを作成するために2回目にフォークして自分で終了します。孫プロセスはサービスを提供します(したがって、serviceSock_のみが必要です。父親は事前に無駄なリッスンソケットlistenSock_をオフにする必要があります)、祖父プロセス外で父親を待ち、客を迎えに行く作業を繰り返す(そのため、おじいちゃんは役に立たないサービスソケットserviceSock_をオフにして、リッスンソケットlistenSock_を残しておかなければならない)
孫プロセスを作成すると、孫プロセスの親プロセスが直接終了するため、孫プロセスは孤立プロセスとなり、孫プロセスがシステムに採用され、そのリサイクル問題はリサイクルのためにシステムに引き継がれます。そして、父親プロセスはブロックし、祖父プロセスによる解放を待ちます。
void loop()
{
// signal(SIGCHLD, SIG_IGN); // only Linux
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4. 获取连接, accept 的返回值是一个新的socket fd ??
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
}
// 4.1 获取客户端基本信息
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
strerror(errno), peerIp.c_str(), peerPort, serviceSock);
// 5.1 v1.1 版本 -- 多进程版本 -- 也是可以的
//爷爷进程
pid_t id = fork();
if(id == 0)
{
// 爸爸进程
close(listenSock_);//建议
// 又进行了一次fork,让 爸爸进程直接终止
if(fork() > 0) exit(0);
// 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
transService(serviceSock, peerIp, peerPort);
exit(0);
}
// 父进程
close(serviceSock); //这一步是一定要做的!
// 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
assert(ret > 0);
(void)ret;
}
}
(4) マルチスレッド版
マルチスレッドを使用して顧客にサービスを提供するには、まず ThreadData クラスを作成します。これは、関数メソッドが transService を呼び出してパラメータを渡すのに便利です。
class ServerTcp; // 申明一下ServerTcp
class ThreadData
{
public:
uint16_t clientPort_;
std::string clinetIp_;
int sock_;
ServerTcp *this_;
public:
ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
: clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
{}
};
————————上面是类外,下面是类内
static void *threadRoutine(void *args)
{
pthread_detach(pthread_self()); //设置线程分离
ThreadData *td = static_cast<ThreadData*>(args);
td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
delete td;
return nullptr;
}
void loop()
{
// signal(SIGCHLD, SIG_IGN); // only Linux
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4. 获取连接, accept 的返回值是一个新的socket fd ??
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
}
// 4.1 获取客户端基本信息
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
strerror(errno), peerIp.c_str(), peerPort, serviceSock);
// 5 提供服务, echo -> 小写 -> 大写
// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
// 5.2 v2 版本 -- 多线程
// 这里不需要进行关闭文件描述符吗??不需要啦
// 多线程是会共享文件描述符表的!
ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void*)td);
// logMessage(DEBUG, "server 提供 service start ...");
// sleep(1);
}
}
2. コード
clientTcp.cc
#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!
volatile bool quit = false;
static void Usage(std::string proc)
{
std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
<< std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverIp = argv[1];
uint16_t serverPort = atoi(argv[2]);
// 1. 创建socket SOCK_STREAM
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
// 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
// 2.1 先填充需要连接的远端主机的基本信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverPort);
inet_aton(serverIp.c_str(), &server.sin_addr);
// 2.2 发起请求,connect 会自动帮我们进行bind!
if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
{
std::cerr << "connect: " << strerror(errno) << std::endl;
exit(CONN_ERR);
}
std::cout << "info : connect success: " << sock << std::endl;
std::string message;
while (!quit)
{
message.clear();
std::cout << "请输入你的消息>>> ";
std::getline(std::cin, message);
if (strcasecmp(message.c_str(), "quit") == 0)
quit = true;
ssize_t s = write(sock, message.c_str(), message.size());
if (s > 0)
{
message.resize(1024);
ssize_t s = read(sock, (char *)(message.c_str()), 1024);
if (s > 0)
message[s] = 0;
std::cout << "Server Echo>>> " << message << std::endl;
}
else if (s <= 0)
{
break;
}
}
close(sock);
return 0;
}
サーバーTcp.cc
#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
class ServerTcp; // 申明一下ServerTcp
class ThreadData
{
public:
uint16_t clientPort_;
std::string clinetIp_;
int sock_;
ServerTcp *this_;
public:
ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
: clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
{}
};
class ServerTcp
{
public:
ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
{
}
~ServerTcp()
{
}
public:
void init()
{
// 1. 创建socket
listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
if (listenSock_ < 0)
{
logMessage(FATAL, "socket: %s", strerror(errno));
exit(SOCKET_ERR);
}
logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);
// 2. bind绑定
// 2.1 填充服务器信息
struct sockaddr_in local; // 用户栈
memset(&local, 0, sizeof local);
local.sin_family = PF_INET;
local.sin_port = htons(port_);
ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
// 2.2 本地socket信息,写入sock_对应的内核区域
if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
{
logMessage(FATAL, "bind: %s", strerror(errno));
exit(BIND_ERR);
}
logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);
// 3. 监听socket,为何要监听呢?tcp是面向连接的!
if (listen(listenSock_, 5 /*后面再说*/) < 0)
{
logMessage(FATAL, "listen: %s", strerror(errno));
exit(LISTEN_ERR);
}
logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
// 运行别人来连接你了
}
static void *threadRoutine(void *args)
{
pthread_detach(pthread_self()); //设置线程分离
ThreadData *td = static_cast<ThreadData*>(args);
td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
delete td;
return nullptr;
}
void loop()
{
// signal(SIGCHLD, SIG_IGN); // only Linux
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4. 获取连接, accept 的返回值是一个新的socket fd ??
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue; 接客失败不怎么样,重新接客就行
}
// 4.1 获取客户端基本信息
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
strerror(errno), peerIp.c_str(), peerPort, serviceSock);
// 5 提供服务, echo -> 小写 -> 大写
// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
// transService(serviceSock, peerIp, peerPort);
// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
// pid_t id = fork();
// assert(id != -1);
// if(id == 0)
// {
// close(listenSock_); //建议
// //子进程
// transService(serviceSock, peerIp, peerPort);
// exit(0); // 进入僵尸
// }
// // 父进程
// close(serviceSock); //这一步是一定要做的!
// 5.1 v1.1 版本 -- 多进程版本 -- 也是可以的
// 爷爷进程
// pid_t id = fork();
// if(id == 0)
// {
// // 爸爸进程
// close(listenSock_);//建议
// // 又进行了一次fork,让 爸爸进程
// if(fork() > 0) exit(0);
// // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
// transService(serviceSock, peerIp, peerPort);
// exit(0);
// }
// // 父进程
// close(serviceSock); //这一步是一定要做的!
// // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
// pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
// assert(ret > 0);
// (void)ret;
// 5.2 v2 版本 -- 多线程
// 这里不需要进行关闭文件描述符吗??不需要啦
// 多线程是会共享文件描述符表的!
ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void*)td);
// waitpid(); 默认是阻塞等待!WNOHANG
// 方案1
// logMessage(DEBUG, "server 提供 service start ...");
// sleep(1);
}
}
// 大小写转化服务
// TCP && UDP: 支持全双工
void transService(int sock, const std::string &clientIp, uint16_t clientPort)
{
assert(sock >= 0);
assert(!clientIp.empty());
assert(clientPort >= 1024);
char inbuffer[BUFFER_SIZE];
while (true)
{
ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为我们读到的都是字符串
if (s > 0)
{
// read success
inbuffer[s] = '\0';
if(strcasecmp(inbuffer, "quit") == 0)
{
logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
break;
}
logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
// 可以进行大小写转化了
for(int i = 0; i < s; i++)
{
if(isalpha(inbuffer[i]) && islower(inbuffer[i]))
inbuffer[i] = toupper(inbuffer[i]);
}
logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
write(sock, inbuffer, strlen(inbuffer));
}
else if (s == 0)
{
// pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
// s == 0: 代表对方关闭,client 退出
logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
break;
}
else
{
logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
break;
}
}
// 只要走到这里,一定是client退出了,服务到此结束
close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
logMessage(DEBUG, "server close %d done", sock);
}
private:
// sock
int listenSock_;
// port
uint16_t port_;
// ip
std::string ip_;
};
static void Usage(std::string proc)
{
std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;
}
// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
if(argc != 2 && argc != 3 )
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
std::string ip;
if(argc == 3) ip = argv[2];
ServerTcp svr(port, ip);
svr.init();
svr.loop();
return 0;
}
util.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#define SOCKET_ERR 1
#define BIND_ERR 2
#define LISTEN_ERR 3
#define USAGE_ERR 4
#define CONN_ERR 5
#define BUFFER_SIZE 1024