c++ネットワークプログラミング2:TCP接続概念とプログラミング
8984 ワード
一.TCP接続を確立する3回の握手
TCP/IPプロトコルでは、TCPプロトコルは信頼できる接続サービスを提供し、3回の握手で接続を確立する.1回目の握手:接続を確立する時、クライアントはSYNパケット(SYN=j)をサーバーに送って、そしてSYN_に入るSEND状態、サーバー確認待ち;【クライアント->サービス側:SYN(j)】2回目の握手:サーバがSYNパケットを受け取り、お客様のSYN(ACK=j+1)を確認するとともに、SYNパケット(SYN=k)、すなわちSYN+ACKパケットを自分から送信し、サーバがSYN_RECV状態;【サービス側->クライアント:SYN(k),ACK(j+1)】3回目の握手:クライアントはサーバのSYN+ACKパケットを受け取り、サーバに確認パケットACK(ACK=k+1)を送信し、このパケットの送信が完了し、クライアントとサーバはESTABLISHED状態に入り、3回の握手を完了する.【クライアント->サービス側:ACK(k+1)】3回の握手を完了し、クライアントとサーバがデータの転送を開始します.上記は完全なTCP接続プロセスです.
しかし、なぜ3回の握手をして接続が二重であることを保証しなければならないのか、一度にしてはいけないのか.二度はだめですか.私たちは実際の生活の中で2人が会話をしている例を挙げて、3回の握手を真似します.
最初の会話:
妻は甲に醤油をかけに行かせたが,途中で乙にぶつかって,甲は「お兄さんたちはご飯を食べましたか.」と聞いた.
結局、乙はイヤホンを持って歌を聞いていたが、全然聞こえず、反響がなかった.甲は心の中で思っています:あなたと話をしても音がなくて、あなたに言わないで、コミュニケーションに失敗しました.乙が甲からの情報を引き継ぐことができない場合、コミュニケーションは必ず失敗することを説明します.
乙が甲の言うことを聞いたら、最初の会話は成功し、次に2回目の会話を行う.2回目の会話:
乙は甲の言うことを闻いて、しかし彼は外国人で、中国语はよくなくて、甲の言うどんな意味も知らないでどのように返事するか分からないで、そこで胜手に1つの学んだ中国语に答えました:私はトイレに行きました.甲は聞くとすぐに笑って、「トイレに行ってご飯を食べます」?道が合わないなら、あなたから離れましょう.コミュニケーションが失敗しました.乙が正確な応答ができない場合、コミュニケーションに失敗することを説明します.
もし乙が甲の話を聞いたら、正しい応答をして、私はご飯を食べました.あなたは?では、2回目の握手に成功しました.
過程の前の2回の対話の証明を通じて、乙は甲の言うことを理解することができて、しかも正しい応答をすることができます.次に3回目の会話を行います.3回目の会話:
甲はちょうど乙と挨拶をして、突然妻は彼を叫んで、“あなたは死んだ鬼で、醤油を打ってどうしてこんなに長い間、私が家に帰ってどのようにあなたを片付けますかを見て、甲は妻が厳しくて、聞いて驚いて何も言わないで家に帰って、乙自身を干しました.乙は考えます:このどんな人、得て、私も家に帰るようにしましょう、疎通は失敗します.甲が応答できない場合のコミュニケーションに失敗したことを説明します.
もし甲も正しい応答をしたら:私も食べました.では、3回目の会話が成功し、2人はスムーズなコミュニケーションチャネルを構築し、チャットを始めました.
2回目と3回目の会話を経て、甲は乙の言うことを理解し、正しい応答をすることができることを証明した.
二人が効果的に会話をするには、この3回の会話の過程が必要であることがわかります.同じようにTCPに対してなぜ3回の握手をしなければならないのか、私たちは同じように理解することができます.
サービス側がクライアントの情報を受信して正しい応答ができるように前の2回(1回目と2回目)の握手を行い、クライアントがサービス側の情報を受信して正しい応答ができるように後2回(2回目と3回目)の握手を行う.
TCP半開接続数の概念:
3回の握手が完了した後、TCP接続が確立され、システムにとって、これはTCP接続数である.システムはこのような数に対して有限ではない.3回の握手が完了せず、クライアントとサーバがデータの転送を開始しない場合、このように3回の握手を完了しようとするイベントの発生数はシステムによって制限される.この数はいわゆる半開接続数です.
二.TCPクライアントのプログラミング
1.まずシステムのソケットライブラリをロードします
2.クライアントのソケットの作成
注意:ソケットは通信の基礎であり、ソケットはプログラマーが通信端を操作する対象と考えられます.例えば、本機のクライアントをリモートマシンに接続したい場合は、クライアントが生成したソケットを使用して接続する必要があります.あなたが操作する対象はソケットです.彼は抽象的な概念ですが、確かにプログラミングに必要です.通信を行う端末(サーバでもクライアントでも)ごとにソケットを生成する必要があります.
3.ソケットをローカルのアドレスとポートにバインドします.現在のクライアントのシステム上で1つのポートを開いてサーバー上のポートとデータのインタラクションを行います.このプロセスはポートのバインドと呼ばれます.TCPクライアントはこのプロセスを必要としません.connectインタフェース接続サービスを呼び出すと自動的に1つのポートが開きます.特殊な需要があれば、1つのポートをバインドしなければなりません.
注意:ネットワークバイト順とホストバイト順は、ここではマシン自体の大端モード(実際にはアドレス格納上位バイト)と小端モード(開始アドレス格納下位バイト)に関連し、intelのCPUは小端モードに属し、ネットワークバイト順は大端モードであるため、htonsを使用してホストバイト順をネットワークバイト順に変換する必要がある.
ネットワーク->ホスト:ntohs ntohlホスト->ネットワーク:htons htonl(h:ホストn:ネットワークs:short l:long)
4.接続サーバー
ここでのconnectはブロック方式であり、ネットワークに問題があり、サーバに接続できない場合、connectは長時間待つことになるので、このような結果をもたらしたくない場合は、socketを非ブロック方式にすべきであり、具体的な操作は以下の通りである.
5.クライアント受信データ(サーバから送信されたもの)
RecvBufferは受信したデータであり、RecvBufferLenは一度に受信する必要があるデータ長であり、RLenは実際に受信したデータ長であり、クライアントがサーバから返されたデータを絶えず受信したい場合は、スレッドを開いて、recvをスレッドに入れてデータを受信しなければならない.
注意:ここでは一度に受信するデータ長RecvBufferLenを設定できますが、この長さにはシステムキャッシュのサポートが必要です.システムにはネットワークから送られてきたデータを入れるキャッシュがあります.recvはこのシステムからデータをキャッシュします.システムのデフォルトのSOCKET受信キャッシュのサイズは8688 B(8.6 K程度)です.sock関数recvまたはrecvFromがシステムキャッシュからデータを受信するのが遅すぎる場合、システムは新しいデータを受信すると元のキャッシュを上書きします.これにより、二次呼び出しrecv関数で新しいデータが取得され、古いデータが失われます.この場合、setsockoptメソッドを使用してシステムを32 Kにキャッシュするのが最適です.以下のようにします.
Max_RecvFrom_Buffer_Size=32*1024 Bは、クライアントのソケットを作成した後にsetsockoptを使用してシステムキャッシュのサイズを設定します.つまり、第2部が終わったらシステムキャッシュを設定します.
6.クライアントがデータを送信する(サーバへ)
SendBufferは送信されたデータであり、sendBufLenは一度に送信されたデータの長さであり、ここでの長さはネットワーク最大の送信ユニットMTU=1500 Bを考慮し、送信されたデータの長さは1400 Bに抑えるのがよい.
三.TCPサービス側のプログラミング1.ローディングソケット(クライアントと同じ)
2.サービス・エンド・ソケットsocketServiceの作成(クライアントと同じ)
3.サービス側でソケットをバインドする(クライアントと同じであるが、サービス側はソケットをバインドしなければならない、すなわちリスニングポートを開く)
4.サービス側のソケットをリスニングモードにする
connectNumsはクライアント接続最大数、SOMAXCONNは最大接続数
5.上の4段階のサービス側はすでに基本的な初期化をして、次は最も重要な一歩で、スレッドを開いて、それからインタフェースacceptを呼び出してクライアントが接続しているかどうかを傍受して、サービス側は実は1つの待つ過程で、クライアントの接続を待って、それからデータを処理して、クライアントに送ります
acceptはクライアントに接続されたsocketを返しますが、このsocketは何の役に立ちますか??サービス側はsocketを作成しますが、多くのクライアントがサービス側に接続されている場合、サービス側はクライアントデータに送信し、クライアントデータから受信する際にサービス側で作成したこのsocketを使用するのは明らかに不合理です.どのクライアントに送信するか分からないので、acceptが返すクライアントsocketが必要です.この返されるclientSocketは唯一です.サービス側は、データの送信と受信に使用し、データの正確性を保証します.したがってacceptが返すsocketサービス側は保存し、mapコンテナで保存する必要があります.ここではコードを書きません.
注意:acceptはブロックモードで、その3番目のパラメータは非常に重要で、SOCKADDRの長さ&addrDataLenでない場合、いくつかの不正な接続のクライアントを受け取り、acceptは204.204.204.204というIPからの接続を受け続けます.
6.クライアントから送信されたデータを受信する
ここでの受信データはクライアントと同様であり、recvの最初のパラメータsocketはacceptが返すクライアント接続socketであり、サービス側が作成したsocketではないことに注意する.
7.クライアントへのデータ送信
ここでの送信データはクライアントと同様であり、sendの最初のパラメータsocketはacceptが返すクライアント接続socketであり、サービス側が作成したsocketではないことに注意する.
TCP/IPプロトコルでは、TCPプロトコルは信頼できる接続サービスを提供し、3回の握手で接続を確立する.1回目の握手:接続を確立する時、クライアントはSYNパケット(SYN=j)をサーバーに送って、そしてSYN_に入るSEND状態、サーバー確認待ち;【クライアント->サービス側:SYN(j)】2回目の握手:サーバがSYNパケットを受け取り、お客様のSYN(ACK=j+1)を確認するとともに、SYNパケット(SYN=k)、すなわちSYN+ACKパケットを自分から送信し、サーバがSYN_RECV状態;【サービス側->クライアント:SYN(k),ACK(j+1)】3回目の握手:クライアントはサーバのSYN+ACKパケットを受け取り、サーバに確認パケットACK(ACK=k+1)を送信し、このパケットの送信が完了し、クライアントとサーバはESTABLISHED状態に入り、3回の握手を完了する.【クライアント->サービス側:ACK(k+1)】3回の握手を完了し、クライアントとサーバがデータの転送を開始します.上記は完全なTCP接続プロセスです.
しかし、なぜ3回の握手をして接続が二重であることを保証しなければならないのか、一度にしてはいけないのか.二度はだめですか.私たちは実際の生活の中で2人が会話をしている例を挙げて、3回の握手を真似します.
最初の会話:
妻は甲に醤油をかけに行かせたが,途中で乙にぶつかって,甲は「お兄さんたちはご飯を食べましたか.」と聞いた.
結局、乙はイヤホンを持って歌を聞いていたが、全然聞こえず、反響がなかった.甲は心の中で思っています:あなたと話をしても音がなくて、あなたに言わないで、コミュニケーションに失敗しました.乙が甲からの情報を引き継ぐことができない場合、コミュニケーションは必ず失敗することを説明します.
乙が甲の言うことを聞いたら、最初の会話は成功し、次に2回目の会話を行う.2回目の会話:
乙は甲の言うことを闻いて、しかし彼は外国人で、中国语はよくなくて、甲の言うどんな意味も知らないでどのように返事するか分からないで、そこで胜手に1つの学んだ中国语に答えました:私はトイレに行きました.甲は聞くとすぐに笑って、「トイレに行ってご飯を食べます」?道が合わないなら、あなたから離れましょう.コミュニケーションが失敗しました.乙が正確な応答ができない場合、コミュニケーションに失敗することを説明します.
もし乙が甲の話を聞いたら、正しい応答をして、私はご飯を食べました.あなたは?では、2回目の握手に成功しました.
過程の前の2回の対話の証明を通じて、乙は甲の言うことを理解することができて、しかも正しい応答をすることができます.次に3回目の会話を行います.3回目の会話:
甲はちょうど乙と挨拶をして、突然妻は彼を叫んで、“あなたは死んだ鬼で、醤油を打ってどうしてこんなに長い間、私が家に帰ってどのようにあなたを片付けますかを見て、甲は妻が厳しくて、聞いて驚いて何も言わないで家に帰って、乙自身を干しました.乙は考えます:このどんな人、得て、私も家に帰るようにしましょう、疎通は失敗します.甲が応答できない場合のコミュニケーションに失敗したことを説明します.
もし甲も正しい応答をしたら:私も食べました.では、3回目の会話が成功し、2人はスムーズなコミュニケーションチャネルを構築し、チャットを始めました.
2回目と3回目の会話を経て、甲は乙の言うことを理解し、正しい応答をすることができることを証明した.
二人が効果的に会話をするには、この3回の会話の過程が必要であることがわかります.同じようにTCPに対してなぜ3回の握手をしなければならないのか、私たちは同じように理解することができます.
サービス側がクライアントの情報を受信して正しい応答ができるように前の2回(1回目と2回目)の握手を行い、クライアントがサービス側の情報を受信して正しい応答ができるように後2回(2回目と3回目)の握手を行う.
TCP半開接続数の概念:
3回の握手が完了した後、TCP接続が確立され、システムにとって、これはTCP接続数である.システムはこのような数に対して有限ではない.3回の握手が完了せず、クライアントとサーバがデータの転送を開始しない場合、このように3回の握手を完了しようとするイベントの発生数はシステムによって制限される.この数はいわゆる半開接続数です.
二.TCPクライアントのプログラミング
1.まずシステムのソケットライブラリをロードします
WORD wVersionRequested;
wVersionRequested=MAKEWORD(1,1);// Winsock :1.1
WSADATA wsaData;
int err;
// WSAStartup , , 1.1
err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
return false;
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1)
{
// , , WSACleanup
WSACleanup();
return false;
}
2.クライアントのソケットの作成
SOCKET sockClient;
unsigned long mode=1;//0:
sockClient=socket(AF_INET,SOCK_STREAM,0);
注意:ソケットは通信の基礎であり、ソケットはプログラマーが通信端を操作する対象と考えられます.例えば、本機のクライアントをリモートマシンに接続したい場合は、クライアントが生成したソケットを使用して接続する必要があります.あなたが操作する対象はソケットです.彼は抽象的な概念ですが、確かにプログラミングに必要です.通信を行う端末(サーバでもクライアントでも)ごとにソケットを生成する必要があります.
3.ソケットをローカルのアドレスとポートにバインドします.現在のクライアントのシステム上で1つのポートを開いてサーバー上のポートとデータのインタラクションを行います.このプロセスはポートのバインドと呼ばれます.TCPクライアントはこのプロセスを必要としません.connectインタフェース接続サービスを呼び出すと自動的に1つのポートが開きます.特殊な需要があれば、1つのポートをバインドしなければなりません.
//sockClient ,localIP IP( ),localPort
bool BindSocketEx(SOCKET sockClient,LPCSTR localIP,UINT localPort)
{
//LPCTSTR unicode const wchar *, unicode const char *
//INADDR_ANY ( ) , IP, INADDR_ANY
//
SOCKADDR_IN addrSrv;
if(localIP==NULL)
addrSrv.sin_addr.S_un.S_addr= htonl(INADDR_ANY);// ,
else
addrSrv.sin_addr.S_un.S_addr=inet_addr(localIP);//inet_addr ip long
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(localPort);// ,
// return bind(sockClient,(SOCKADDR *)&addrSrv,sizeof(SOCKADDR))==0;
if(bind(sockClient,(SOCKADDR *)&addrSrv,sizeof(SOCKADDR))==0)
return true;
else
return false;
}
注意:ネットワークバイト順とホストバイト順は、ここではマシン自体の大端モード(実際にはアドレス格納上位バイト)と小端モード(開始アドレス格納下位バイト)に関連し、intelのCPUは小端モードに属し、ネットワークバイト順は大端モードであるため、htonsを使用してホストバイト順をネットワークバイト順に変換する必要がある.
ネットワーク->ホスト:ntohs ntohlホスト->ネットワーク:htons htonl(h:ホストn:ネットワークs:short l:long)
4.接続サーバー
//sockClient ,svrIP IP,svrPort
bool TCP_Client_ConnectSocketEx(SOCKET sockClient,LPCSTR svrIP,USHORT svrPort)
{
if(svrIP==NULL||svrPort==0)
return NULL;
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr(svrIP);// IP,inet_addr ip long
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(svrPort);// ,
// connect TCP 0
return connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))==0;
}
ここでのconnectはブロック方式であり、ネットワークに問題があり、サーバに接続できない場合、connectは長時間待つことになるので、このような結果をもたらしたくない場合は、socketを非ブロック方式にすべきであり、具体的な操作は以下の通りである.
//sockClient ,svrIP IP,svrPort
bool TCP_Client_ConnectSocketEx(SOCKET sockClient,LPCSTR svrIP,USHORT svrPort)
{
if(svrIP==NULL||svrPort==0)
return NULL;
unsigned long on = 1;
struct timeval timeout ;
fd_set r;
int ret;
ioctlsocket(hSocket, FIONBIO, &on);// ,on=1 ,on=0
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr(svrIP);// IP,inet_addr ip long
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(svrPort);// ,
// connect TCP 0
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
FD_ZERO(&r);
FD_SET(sockClient, &r);
timeout.tv_sec = 5000/1000; // 5
5.クライアント受信データ(サーバから送信されたもの)
int RLen=0;
RLen=recv(sockClient,(char *)RecvBuffer,RecvBufferLen,0);
if(RLen<=0)
return false;
RecvBufferは受信したデータであり、RecvBufferLenは一度に受信する必要があるデータ長であり、RLenは実際に受信したデータ長であり、クライアントがサーバから返されたデータを絶えず受信したい場合は、スレッドを開いて、recvをスレッドに入れてデータを受信しなければならない.
注意:ここでは一度に受信するデータ長RecvBufferLenを設定できますが、この長さにはシステムキャッシュのサポートが必要です.システムにはネットワークから送られてきたデータを入れるキャッシュがあります.recvはこのシステムからデータをキャッシュします.システムのデフォルトのSOCKET受信キャッシュのサイズは8688 B(8.6 K程度)です.sock関数recvまたはrecvFromがシステムキャッシュからデータを受信するのが遅すぎる場合、システムは新しいデータを受信すると元のキャッシュを上書きします.これにより、二次呼び出しrecv関数で新しいデータが取得され、古いデータが失われます.この場合、setsockoptメソッドを使用してシステムを32 Kにキャッシュするのが最適です.以下のようにします.
if(setsockopt(sockClient,SOL_SOCKET,SO_RCVBUF,(const char *)&Max_RecvFrom_Buffer_Size,sizeof(int))!=0)
{
//
return INVALID_SOCKET;
}
Max_RecvFrom_Buffer_Size=32*1024 Bは、クライアントのソケットを作成した後にsetsockoptを使用してシステムキャッシュのサイズを設定します.つまり、第2部が終わったらシステムキャッシュを設定します.
6.クライアントがデータを送信する(サーバへ)
int Ret;
Ret =send(sockClient,SendBuffer,sendBufLen,0);
if ( Ret == SOCKET_ERROR )
{
int err = GetLastError( );
if ( err == WSAEWOULDBLOCK || err == WSAETIMEDOUT )
{
Sleep(1);
}
else //
{
Ret = 0;
return false;
}
}
SendBufferは送信されたデータであり、sendBufLenは一度に送信されたデータの長さであり、ここでの長さはネットワーク最大の送信ユニットMTU=1500 Bを考慮し、送信されたデータの長さは1400 Bに抑えるのがよい.
三.TCPサービス側のプログラミング1.ローディングソケット(クライアントと同じ)
2.サービス・エンド・ソケットsocketServiceの作成(クライアントと同じ)
3.サービス側でソケットをバインドする(クライアントと同じであるが、サービス側はソケットをバインドしなければならない、すなわちリスニングポートを開く)
4.サービス側のソケットをリスニングモードにする
bool ListenSocketEx(SOCKET socketService,UINT connectNums)
{
if(connectNums==0)
return listen(socketService,SOMAXCONN)==0;
else
return listen(socketService,connectNums)==0;
}
connectNumsはクライアント接続最大数、SOMAXCONNは最大接続数
5.上の4段階のサービス側はすでに基本的な初期化をして、次は最も重要な一歩で、スレッドを開いて、それからインタフェースacceptを呼び出してクライアントが接続しているかどうかを傍受して、サービス側は実は1つの待つ過程で、クライアントの接続を待って、それからデータを処理して、クライアントに送ります
UINT ClientSocketListenProc(LPVOID lpParameter)
{
while(true)
{
SOCKADDR_IN addrClient;
int addrDataLen=sizeof(SOCKADDR);//
SOCKET clientSock=accept(socketService,(sockaddr *)&addrClient,&addrDataLen);
}
}
acceptはクライアントに接続されたsocketを返しますが、このsocketは何の役に立ちますか??サービス側はsocketを作成しますが、多くのクライアントがサービス側に接続されている場合、サービス側はクライアントデータに送信し、クライアントデータから受信する際にサービス側で作成したこのsocketを使用するのは明らかに不合理です.どのクライアントに送信するか分からないので、acceptが返すクライアントsocketが必要です.この返されるclientSocketは唯一です.サービス側は、データの送信と受信に使用し、データの正確性を保証します.したがってacceptが返すsocketサービス側は保存し、mapコンテナで保存する必要があります.ここではコードを書きません.
注意:acceptはブロックモードで、その3番目のパラメータは非常に重要で、SOCKADDRの長さ&addrDataLenでない場合、いくつかの不正な接続のクライアントを受け取り、acceptは204.204.204.204というIPからの接続を受け続けます.
6.クライアントから送信されたデータを受信する
int RLen=0;
RLen=recv(acptClientSocket,(char *)RecvBuffer,RecvBufferLen,0);
if(RLen<=0)
return false;
ここでの受信データはクライアントと同様であり、recvの最初のパラメータsocketはacceptが返すクライアント接続socketであり、サービス側が作成したsocketではないことに注意する.
7.クライアントへのデータ送信
int Ret;
Ret =send(acptClientSocket,SendBuffer,sendBufLen,0);
if ( Ret == SOCKET_ERROR )
{
int err = GetLastError( );
if ( err == WSAEWOULDBLOCK || err == WSAETIMEDOUT )
{
Sleep(1);
}
else //
{
Ret = 0;
return false;
}
}
ここでの送信データはクライアントと同様であり、sendの最初のパラメータsocketはacceptが返すクライアント接続socketであり、サービス側が作成したsocketではないことに注意する.