Очень часто при проектировании и начальной отладке сетевых протоколов, особенно для встраиваемых систем нужна утилита, которая просто принимает входящие сетевые данные и записывает их обратно. Подобных решений мало, а те, что я в своё время смог найти неоправданно громоздки. Так что как и в случае с логом, я решил сам написать максимально простой TCP-эхо сервер на С, который работает как с Windows, так и с Linux. Чем, собственно, и делюсь.

Утилита максимально простая и состоит из одного файла (и еще двух файлов модуля лога). Исходники файлами и скомпилированные бинарные образы для Windows в конце поста.


/** @file main.c */
/** @author Egor 'khaos' Zelensky <i@khaos.su> */
/** @copyright Public domain. */
/** @brief This file contains TCP echo server utility code for both Windows and Linux. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include "log.h"

#ifdef _WIN32
// Link with WinSock2 static library.
#pragma comment(lib, "ws2_32")

/** @brief inet_ntop for WinSock. */
/** @param af	Address family. */
/** @param src	Pointer on source address struct. */
/** @param dst	Pointer on destination string. */
/** @param cnt	Length of the string. */
#define inet_ntop(af, src, dst, cnt)	WSAAddressToString((struct sockaddr*) src, sizeof(struct sockaddr_in), NULL, dst, (LPDWORD)&cnt)
#endif

/** @brief Main entry point. */
int main(int argc, char *argv[]) {
	/// Variable declarations.
	int port_number;
#ifdef _WIN32
	WSADATA wsa;
	WORD version;
#endif
	int sockfd, cli_sockfd;
	int status;
	int n;
	size_t cli_len;
	char buffer[4096];
	char ip_str[25];
	struct sockaddr_in serv_addr;
	struct sockaddr_in cli_addr;
	struct timeval timeout;

	/// Function body.
	log_open("tcpecho", stdout);
	
	if (argc < 2) {
		log_entry(LOG_CRIT, "Port number must be specified.");
		return 1;
	}

	/// Assigning port number.
	port_number = atoi(argv[1]);

	if (port_number <= 0 || port_number > 65535) {
		log_entry(LOG_CRIT, "Specified port value is invalid.");
		return 1;
	}

#ifdef _WIN32
	/// Initializing WinSock2
	version = MAKEWORD(2, 0);
	status = WSAStartup(version, &wsa);

	if (status != 0) {
		log_entry(LOG_CRIT, "Was not able to initialize WinSock.");
		return 1;
	}

	if (LOBYTE(wsa.wVersion) != 2 || HIBYTE(wsa.wVersion) != 0) {
		WSACleanup();
		log_entry(LOG_CRIT, "WinSock version mismatch.");
		return 1;
	}
#endif

	/// Initializing socket.
	sockfd = socket(AF_INET, SOCK_STREAM, NULL);

	if (sockfd < 0) {
#ifdef _WIN32
		WSACleanup();
#endif
		log_entry(LOG_CRIT, "Failed to create a socket.");
		return 1;
	}

	/// Initializing timeouts.
	timeout.tv_sec = 5;
	timeout.tv_usec = 0;

	/// Initializing server address struct.
	memset((char*) &serv_addr, 0, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(port_number);

	log_entry(LOG_INFO, "Listening on port %d", port_number);

	/// Binding server socket to address.
	if (bind(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {
#ifdef _WIN32
		WSACleanup();
		closesocket(sockfd);
#elif __unix__
		close(sockfd);
#endif
		log_entry(LOG_CRIT, "Cannot bind socket.");
		return 1;
	}

	listen(sockfd, SOMAXCONN);
	log_entry(LOG_INFO, "Waiting for new connections...");

	// Needed for accept() not to fail with 10014.
	cli_len = sizeof(cli_addr);

	/// Main receive-send loop.
	while (true) {
		// Zero buffers.
		memset((void*)&cli_addr, 0, sizeof(cli_addr));
		ip_str[0] = '\0';

		if ((cli_sockfd = accept(sockfd, (struct sockaddr*) &cli_addr, &cli_len)) < 0) {
			log_entry(LOG_WARNING, "Failed to accept client socket, dropping.");
			continue;
		}

		// Set socket timeouts.
		// WARNING! It was discovered that some rare embedded clients terminate the connection after those are set.
		//          Comment out the lines below if you encounter such behaviour.
		setsockopt(cli_sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
		setsockopt(cli_sockfd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));

		// Get peer IP address and prepare the buffer.
		memset(&buffer, 0, sizeof(buffer));
		cli_len = sizeof(ip_str);
		//WSAAddressToString(&cli_addr, sizeof(struct sockaddr_in), NULL, &ip_str, &cli_len);
		inet_ntop(AF_INET, &cli_addr, &ip_str, cli_len);

		// Read data and send it back to the client.
		while ((n = recv(cli_sockfd, buffer, sizeof(buffer), 0)) > 0) {
			log_entry(LOG_INFO, "From client %s received %d bytes.", ip_str, n);
			status = send(cli_sockfd, buffer, n, 0);

			// If error, report and break.
			if (status == SOCKET_ERROR) {
				log_entry(LOG_ERR, "Failed to send data back to the client %s.", ip_str);
				break;
			}
		}

		// Cleanup socket.
#ifdef _WIN32
		closesocket(cli_sockfd);
#elif __unix__
		close(cli_sockfd);
#endif
	}

	/// Cleanup.
#ifdef _WIN32
	closesocket(sockfd);
	WSACleanup();
#elif __unix__
	close(sockfd);
#endif
	log_close();
	return 0;
}

Исходный код отдельным файлом, а так же скомпилированная программа для Win32/Win64 доступна по ссылкам ниже.


comments powered by HyperComments