Windows SocketプログラミングのTCPサービスとクライアント
8910 ワード
前述の記事では、プロセス間またはネットワーク間の通信のためにパイプを作成する名前付きパイプ通信について説明しています.しかし、効率が低いなどの欠陥があります.この文章からsocketプログラミングを紹介します.SOcketは,TCP,UDP,IPプロトコルによる通信であり,効率が高い.ここではまずTCPのサービス・エンドとクライアントについて説明します.
TCPは接続向けで、通信を行う前に、双方が先にコミュニケーションを取ってから通信を行う必要があります.また,TCPはデータストリーム方式でデータ伝達を行い,自動的にパケットの分解やパケットのグループ化を行うプロセスである.したがって、TCPの接続は比較的信頼できるが、伝送速度も比較的遅い.次に,コード内でTCPの通信をどのように実現するかを見る.
Windowsでは、SOcketネットワークの操作を行うには、WinSock 2という名前を含める必要があります.h(またはWinSock.h)、WinSock 2が含まれている場合.hはwindowsでなければなりません.hの前に、そうでないと再定義されたコンパイルエラーが発生します.ヘッダファイルを含むと、ライブラリファイルws 2_もリンクされます.32.lib、完了したら、TCPサービス側とクライアントの作成を開始することができます.
まずサービス端を見てみましょう.まず,ネットワーク操作を行うには,まずネットワーク環境の初期化を行う.WSAstartup関数は、ネットワーク環境を初期化するために使用されます.次のように宣言されます.
初期化が完了したら、クライアントとサービス側の接続に使用するパイプに相当するsocket(ソケット)を作成する必要があります.SOcket関数を呼び出すと、次のように宣言するソケットを作成できます.
ソケットを作成したら、パイプライン通信で標準入出力ポートにバインドされるのと同じように、オペレーティングシステムがどのアドレスとポートでネットワーク操作を行う必要があるかを伝える必要があります.バインドするときは、SOCKADDRが必要です.INという構造体は、以下のように宣言されています.
リスニングが完了すると、受信クライアントの接続を行うことができます.acceptという関数を呼び出して接客する必要があります.次のように宣言します.
クライアントは比較的簡単で、前の部分とサービス側は基本的に同じで、バインド操作に違いがあります.サービス側がバインドするIPアドレスは、本機のすべてのネットワークカードのIPであり、クライアントは1つだけバインドすればよい.クライアントにとって、指定されたサーバに接続するだけだからだ.割り当て済みSOCKADDR_IN構造の後、サービス側はbind関数を呼び出しますが、クライアントはconnect関数を呼び出す必要があります.その宣言は以下の通りです.
通信が完了すると、接続を閉じることができます.冒頭で述べたように、クライアントとサービス側が接続を開始したばかりのとき、両者はまずコミュニケーションを行います.このコミュニケーションには3つのステップが必要です.私たちは3回の握手と呼ばれています.同じように接続を閉じるときは、4つのステップが必要です.私たちは4回の握手と呼ばれています.もしあなたが乱暴で、直接ネットを抜くならば、それもその中の2回のステップを完成して、応用層として開発して、その中の原理を深く究める必要はありません.興味があれば、自分で資料を探すことができます.クライアントのサンプルコードを添付します.
TCPは接続向けで、通信を行う前に、双方が先にコミュニケーションを取ってから通信を行う必要があります.また,TCPはデータストリーム方式でデータ伝達を行い,自動的にパケットの分解やパケットのグループ化を行うプロセスである.したがって、TCPの接続は比較的信頼できるが、伝送速度も比較的遅い.次に,コード内でTCPの通信をどのように実現するかを見る.
Windowsでは、SOcketネットワークの操作を行うには、WinSock 2という名前を含める必要があります.h(またはWinSock.h)、WinSock 2が含まれている場合.hはwindowsでなければなりません.hの前に、そうでないと再定義されたコンパイルエラーが発生します.ヘッダファイルを含むと、ライブラリファイルws 2_もリンクされます.32.lib、完了したら、TCPサービス側とクライアントの作成を開始することができます.
まずサービス端を見てみましょう.まず,ネットワーク操作を行うには,まずネットワーク環境の初期化を行う.WSAstartup関数は、ネットワーク環境を初期化するために使用されます.次のように宣言されます.
int WSAStartup(
WORD wVersionRequested, // , 2.2
LPWSADATA lpWSAData //WSAData
);
上の関数の2番目のパラメータは、Wsadata構造のポインタを受信します.この構造には、バージョン番号が含まれています.私たちが渡したバージョン番号は、その構造のバージョン番号を初期化します.初期化が完了したら、クライアントとサービス側の接続に使用するパイプに相当するsocket(ソケット)を作成する必要があります.SOcket関数を呼び出すと、次のように宣言するソケットを作成できます.
SOCKET socket(
int af, //IP
int type, // ,TCP SOCK_STREAM
int protocol //
);
実は、socketもカーネルオブジェクトですが、カーネルオブジェクトが持つ明らかなフラグ、セキュリティ属性はありません.ソケットを作成したら、パイプライン通信で標準入出力ポートにバインドされるのと同じように、オペレーティングシステムがどのアドレスとポートでネットワーク操作を行う必要があるかを伝える必要があります.バインドするときは、SOCKADDRが必要です.INという構造体は、以下のように宣言されています.
struct sockaddr_in{
short sin_family; //
unsigned short sin_port; //
struct in_addr sin_addr; //ip
char sin_zero[8]; // SOCKADDR
};
のSOCKADDR構造は上記の機能と全く同じですが、SOCKADDRという構造の中には2人のメンバーしかいません.1つはプロトコルクラスタで、1つは14バイトのchar配列です.コードをよりよく書くために、char配列をSOCKADDR_に分解しました.INの下位3メンバー.ポート、アドレスなどの情報を初期化した後、bind関数を呼び出してバインド操作を完了する必要があります.次のように宣言します.int bind(
SOCKET s, // socket
const struct sockaddr FAR *name, //sockaddr
int namelen //sockaddr
);
バインド後、listen関数を呼び出してリスニング操作を行う必要があります.この操作は、警備員と同じです.誰かが来たら、これがリスニングです.この関数は次のように宣言されます.int listen(
SOCKET s, // socket
int backlog //
);
の2番目のパラメータbacklogは、私たちは一般的にあまりあげないでください.これはあなたが電気代を払うのと同じで、列に並んで待つのと同じです.列に並んでいる人が多くなると、悪い体験を残してしまいます.だから、勝手に10100をあげればいいです.リスニングが完了すると、受信クライアントの接続を行うことができます.acceptという関数を呼び出して接客する必要があります.次のように宣言します.
SOCKET accept(
SOCKET s, // socket
struct sockaddr FAR *addr, // sockaddr ,
int FAR *addrlen //sockaddr
);
お客様を迎えた後、私たちは通信することができます.recvとsendの2つの関数を呼び出してデータを送受信する必要があります.これらの宣言は以下の通りです.int recv(
SOCKET s, // socket
char FAR *buf, //
int len, //
int flags // , 0
);
int send(
SOCKET s, // socket
const char FAR *buf, //
int len, //
int flags // , 0
);
データを転送した後、WSACLeanupとclosesocketを呼び出してネットワーク環境とソケットを閉じる必要があります.次のように宣言します.int WSACleanup (void);
int closesocket(
SOCKET s //
);
サービス側のサンプルコードを添付します.#include
#include // windwos.h
#include
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
DWORD WINAPI clientProc(LPARAM lparam)
{
SOCKET sockClient = (SOCKET)lparam;
char buf[1024];
while (TRUE)
{
memset(buf, 0, sizeof(buf));
//
int ret = recv(sockClient, buf, sizeof(buf), 0);
//
if (SOCKET_ERROR == ret)
{
printf("socket recv failed
");
closesocket(sockClient);
return -1;
}
// 0
if (ret == 0)
{
printf("client close connection
");
closesocket(sockClient);
return -1;
}
//
ret = send(sockClient, buf, strlen(buf), 0);
//
if (SOCKET_ERROR == ret)
{
printf("socket send failed
");
closesocket(sockClient);
return -1;
}
}
closesocket(sockClient);
return 0;
}
bool InitNetEnv()
{
//
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed
");
return false;
}
return true;
}
int main(int argc, char * argv[])
{
if (!InitNetEnv())
{
return -1;
}
// , TCP socket
SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//
if (sServer == INVALID_SOCKET)
{
printf("socket failed
");
return -1;
}
printf("Create socket OK
");
//
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET; // IPV4
addrServ.sin_port = htons(PORT); // , , htons
addrServ.sin_addr.S_un.S_addr = INADDR_ANY; // ip ,INADDR_ANY IP
//
int ret = bind(sServer, (sockaddr *)&addrServ, sizeof(sockaddr));
//
if (SOCKET_ERROR == ret)
{
printf("socket bind failed
");
WSACleanup(); //
closesocket(sServer); //
return -1;
}
printf("socket bind OK
");
// ,
ret = listen(sServer, 10);
//
if (SOCKET_ERROR == ret)
{
printf("socket listen failed
");
WSACleanup();
closesocket(sServer);
return -1;
}
printf("socket listen OK
");
//
sockaddr_in addrClient; //
int addrClientLen = sizeof(sockaddr_in);
while (TRUE)
{
// socket,
SOCKET *sClient = new SOCKET;
//
*sClient= accept(sServer, (sockaddr*)&addrClient, &addrClientLen);
if (INVALID_SOCKET == *sClient)
{
printf("socket accept failed
");
WSACleanup();
closesocket(sServer);
delete sClient;
return -1;
}
//
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)clientProc, (LPVOID)*sClient, 0, 0);
}
closesocket(sServer);
WSACleanup();
return 0;
}
次に、クライアントを見てみましょう.クライアントは比較的簡単で、前の部分とサービス側は基本的に同じで、バインド操作に違いがあります.サービス側がバインドするIPアドレスは、本機のすべてのネットワークカードのIPであり、クライアントは1つだけバインドすればよい.クライアントにとって、指定されたサーバに接続するだけだからだ.割り当て済みSOCKADDR_IN構造の後、サービス側はbind関数を呼び出しますが、クライアントはconnect関数を呼び出す必要があります.その宣言は以下の通りです.
int connect(
SOCKET s, // socket
const struct sockaddr FAR *name, //SOCKADDR
int namelen //SOKADDR
);
接続に成功すると、サービス側と通信でき、recvとsendを呼び出してデータの送受信を行うことができます.サービス側プログラムがrecv操作を先に行う場合は、クライアントでsend操作を先に行うべきであり、2つが同時に同じ操作を行うと、recvとsendはブロック型の関数であるため、現在の位置に詰まってしまうことに注意してください.通信が完了すると、接続を閉じることができます.冒頭で述べたように、クライアントとサービス側が接続を開始したばかりのとき、両者はまずコミュニケーションを行います.このコミュニケーションには3つのステップが必要です.私たちは3回の握手と呼ばれています.同じように接続を閉じるときは、4つのステップが必要です.私たちは4回の握手と呼ばれています.もしあなたが乱暴で、直接ネットを抜くならば、それもその中の2回のステップを完成して、応用層として開発して、その中の原理を深く究める必要はありません.興味があれば、自分で資料を探すことができます.クライアントのサンプルコードを添付します.
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main(int argc, char * argv[])
{
//
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed
");
return -1;
}
// , TCP socket
SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sServer == INVALID_SOCKET)
{
printf("socket failed
");
return -1;
}
//
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(PORT);
// ,127.0.0.1
addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// Bind
int ret = connect(sServer,(SOCKADDR*)&addrServ,sizeof(SOCKADDR));//
if (SOCKET_ERROR == ret)
{
printf("socket connect failed
");
WSACleanup();
closesocket(sServer);
return -1;
}
// ,
char szBuf[1024];
memset(szBuf,0,sizeof(szBuf));
sprintf_s(szBuf,sizeof(szBuf),"Hello server");
// recv , send, , recv send
ret = send(sServer, szBuf, strlen(szBuf), 0);
if (SOCKET_ERROR == ret)
{
printf("socket send failed
");
closesocket(sServer);
return -1;
}
ret = recv(sServer, szBuf, sizeof(szBuf), 0);
if (SOCKET_ERROR == ret)
{
printf("socket recv failed
");
closesocket(sServer);
return -1;
}
printf("%s
",szBuf);
//
closesocket(sServer);
WSACleanup();
return 0;
}