Windows でのネットワーク プログラミング Winsock

序文

ws2_32.lib ライブラリ パスに注目してください: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64。

#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

ネットワークプログラミングは基本に比べて複雑ですが、実は決まったフォーマットがあるので覚えておきましょう。

1 つ目は、必要なヘッダー ファイルとライブラリです。

#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

1. サーバー下の Winsock

主なプロセスと主な機能:

  1. ネットワーク環境の初期化: WSAStartup。
  2. サーバーソケットを作成します:ソケット。
  3. ローカル IP とポートをバインドします: bind。
  4. リスニングクライアント:聞いてください。
  5. クライアント接続を待機しています:受け入れます。
  6. メッセージを送信します:送信します。
  7. メッセージを受信します: recv.
  8. ソケットを閉じる: closesocket。
  9. ネットワーク環境をクリーンアップします: WSACleanup。

WinsockWindows上のネットワーク プログラミングインターフェイスであり、Unix上のBSDSocketから開発されたもので、ネットワーク プロトコルとは何の関係もないプログラミング インターフェイスです。

1.1. プログラミング環境を構築します。

Winsock には、一般的なWindowsプラットフォーム上にWinsock1Winsock2 という2 つの主要なバージョンがありますWinsock1と互換性のあるプログラムを作成する場合はヘッダ ファイルWINSOCK.Hを参照する必要があり、 Winsock2を使用するプログラムを作成する場合はWINSOCK2.Hを参照する必要がありますさらに、Windowsプラットフォーム上の高性能ネットワーク プログラム拡張機能をサポートするために特別に使用されるMSWSOCK.Hヘッダー ファイルがあります。ヘッダファイルWINSOCK.Hを使用する場合はライブラリ ファイルWSOCK32.LIBが必要、WINSOCK2.Hを使用する場合はWS2_32.LIBが必要、MSWSOCK.Hの拡張APIを使用する場合はMSWSOCK.LIBが必要です。ヘッダー ファイルを正しく引用し、対応するライブラリ ファイルをリンクすると、 WINSOCKネットワーク プログラムを作成する環境が構築されます。

1.2、WSAデータ構造

WSADataの機能は、**Windowsソケット** の初期化情報を保存することです構造は次のとおりです。

struct WSAData {
    WORD wVersion;
    WORD wHighVersion;
    char szDescription[WSADESCRIPTION_LEN+1];
    char szSystemStatus[WSASYSSTATUS_LEN+1];
    unsigned short iMaxSockets;
    unsigned short iMaxUdpDg;
    char FAR * lpVendorInfo;
};
  • wVersion は、使用する Winsock のバージョン番号です。
  • wHighVersion は、ロードされたWinsockダイナミック ライブラリでサポートされる最高のバージョンです。上位バイトはマイナー バージョンを表し、下位バイトはメジャー バージョンを表すことに注意してください。
  • szDescriptionszSystemStatus はWinsockの特定のバージョンによって設定され、実際にはあまり役に立ちません。
  • iMaxSockets は同時Socketsの最大数を表し、その値は利用可能なハードウェア リソースによって異なります。
  • iMaxUdpDg はデータグラムの最大長を表しますが、データグラムの最大長を取得するには、WSAEnumProtocolsを使用してプロトコルをクエリする必要があります。同時ソケットの最大数は魔法の数字ではなく、利用可能な物理リソースによって決まります。
  • lpVendorInfoはWinsock実装用に予約されている製造元情報であり、Windows プラットフォームでは役に立ちません。

1.3. WSAStartup による Winsock の初期化

Winsockプログラミングを実行するときは、まずWSAStartup関数を呼び出し、プログラムで使用されるWinsock のバージョンを設定し、対応するバージョンのライブラリを初期化する必要があります。この関数はネットワーク環境を初期化するために使用され、パラメータは基本的に固定されているので、必ず存在することを覚えておいてください。

#include <winsock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
// 成功时返回0,失败时返回非零的错误代码值。
// wVersionRequested   程序员要用的Winsock版本信息。
// lpWSAData           WSADATA结构体变量的地址值。

#include<WinSock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32")
using namespace std;
int main() {
	WSADATA data;
	int ret=WSAStartup(MAKEWORD(2,2),&data);
	if (ret) {
		cout << "初始化网络错误!" << endl;
		return -1;
	}
}

上記 2 つのパラメータについて詳しく説明する必要があります。まず 1 つ目について説明します。Winsockには複数のバージョンがあり、 WORD型 ( WORDtypedef宣言で定義されたunsigned short型)のソケット バージョン情報を用意し、この最初のパラメーターwVersionRequestedに渡す必要があります。機能

  • バージョンが 1.2 (1 がメジャー バージョン番号、2 がマイナー バージョン番号) の場合、0x0201 を渡す必要があります。

  • 前述したように、上位8ビットをマイナーバージョン番号、下位8ビットをメインバージョン番号として送信する。この本では主にバージョン2.2を使用するため、 0x0202を渡す必要があります。

  • しかし、バージョン情報をバイト単位で手動で構築するのは面倒ですが、 MAKEWORDマクロ機能を利用すると簡単にWORD型のバージョン情報を構築できます。

    • MAKEWORD(1, 2) //メイン バージョンは 1、マイナー バージョンは 2、0x0201 を返します。

    • MAKEWORD(2, 2) //メイン バージョンは 2、マイナー バージョンは 2、0x0202 を返します。

次に、2 番目のパラメータlpWSADATAについて説明します。これは、 WSADATA構造体変数のアドレスを渡す必要があります( LPWSADATAはWSADATAのポインタ型です)。関数が呼び出された後、対応するパラメータには初期化されたライブラリ情報が設定されます。特に意味はありませんが、関数を呼び出すためにはWSADATA構造体の変数アドレスを渡す必要があります。

WSAStartup関数の呼び出し処理は以下のとおりでこのコードはほぼ Winsock プログラミングの公式になっています

#include<iostream>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[])
{
    WSADATA wsaDAta;
    // ...
    if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0) {
        ErrorHandling("WSAStartup() error!");
    }
    // ...
    return 0;
}

Winsockプログラムは、WSAStartup関数を使用して、適切なWinsockダイナミック リンク ライブラリをロードする必要があります。ロードが失敗した場合、WSAStartup関数はSOCKET_ERRORを返しますこのエラーはWSANOTINITIALISEDです。WSAStartup関数の定義は次のとおりです:

int WSAStartup(
    WORD wVersionRequested,
    LPWSADATA lpWSAData
);
  • wVersionRequested はロードするWinsockバージョンを指定し、上位バイトはマイナー バージョン番号を指定し、下位バイトはメジャー バージョン番号を指定します。マクロMAKEWORD(x, y)を使用してバージョン番号を指定できます。ここで、 xはメジャー バージョンを表し、yはマイナー バージョンを表します。
  • lpWSADataはWSAData構造体へのポインタでありWSAStartup関数は、ロードするWinsockダイナミック リンク ライブラリの情報を構造体に書き込みます。
typedef struct WSAData{
    WORD           wVersion;
    WORD           wHighVersion;
    char           szDescription[WSADESCRIPTION_LEN + 1];
    char           szSystemStatus[WSASYS_STATUS_LEN + 1];
    unsigned short iMaxSockets;
    unsigned short iMaxUdpDg;
    char FAR *     lpVendorInfo;
} WSADATA, * LPWSADATA; 
  • wVersionは使用するWinsock のバージョン番号、 wHighVersion はロードされたWinsockダイナミック ライブラリでサポートされる最高のバージョンです。上位バイトはマイナー バージョンを表し、下位バイトはメジャー バージョンを表すことに注意してください。

  • szDescriptionszSystemStatus はWinsockの特定のバージョンによって設定され、実際にはあまり役に立ちません。

  • iMaxSockets は同時Socketsの最大数を表し、その値は利用可能なハードウェア リソースによって異なります。

  • iMaxUdpDg はデータグラムの最大長を表しますが、データグラムの最大長を取得するには、WSAEnumProtocolsを使用してプロトコルをクエリする必要があります。

  • 同時ソケットの最大数は魔法の数字ではなく、利用可能な物理リソースによって決まります。

  • lpVendorInfoはWinsock実装用に予約されている製造元情報であり、 Windowsプラットフォームでは役に立ちません。

Windows 95以降のオペレーティング システムはWinsock 2.2バージョンをサポートしています。たとえそうであっても、これらのWindowsプラットフォームが最新のWinsockバージョンをサポートしているとは考えられません。プログラムをほとんどのプラットフォームで実行するには、Winsock1.1仕様を使用するのが最善です。

1.4、WSACleanup リリース Winsock

Winsock インターフェイスの使用が終了したら、次の関数を呼び出して、Winsock インターフェイスが占有しているリソースを解放します。

#include<winsocket2.h>
int WSACleanup(void);

成功した場合は0を返し、失敗した場合はSOCKET_ERRORを返します。この関数が呼び出されると、Winsock関連ライブラリはWindowsオペレーティング システムに返され、 Winsock関連関数は呼び出せなくなります。この関数は原則としてWinsock関数を使用する必要がない場合に呼び出されますが、通常はプログラムの終了前に呼び出されます。

この関数の呼び出しが失敗しても問題はありません。オペレーティング システムがこの関数を自動的に解放し、各WSAStartup関数呼び出しに対応するWSACleanup関数呼び出しが存在するはずです

エラー処理:ほとんどのWinsock関数呼び出しは失敗し、 SOCKET_ERROR (実際には -1)を返します。WSAGetLastError関数を呼び出して、エラーの詳細情報を取得できます。

int WSAGetLastError (void);

この関数を呼び出すとエラー コードが返され、そのコード値はWINSOCK.HまたはWINSOCK2.H (バージョンに応じて) で定義されており、これらの事前定義された値はWSAEで始まります。WSASetLastErrorを使用してエラー コード値をカスタマイズします。

#include<iostream>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")

void main(void){
   WSADATA wsaData;
    
    int Ret;

   // Initialize Winsock version 2.2

   if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
   {
      // NOTE: Since Winsock failed to load we cannot use
      // WSAGetLastError to determine the specific error for
      // why it failed. Instead we can rely on the return
      // status of WSAStartup.

      printf("WSAStartup failed with error %d\n", Ret);
      return;
   }

   // Setup Winsock communication code here

   // When your application is finished call WSACleanup
   if (WSACleanup() == SOCKET_ERROR)
   {
      printf("WSACleanup failed with error %d\n", WSAGetLastError());
   }
    	
}

1.5、socket はソケットを作成します

関数プロトタイプ:

SOCKET socket(
    int af,		 //地址类型,常用IPv4地址:AF_INET,和IPv6地址:AF_INET6
    int type,	 //套接字类型,常用TCP协议:SOCK_STREAM,UDP协议:SOCK_DGRAM
    int protocol //协议类型,一般填0,自动选择即可
);
//返回值,INVALID_SOCKET失败,该宏实则定义为-1,否则成功

使用:

    SOCKET serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字
    if (serverSock == INVALID_SOCKET)
    {
        cout << "socket failed!" << endl;
        WSACleanup(); //释放Winsock库资源
        return 1;
    }

このコードは、タイプ IPv4 のアドレス、つまり TCP プロトコルのソケットを作成します。
ここに画像の説明を挿入

ネットワーク プログラミングは、ネットワークに接続された 2 台のコンピュータが相互にデータを交換できるようにするプログラムを作成することです。必要なのはそれだけですか? はい! ネットワーク プログラミングは、思っているよりもはるかに簡単です。では、これら 2 台のコンピューター間でデータを送信するには何が使用されるのでしょうか?

  • 最初に物理的な接続が必要です。最近のほとんどのコンピューターは広大なインターネットに接続されていますので、心配する必要はありません。

  • そこからは、データ転送ソフトウェアを作成する方法を考えてください。しかし実際には、オペレーティング システムが **「ソケット」(ソケット)** と呼ばれるコンポーネントを提供するため、これについて心配する必要はありません。

  • ソケットは、ネットワーク データ送信に使用されるソフトウェア デバイスです。

  • ネットワークのデータ伝送の原理を知らなくても、ソケットを介してデータ伝送を完了できるため、ネットワークプログラミングはソケットプログラミングとも呼ばれます。

  • では、なぜ「ソケット」という言葉を使うのでしょうか?

  • 私たちがコンセントにプラグを差し込むことで電力網から電力を得るのと同じように、リモート コンピューターにデータを転送するにはインターネットへの接続が必要です。

  • プログラミングにおける「ソケット」とは、ネットワークに接続するためのツールです。

  • それ自体は「接続」という意味を持ち、拡張すると2台のコンピュータ間のネットワーク接続を意味することもあります。

  • ソケットには大きく分けて 2 種類あり、そのうち最初に説明するTCP ソケットは電話にたとえられます。実際には、電話機も固定電話網(電話網)を介して音声データの交換を完結します。したがって、私たちが使い慣れている固定電話とコンセントの間には、実際には大きな違いはありません。

以下では、電話を使用してソケットの作成と使用について説明します。

  • 電話機を使用すると、同時にダイヤルに接続することも、リッスンして受信することもできますが、ソケットの場合は、ダイヤルに接続することと、リッスンして受信することには違いがあります。まず、リッスンするためのソケットの作成プロセスについて説明します。

  • 電話をかけたり受けたりするには電話機が必要です。電話機があってこそ電話機を設置できます。次に、美しい電話機を用意します。

  • 次の関数は、電話と同等のソケットを作成します。

#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
// 成功时返回套接字句柄,失败时返回INVALID_SOCKET。

1.6、bind はソケットをバインドし、それを呼び出して IP アドレスとポート番号を割り当てます。

関数プロトタイプ:

int bind( 
    SOCKET s, //创建的socket
    sockaddr * name, //包含地址和端口的结构体
    int namelen //sockaddr 结构长度
);
//返回值:返回SOCKET_ERROR失败,该宏被定义为-1,否则成功,返回值为0

使用:

#define _WINSOCK_DEPRECATED_NO_WARNINGS //vs环境下必须定义,否则无法使用inet_addr函数
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(12345);
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //具体绑定本机的地址
if (bind(serverSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) //绑定套接字
{
    cout << "bind failed!" << endl;
    closesocket(serverSock); //关闭套接字
    WSACleanup(); //释放Winsock库资源
    return 1;
}

ここに画像の説明を挿入

  • 私たちは機械を購入するだけで、あとの設置や電話番号の割り当てなどは総合通信局の職員がやってくれます。
  • ソケットは自分でインストールする必要があり、これがソケットプログラミングの難しさでもありますが、数回インストールすることは難しくありません。
  • 電話の準備ができたら、他の人が連絡できるように電話番号を割り当てることを検討してください。
  • ソケットについても同様です。電話機に電話番号を割り当てるのと同じように(実際には電話番号が電話機に割り当てられるわけではありません)、次の関数を使用して、作成したソケットにアドレス情報(IP アドレスとポート番号)を割り当てます
#include<winsock2.h>
int bind(SOCKET s, const struct sockaddr *name, int namelen);
// 成功时返回0,失败时返回SOCKETERROR。

1.7、リッスンリスニングソケット

関数プロトタイプ:

int listen(
    SOCKET s, //要监听的socket
    int backlog //等待连接的最大队列长度
);
//返回值:返回SOCKET_ERROR失败,该宏被定义为-1,否则成功,返回值为0

使用:

if (listen(serverSock, SOMAXCONN) == SOCKET_ERROR) //监听套接字
{
    cout << "listen failed!" << endl;
    closesocket(serverSock); //关闭套接字
    WSACleanup(); //释放Winsock库资源
    return 1;
}

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-k9bNK2U0-1686126105795) (C:\Users\Administrator\AppData\Roaming\Typora\) typora-user-images\ image-20230607154514419.png)]

  • バインド関数を呼び出してソケットにアドレスを割り当てたら、基本的に電話に出るための準備はすべて完了します。次に、電話回線を接続し、着信を待つ必要があります。

  • 電話回線が接続されるとすぐに、電話は応答可能な状態になり、その時点で他の人が電話への接続を要求する電話をかけることができます。同様に、ソケットは接続を受け入れられる状態にする必要があります。

#include <winsock2.h>
int listen(SOCKET s, int backlog);
// 成功时返回0,失败时返回SOCKETERROR。

1.8、acceptはクライアント接続リクエストを受け入れます

関数プロトタイプ:

SOCKET accept(
    SOCKET s, //接收的socket
    sockaddr* addr, //接收到客户端的地址信息
    int * addrlen //地址信息长度
);
//返回值:返回INVALID_SOCKET失败,该宏定义为-1,否则成功返回客户端的套接字,可进行发送和接收消息

使用:

SOCKET clientSock = accept(serverSock, NULL, NULL); //接受客户端连接
if (clientSock == INVALID_SOCKET)
{
    cout << "accept failed!" << endl;
    closesocket(serverSock); //关闭套接字
    WSACleanup(); //释放Winsock库资源
    return 1;
}

cout << "Client is connected." << endl;

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-iQGqGQoZ-1686126105796) (C:\Users\Administrator\AppData\Roaming\Typora\) typora-user-images\ image-20230607154655976.png)]

  • 電話回線が接続された後、誰かが電話をかけると呼び出し音が鳴り、受話器を取らないと電話に出ることができません。

  • マイクを取るということは、相手の接続要求を受け入れることを意味します。ソケットについても同様で、データ送信を完了するために誰かが接続をリクエストした場合、それを受け入れるために次の関数を呼び出す必要があります。

#include<winsock2.h>
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
// 成功时返回套接字句柄,失败时返回INVALID_SOCKET。

ネットワーク プログラミングで接続要求を受け入れるためのソケット作成プロセスは、次のように構成できます。

  • ステップ 1:ソケット関数を呼び出してソケットを作成します。
  • ステップ 2:バインド関数を呼び出して、IP アドレスとポート番号を割り当てます。
  • ステップ 3: listen関数を呼び出して、リクエストを受信できる状態に切り替えます。
  • ステップ 4: accept関数を呼び出して、接続要求を受け入れます。

これらの手順を覚えてマスターすることはソケットプログラミングの輪郭を描くことに相当し、その後のプログラミングはこの輪郭に色を付けていきます。

1.9、closesocket はソケットを閉じます

#include<winsock2.h>
int closesocket(SOCKET s);
// 成功时返回0,失败时返回SOCKETERROR。

int closesocket(
    SOCKET s //要关闭的socket
);

この機能は、未使用のソケットを閉じ、リソースを解放します。

1.10、Windows での io 関数

1.10.1、送信機能

関数プロトタイプ:

int send(
    SOCKET s,
    char * buf,//要发送的内容
    int len, //内容长度
    int flags //一般为0,拷贝到程序中就立即删除内核中的数据,或MSG_DONTROUTE:要求传输层不要将数据路由出去,MSG_OOB:标志数据应该被带外发送
);
//返回值:-1(或宏SOCKET_ERROR)表示发送失败,否则返回发送成功的字节数

使用:

char buf[0xFF] = "我是服务器";
ret=send(sockCli, buf, strlen(buf),0);
if (ret == -1) {
	cout << "发送信息失败";
}

Windowsでは、ファイルI/O機能とソケットI/O機能が厳密に区別されています。Winsockのデータ転送送信機能について説明します

#include<winsock2.h>
int send(SOCKET s, const char *buf, int len, int flags);
// 成功时返回传输字节数,失败时返回SOCKETERROR。
// s        表示数据传输对象连接的套接字句柄值。
// buf      保存待传输数据的缓冲地址值。
// len      要传输的字节数。
// flags    传输数据时用到的多种选项信息。

1.10.2、recv関数

関数プロトタイプ:

int recv(
    SOCKET s, //套接字
     char * buf, //接受数据的缓存区
    int len, //缓存区大小
    int flags //标志,一般填0,将消息拷贝到应用程序中,将内核中的数据删除,还可以填MSG_PEEK,只取数据,不从内核中删除数据,MSG_OOB:处理带外数据
);
//返回值:小于等于0都表示出错,大于0则表示接收成功的数据大小

使用:

ret=recv(sockCli,buf,0xFF,0);
if (ret <= 0) {
	cout << "接受客户端数据失败";
	return -1;
}

Winsockデータ受信recv関数を導入します

#include<winsock2.h>
int recv(SOCKET s, const char *buf, int len, int flags);
// 成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKETERROR。
// s        表示数据接收对象连接的套接字句柄值。
// buf      保存接收数据的缓冲地址值。
// len      能够接收的最大字节数。
// flags    接收数据时用到的多种选项信息。

1.11、完全なサーバーコード

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WinSock2.h>
#pragma comment(lib,"ws2_32")
#include<iostream>
using namespace std;
int main() {
	WSADATA data;
	int ret=WSAStartup(MAKEWORD(2,2),&data);
	if (ret) {
		cout << "初始化网络错误!" << endl;
		WSACleanup();
		return -1;
	}
	SOCKET sock=socket(AF_INET,SOCK_STREAM,0);
	if (sock == -1) {
		cout << "创建套接字失败";
		WSACleanup();
		return -1;
	}
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9999);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	ret=bind(sock,(sockaddr*)&addr, sizeof(addr));
	if (ret == -1) {
		cout << "绑定地址端口失败";
		WSACleanup();
		return -1;
	}
	ret=listen(sock,5);
	if (ret == -1) {
		cout << "监听套接字失败";
		WSACleanup();
		return -1;
	}
	sockaddr addrCli;
	int len = sizeof(addrCli);
	SOCKET sockCli=accept(sock,&addrCli,&len);
	if (sockCli == -1) {
		cout << "接收客户端连接失败";
		WSACleanup();
		return -1;
	}
	char buf[0xFF] = "我是服务器";
	ret=send(sockCli, buf, strlen(buf),0);
	if (ret == -1) {
		cout << "发送信息失败";
		WSACleanup();
		return -1;
	}
	ret=recv(sockCli,buf,0xFF,0);
	if (ret <= 0) {
		cout << "接受客户端数据失败";
		WSACleanup();
		return -1;
	}
	WSACleanup();
}

2. クライアント配下の Winsock

主なプロセスと機能:

  1. **ネットワーク環境を初期化します: **WSAStartup。
  2. ソケットを作成します: ソケット。
  3. **サーバーに接続します: **接続します。
  4. **データの送信: **送信。
  5. **受信データ: **recv.
  6. **ネットワーク環境をクリーンアップします: **WSACleanup。

他の3つの機能はサーバーと同じですが、追加のconnect機能があり、使用方法はバインド機能に似ています

2.1、接続機能

関数プロトタイプ:

int connect(
    SOCKET s, //与服务器连接的socket
    sockaddr* name, //服务器的地址端口
    int namelen //上个参数结构体的长度
);
//返回值:-1失败,否则成功

手順:

sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(sock, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
	cout << "连接服务器失败" << endl;
	return -1;
}

2.2. 完全なクライアントコード

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main() {
	WSADATA data;
	int ret = WSAStartup(MAKEWORD(2, 2), &data);
	if (ret) {
		cout << "初始化网络错误!" << endl;
		WSACleanup();
		return -1;
	}
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9999);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = connect(sock, (sockaddr*)&addr, sizeof(addr));
	if (ret == -1) {
		WSACleanup();
		cout << "连接服务器失败" << endl;
		return -1;
	}
	char buf[0xFF];
	ret=recv(sock,buf,sizeof(buf),0);
	if (ret <= 0) {
		WSACleanup();
		cout << "接收服务器数据失败" << endl;
		return -1;
	}
	cout << "服务器:" << buf << endl;

	ret=send(sock,buf,ret,0); //将接收到的数据发回服务器
	if (ret <= 0) {
		WSACleanup();
		cout << "发送服务器数据失败" << endl;
		return -1;
	}
	WSACleanup();
}

3. Windows 下のサーバー コード表示

#include <WinSock2.h>
#include <cstring>
#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        return 0;
    }

    // 初始化库
    WSADATA wsaData;
    int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (stu != 0) {
        std::cout << "WSAStartup 错误:" << stu << std::endl;
        return 0;
    }

    // 创建socket
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, 0);
    if (servSock == INVALID_SOCKET) {
        std::cout << "socket 错误" << std::endl;
        return 0;
    }

    // 初始化服务器地址信息
    sockaddr_in servAddr;
    std::memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(std::atoi(argv[1]));

    // 调用bind函数绑定地址信息
    stu = bind(servSock, (sockaddr*)&servAddr, sizeof(servAddr));
    if (stu == SOCKET_ERROR) {
        closesocket(servSock);
        std::cout << "bind 错误" << std::endl;
        return 0;
    }

    // 调用listen函数,进入监听状态
    stu = listen(servSock, 5);
    if (stu == SOCKET_ERROR) {
        closesocket(servSock);
        std::cout << "listen 错误" << std::endl;
        return 0;
    }

    sockaddr_in clntAddr;
    int clntAddrSize = sizeof(clntAddr);
    // 接收请求
    SOCKET clntSock = accept(servSock, (sockaddr*)&clntAddr, &clntAddrSize);
    if (clntSock == INVALID_SOCKET) {
        closesocket(servSock);
        std::cout << "accept 错误" << std::endl;
        return 0;
    }

    std::string msg = "Hello World!";
    send(clntSock, msg.c_str(), msg.size(), 0);

    closesocket(clntSock);
    closesocket(servSock);

    // 清理库
    WSACleanup();

    return 0;
}

3. Windowsクライアントコード表示

#include <WinSock2.h>
#include <cstring>
#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " IP port" << std::endl;
        return 0;
    }

    // 初始化库
    WSADATA wsaData;
    int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (stu != 0) {
        std::cout << "WSAStartup 错误:" << stu << std::endl;
        return 0;
    }

    // 创建socket
    SOCKET clntSock = socket(PF_INET, SOCK_STREAM, 0);
    if (clntSock == INVALID_SOCKET) {
        std::cout << "socket 错误" << std::endl;
        return 0;
    }

    // 初始化服务端地址信息
    sockaddr_in servAddr;
    std::memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(std::atoi(argv[2]));

    // 连接到服务端
    stu = connect(clntSock, (sockaddr*)&servAddr, sizeof(servAddr));
    if (stu == SOCKET_ERROR) {
        closesocket(clntSock);
        std::cout << "connect 错误" << std::endl;
        return 0;
    }

    char msg[30] = { 0 };
    int strLen = recv(clntSock, msg, sizeof(msg) - 1, 0);
    if (strLen == -1) {
        closesocket(clntSock);
        std::cout << "recv 错误" << std::endl;
        return 0;
    }

    std::cout << "接收:" << msg << std::endl;
    closesocket(clntSock);

    WSACleanup();

    return 0;
}

4. ソケットの説明

4.1、テキスト

Socketをわかりやすく説明するには、まずTCP を理解する必要があります。TCP を知りたい場合は、 TCP/IPのアーキテクチャと各層の一般的な動作を理解する必要があります。次に、 TCP/IPの階層化について説明します。

4.2. TCP/IP アーキテクチャ

まずOSI参照モデルについて簡単に説明しますと、 OSIではネットワークを下から物理層、データリンク層、ネットワーク層、トランスポート層、セッション層、プレゼンテーション層、アプリケーション層の7層に分割しています。 / IPシステム ネットワークを下からネットワークインターフェース層、ネットワーク層、トランスポート層、アプリケーション層の4層に分けた構造です。各層を分かりやすく説明するために、ネットワークインターフェース層を物理層とデータリンク層に分け、 OSI参照モデルとTCP/IPアーキテクチャの比較図を「図解TCP/IP」に掲載しました。
ここに画像の説明を挿入

TCP/IPの層を紹介した後、各層の一般的な機能について説明します。コンピュータの世界はとても素晴らしく、現実世界と一対一に対応するものがたくさんありますが、これはコンピュータの設計者が意図的に行っているのかもしれません。まず、ネットワークにおけるデータパケットの伝送プロセスについて説明し、その後物流の例で説明すると、各層の役割が理解できると思います。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-guLSkVpC-1686126105797) (C:\Users\Administrator\AppData\Roaming\Typora\) typora-user-images\ image-20230607151543291.png)]

上の図を見ると、送信者は受信者にデータを送信したいと考えています。まず、アプリケーション層は送信するデータを準備し、それをトランスポート層に渡します。トランスポート層の主な機能は、送信者と受信者に信頼性の高い接続サービスを提供することです。トランスポート層はデータを処理して送信します。ネットワーク層

ネットワーク層の機能はネットワークを管理することです。中心的な機能の 1 つはパス選択 (ルーティング) です。送信者から受信者までのパスは多数あります。ネットワーク層は、次のデータがどのルーターに送信されるかを管理する責任があります。 。パスが選択されると、データはデータリンク層に届きます。データリンク層は、あるルーターから別のルーターにデータを送信する役割を果たします。次に物理層ですが、物理層はネットワークケーブルなどの最も基本的な機器であると簡単に理解できます。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-exWb9f5m-1686126105798) (C:\Users\Administrator\AppData\Roaming\Typora) \typora-user-images\ image-20230607151651465.png)]

Xiao Ming は上海市長江路興福区 5#666 に住んでおり、現在 Xiao Ming は JD.com で Mi 10 Pro を購入しました。JingdongがXiaomiから注文を受けた後、スタッフは倉庫からXiaomi 10Pro(アプリケーション層)を見つけました。スタッフは携帯電話を梱包し、京東物流(輸送層)に引き渡しましたその後、携帯電話は転送センター(ルータ)に到着し、転送センターは時間やコストなどの一連の要素に応じて、次にどの転送センター(ネットワーク層)に送信するかを決定します。次にどの積み替えセンターに送るかを決定したら、トラックが輸送を開始します。その後、輸送プロセスは、あるエンドポイントから別のエンドポイントにデータを送信する役割を担うデータリンク層になります。そして、トラックが走行する道路が物理層になります。数回のラウンドの後、携帯電話はシャオミンに無事届けられた。

この例を読めば、誰もがネットワーク内でのデータ転送のプロセスと各層の役割を理解できるはずです。それでは、もう一度TCPについて説明しましょう

4.3、TCPプロトコル

まず、Baidu Encyclopedia によるTCPプロトコルの定義を見てみましょう。 **伝送制御プロトコル (TCP、Transmission Control Protocol)** は、コネクション指向で信頼性の高いバイトストリームベースのトランスポート層通信プロトコルです。このテキストは何を意味しますか? 上の例を続けてみましょう。

配送前に、スタッフはまず道路が開いているかどうかを確認する必要があります。たとえば、今日は旧正月で、すべての物流が停止しているため、道路が封鎖されているのに、なぜ商品を送る必要があるのでしょうか。問題がなく、物流がすべて正常に動作している場合、商品を出荷いたします。シャオミンさんの家に携帯電話が到着した後、シャオミンさんはまず携帯電話を分解して輸送中に破損していないか確認し、破損があればカスタマーサービスに連絡して対応し、問題がなければカスタマーサービスに連絡した。受信を確認します。上記の定義に戻りますが、コネクション指向とは、データを送信する前にまずコネクションを確立すること、つまり、配送する前に道路が通過できることを確認することを指します。信頼性とは、輸送中に商品が破損または紛失した場合、Xiao Ming が受け取った携帯電話に問題がないことを確認するために、JD.com が商品を再配達することを意味します。バイト ストリームに基づくということは、たとえば、Xiao Ming が携帯電話と携帯電話アクセサリを購入し、それらを別々に発送したことを意味します。複数の品目は 1 つのパッケージで送られるのではなく、1 つずつ送られてきます。この例では、JD.com のスタッフと Xiao Ming が TCP プロトコルの役割を果たし、二人が共同で商品の完全性を保証しました

4.4、ソケット

TCP/IPアーキテクチャとTCPプロトコルの一般的な内容を説明した後、ソケットとは何かについて説明しますまずは Baidu Encyclopedia のSocketの紹介を見てみましょう: Socket は、アプリケーションがデータを送受信できる抽象化レイヤーであり、ファイルのように開いたり、読み取ったり、書き込んだり、閉じたりすることができます。ソケットを使用すると、アプリケーションはネットワークに I/O を挿入し、ネットワーク上の他のアプリケーションと通信できるようになります。ネットワーク ソケットは、IP アドレスとポートの組み合わせです

コミュニティをコンピュータに例えます。コンピュータでは多くのプログラムが実行されます。プログラムを区別するにはどうすればよいですか? コミュニティが各世帯を区別するために番地を使用するのと同じように、ポートが使用されます。携帯電話はシャオミンの家に送られてきたのですが、どうすれば入ることができますか? 門を通って入るのですが、どうやって門を見つけますか?家屋番号。インターネットから受信側のコンピュータを見つけて、ポートに応じてどのプログラムを与えるべきかを判断するのと同じではないでしょうか。Xiaoming の家の入り口は、コミュニティの住所 + 家番号で一意に表すことができ、同様に、プログラムもIP + ポート番号で一意に識別できます。このプログラムのエントリはSocketと呼ばれます。

Socektプログラミングとは何かについて話しましょう。TCPプロトコルを単純化してみましょう。コア機能は 3 つだけです:接続の確立、データの送信、データの受信です
ここに画像の説明を挿入

5. その他のネットワーク関連機能

5.1、htons、ntohs など

このような関数名には固定された意味があります。

  • ひ:家です。このマシン
  • n: ネットワーク。通信網
  • S:短い。ショートタイプ
  • l: 長いです。ロングタイプ

**htons:** は、ネイティブ バイト オーダーがネットワーク バイト オーダーとshort型の長さに転送されることを意味します。
**ntohs:** は、ネットワーク バイト オーダーがネイティブ バイト オーダーとshort型の長さに変換されることを意味します。

同様の意味を持つhtonl、htonll、htonfなどもあります。

5.2、inet_addr、inet_ntoa

  • inet_addr:通常見られるネットワーク アドレス127.0.0.1をネットワーク バイト オーダーに変換します
  • inet_ntoa:ネットワークのバイトオーダーを、通常見られる文字列127.0.0.1に復元する役割を果たします

手順:

sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //将127.0.0.1转换为网络字节序
char* c_IP = inet_ntoa(addr.sin_addr);//将网络字节序转换为127.0.0.1字符串

5.3、gethostbyname

ドメイン名からIPアドレスを取得します。たとえば、一般的なwww.baidu.comIPアドレスは何ですか? この関数を使用して取得できます

手順:

//获取主机ip
HOSTENT* host = gethostbyname("www.baidu.com"); //如获取网站IP地址,参数填写域名即可,不需加"http://"
if (host == NULL)
{
	return false;
}
//转化为char*并拷贝返回
cout << inet_ntoa(*(in_addr*)*host->h_addr_list);

5.4. 注意事項

これらの関数は Microsoft によって安全でない関数として定義されています。これらを通常どおり使用したい場合は、コードの先頭でマクロを定義する必要があります。

#define _WINSOCK_DEPRECATED_NO_WARNINGS

おすすめ

転載: blog.csdn.net/qq_44918090/article/details/131090585