Windowsでのソケットプログラミングの原則と段階的な実装

序文

数年前にソケットプログラミングを書きましたが、長い間使用していなかったため、このテクノロジーを忘れていました。最近、msfペイロードの実行原理を研究していて、ソケットプログラミング技術を使わなければならなかったので、この記事が作成されました。

この記事はmsfのテクノロジーとは関係ありません。基本的な記事なので、この記事を読んだ後、msfペイロードの原則を読み続けると、混乱を感じるでしょう。


Windowsでのソケットプログラミングの機能

Pythonベースのソケットプログラミングと比較して、Windowsでのソケットプログラミングは非常に複雑です。複数のステップが必要であり、複数のデータ構造をセットアップする必要があります。
Pythonが必要とするのは2行だけです
ここに画像の説明を挿入
。C言語では多くの行が必要です
ここに画像の説明を挿入
しかし、ここでは、ソケットが何であるかを理解しやすいので、Cでソケットを記述する方法を誰もが知ってほしいと思っています。


コード(サーバー)

Windows Sock仕様のバージョン番号を設定する

Windowsでソケットをプログラムするために最初に必要なことは、ソケットのバージョンを設定することです。これは、2つのパーティが通信するためにソケット接続に使用するプロトコルのバージョンとして理解できます。これは、ソケットを作成するときの最初のステップです。

void set_editor() {
    
    
	printf("开始设置版本号");
	WSADATA	container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
	WORD 		version_number; //存储版本号的容器

	version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1

	if (WSAStartup(version_number, &container) < 0) {
    
    // 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
		printf("ws2_32.dll is out of date.\n");
		WSACleanup();//放弃加载ws2_32.dllwin。    备注:socket连接必须加载这个dll
		exit(1);//退出程序,并输出错误码‘1’。
	}
	else
	{
    
    
		printf("win_socket规范版本设置成功\n");
	}
}

ソケットの関連属性(ip、プロトコルなど)を定義します。

私の理解では、ソケットを作成するには2つのステップに分割する必要があります。最初にgetaddrinfo関数を使用して、ソケットの特定の属性(ipバージョン、tcp接続またはudp接続、socktypeなどのサポート)を定義します。次に、socket関数を使用して完全なソケットを作成します。具体的なコアコードは次のとおりです。

	struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
	struct addrinfo hints; //存储着服务端的socket的相关详细数据。
	ZeroMemory(&hints, sizeof(hints)); //初始化hints

	hints.ai_family = AF_INET;//IPV4
	hints.ai_socktype = SOCK_STREAM;//tcp模式
	hints.ai_protocol = IPPROTO_TCP;//基于tcp协议


	printf("开始执行getaddrinfo\n");
	int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
	if (Result != 0)
	{
    
    
		printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
		safeexit();
	}

	printf("开始创建socket\n");
	SOCKET sockett;
	sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
	if (sockett == INVALID_SOCKET) {
    
    
		printf("socket创建失败 %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		safeexit();//这是我自己写等函数,用于安全退出socket。
	}

バインドポートとIP

バインドポートとIPの使用法はバインド関数です。バインドは、作成したソケットを特定のポートにバインドすることと理解できます。現時点では、他のホストがこのポートにアクセスでき、ポートにアクセスできます。これは、ソケット接続を確立します。
最初のステップでgetaddrinfoを使用してソケット情報を定義したので、現時点ではそれを直接使用できます。

    printf("开始bind端口\n");
	Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
	if (Result == SOCKET_ERROR) {
    
    
		printf("绑定失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(sockett);
		safeexit();
	}
	freeaddrinfo(result);//释放result使用的内存

bindのパラメーターの取得方法については、公式のMicrosoftドキュメントを確認できますが、ここでは直接回答します。

付録:
getaddrinfo関数構造
addrinfo構造
bind関数構造
sockaddr構造


リスニングポート

情報をバインドしたら、ポートを開いて待機します。

	printf("开始listen\n");
	Result = listen(sockett,SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
	if (Result == SOCKET_ERROR) {
    
    
		printf("监听失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}

全体は比較的単純で特別なものはありません。


接続を待っています

ホストが接続を希望する場合は、accept関数を呼び出して接続要求を受け入れる必要があります。具体的なコードは次のとおりです。

    char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	closesocket(sockett);
	printf("连接建立成功,关闭socket,准备传输数据\n");

付録:関数構造を受け入れる

それについてお話しましょう。受け入れ後の2つのパラメーターの意味は、バインドの3つのパラメーターの意味と同じです。リンク先のホストに制限を課すために、ここでNULLを使用しているだけです。制限したい場合は、 addrinfo構造オブジェクトを定義すると、指定された条件でホストを制限して、このソケットに接続できます。

注意:accept関数を使用した後、新しいソケット接続が生成され、前に作成されたソケットはclosesocket関数を使用して破棄できます。

データを送る

上記のすべての手順が完了すると、接続が確立されます。この時点で、データの送信を選択できます。具体的なコードは次のとおりです。

	char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	printf("连接建立成功,关闭socket,准备传输数据\n");
	send(another_socket, recbuf, strlen(recbuf) + 1, 0);  // 发送显示欢迎信息
	int n = 20;
	char recvBuf[11] = "1234567890";
	int zonghe = 0;
	while (n--) {
    
        // 不断等待客户端请求的到来
		char* wel = NULL;
		wel = "Please enter the data:";
		printf("一共传输20次数据,还剩%d次:\n", n + 1);
		int num = recv(another_socket, recvBuf, 6, 0);
		printf("%s : %s\n", sockConnName, recvBuf);     // 接收信息
		printf("当前轮次接收数据量为:%d Byte\n", num);
		zonghe = zonghe + num;
		printf("一共接受数据量为:%d Byte\n", zonghe);
		send(another_socket, wel, strlen(wel) + 1, 0);  // 发送显示欢迎信息
		if (num == SOCKET_ERROR)
		{
    
    
			safeexit();
		}
	}


	printf("数据接收完毕,退出程序");
	shutdown(sockett, SD_SEND);
	safeexit();

ここで、2つの関数sendとrecvについて説明しておきます。これら2つの関数のパラメーターは同じです。これらは、確立されたソケット接続の記述子、データを格納するバッファー、送信または受信したデータの長さ、最後の1つです。フラグビットの具体的な使用方法については、こちらの記事を参照してください。通常、0を使用することをお勧めします。


完全なサーバーコードを以下に示します。使用する場合は、ncコマンドを使用して、対応するポート番号に直接接続できます。

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"


void safeexit()
{
    
    
	WSACleanup();
	exit(1);
}
/* 设置windows Sock规范版本号 */
void set_editor() {
    
    
	printf("开始设置版本号");
	WSADATA	container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
	WORD 		version_number; //存储版本号的容器

	version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1

	if (WSAStartup(version_number, &container) < 0) {
    
    // 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
		printf("ws2_32.dll is out of date.\n");
		WSACleanup();//放弃加载ws2_32.dllwin。    备注:socket连接必须加载这个dll
		exit(1);//退出程序,并输出错误码‘1’。
	}
	else
	{
    
    
		printf("win_socket规范版本设置成功\n");
	}
}

void llisten()
{
    
    
	struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
	struct addrinfo hints; //存储着服务端的socket的相关详细数据。
	ZeroMemory(&hints, sizeof(hints)); //初始化hints

	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;


	printf("开始执行getaddrinfo\n");
	int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
	if (Result != 0)
	{
    
    
		printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
		safeexit();
	}

	printf("开始创建socket\n");
	SOCKET sockett;
	sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
	if (sockett == INVALID_SOCKET) {
    
    
		printf("socket创建失败 %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		safeexit();
	}
	printf("开始bind端口\n");
	Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
	if (Result == SOCKET_ERROR) {
    
    
		printf("绑定失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(sockett);
		safeexit();
	}
	freeaddrinfo(result);//释放result使用的内存

	printf("开始listen\n");
	Result = listen(sockett, SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
	if (Result == SOCKET_ERROR) {
    
    
		printf("监听失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}


	char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	closesocket(sockett);
	printf("连接建立成功,关闭socket,准备传输数据\n");
	send(another_socket, recbuf, strlen(recbuf) + 1, 0);  // 发送显示欢迎信息
	int n = 20;
	char recvBuf[11] = "1234567890";
	int zonghe = 0;
	while (n--) {
    
        // 不断等待客户端请求的到来
		char* wel = NULL;
		wel = "Please enter the data:";
		printf("一共传输20次数据,还剩%d次:\n", n + 1);
		int num = recv(another_socket, recvBuf, 6, 0);
		printf("%s : %s\n", sockConnName, recvBuf);     // 接收信息
		printf("当前轮次接收数据量为:%d Byte\n", num);
		zonghe = zonghe + num;
		printf("一共接受数据量为:%d Byte\n", zonghe);
		send(another_socket, wel, strlen(wel) + 1, 0);  // 发送显示欢迎信息
		if (num == SOCKET_ERROR)
		{
    
    
			safeexit();
		}
	}


	printf("数据接收完毕,退出程序");
	shutdown(sockett, SD_SEND);
	safeexit();
}



int main()
{
    
    
	set_editor();
	llisten();
}


演算結果:

Windows上のサーバー
ここに画像の説明を挿入

Linuxでncに接続します。
ここに画像の説明を挿入

Linuxで送信するデータを入力できます。
ここに画像の説明を挿入

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_41874930/article/details/108070886