Principle and step-by-step implementation of socket programming under windows

Preface

I wrote socket programming a few years ago, but I didn’t use it for a long time and I forgot about this technology. Recently, I was studying the execution principle of msf payload, and I had to use socket programming technology, so this article was created.

This article has nothing to do with the technology in msf. It is a basic article. After reading this article, continue to read the principle of msf payload and you will feel a sense of confusion.


Features of socket programming under windows

Compared with socket programming based on python, socket programming under windows is very complicated. Multiple steps are required, and multiple data structures need to be set up.
Python only needs two lines:
Insert picture description here
C language requires many, many, many lines:
Insert picture description here
but here I still hope that everyone can know how to write sockets in C, because it is easier to understand what a socket is.


Code (server)

Set the version number of the windows Sock specification

The first thing you need to program a socket under windows is to set the version of the socket, which can be understood as which version of the protocol the two parties want to use for the socket connection to communicate. This is the first step when creating a socket.

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");
	}
}

Define the relevant attributes of the socket (ip, protocol, etc.)

My understanding is that to create a socket needs to be divided into two steps, first use the getaddrinfo function to define the specific attributes of the socket, such as support for ip version, tcp connection or udp connection, socktype, etc. Then use the socket function to create a complete socket. The specific core code is as follows:

	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。
	}

Bind port and ip

The binding port and ip usage are the bind functions. Binding can be understood as binding the socket we created to a certain port. At this time, other hosts can access this port and can access our port, which is equivalent to applying to us Establish a socket connection.
Because we have defined the socket information through getaddrinfo in the first step, just use it directly at this time.

    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使用的内存

As for how the parameters in bind come from, you can check the official Microsoft documentation. I only provide a direct answer here.

Appendix:
getaddrinfo function structure
addrinfo structure
bind function structure
sockaddr structure


Listening port

After binding the information, it is time to open the port to listen:

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

The whole is relatively simple and nothing special.


Waiting for connection

When a host wants to connect to us, we need to call the accept function to accept the connection request. The specific code is as follows:

    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");

Appendix: accept function structure

Let me talk about it. The meaning of the two parameters after accept is the same as the meaning of the three parameters of bind. It's just that we use NULL here to impose restrictions on the host that we want to link to. If you want to restrict it, you can Defining an addrinfo structure object can restrict the host with specified conditions to connect to this socket.

Remember: after using the accept function, a new socket connection will be generated, and the socket created before can be discarded using the closesocket function.

send data

After all the above steps are completed, the connection has been established. At this time, you can choose to send data. The specific code is as follows:

	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();

It is worth mentioning here that the two functions send and recv, the parameters of these two functions are the same, they are the descriptor of the established socket connection, the buffer that stores the data, the length of the sent or received data, and the last one For the specific usage of the flag bit, see this article . Generally, it is better to use 0.


The complete server code is given below. When using it, you can use the nc command to directly connect to the corresponding port number:

#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();
}


operation result:

server on windows
Insert picture description here

Connect with nc on linux:
Insert picture description here

You can enter data for transmission on linux:
Insert picture description here

Insert picture description here

Guess you like

Origin blog.csdn.net/qq_41874930/article/details/108070886