0
1

簡単なエコーサーバーを作成してみた。

Last updated at Posted at 2024-01-12

はじめに

どうも。こんにちは。
42tokyoといったところで、ソケットプログラミング??を学んでいます。
前回は、ノンブロッキングI/Oについて調べてみた。を書きました。

今回は、簡単なエコーサーバーを作成してみます。
使用した言語は、C++98です。

書籍『TCP/IP ソケットプログラミング C言語編』を参考にしました。
この記事では、C++に換装したコードのみを提示しております。
詳しい解説は、書籍を読んでください。

間違い等があれば、ご指摘ください。

コード

注意:このコードには、バグが多分に含まれております。

Server.hpp
#ifndef SERVER_HPP
# define SERVER_HPP

#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>

# define RECV_BUF_SIZE 32

class Server {
 private:
	 int		socketFd_;
	 const int	maxPending_;

 public:
	 explicit Server(unsigned short port);
	 ~Server();

	 void startServer();
};

#endif  // SERVER_HPP
Server.cpp
#include "./Server.hpp"

static void fatalError(const std::string message) {
    perror(message.c_str());
    exit(EXIT_FAILURE);
}

Server::Server(unsigned short port) :
	socketFd_(0), maxPending_(5) {
	this->socketFd_ = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (this->socketFd_ < 0) {
		fatalError("socket()");
	}

	struct sockaddr_in	echoServAddr;
	memset(&echoServAddr, 0, sizeof(echoServAddr));
	echoServAddr.sin_family = AF_INET;
	echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	echoServAddr.sin_port = htons(port);

	if (bind(this->socketFd_, reinterpret_cast<struct sockaddr *>(&echoServAddr), sizeof(echoServAddr)) < 0) {
		fatalError("bind()");
	}
	if (listen(this->socketFd_, this->maxPending_) < 0) {
		fatalError("listen()");
	}
}

Server::~Server() {
	close(this->socketFd_);
}

static void	handleClient(int clientSocket) {
	while (1) {
		char	buf[RECV_BUF_SIZE] = {0};
		int		recvMsgSize = 0;

		if ((recvMsgSize = recv(clientSocket, buf, RECV_BUF_SIZE, 0)) < 0) {
			fatalError("recv()");
		}
		if (recvMsgSize == 0) {
			break;
		}
		if (send(clientSocket, buf, recvMsgSize, 0) != recvMsgSize) {
			fatalError("send()");
		}
	}
	close(clientSocket);
}

void	Server::startServer() {
	while (1) {
		struct sockaddr_in	clientAddr;
		unsigned int		clientLen(sizeof(clientAddr));
		int					clientSocket(0);

		memset(&clientAddr, 0, sizeof(clientAddr));
		// block until receive client request
		clientSocket = accept(this->socketFd_, reinterpret_cast<struct sockaddr *>(&clientAddr), &clientLen);
		if (clientSocket < 0) {
			fatalError("accept()");
		}
		std::cout << "Handling client " << inet_ntoa(clientAddr.sin_addr) << std::endl;
		handleClient(clientSocket);
	}
}

int main() {
	Server	server(8080);
	server.startServer();

	return (0);
}
Client.hpp
#ifndef CLIENT_HPP
# define CLIENT_HPP

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>

#define RCVBUFSIZE 32

class Client {
 private:
	 int					socket_;
	 struct sockaddr_in		serverAddr_;

 public:
	 Client(const std::string& serverIP, unsigned short serverPort);
	 ~Client();

	 void	connectToServer();
	 void	sendMessage(const std::string& message);
	 void	receiveMessage();
};

#endif  // CLIENT_HPP
Client.cpp
#include "./Client.hpp"

static void fatalError(const std::string message) {
    perror(message.c_str());
    exit(EXIT_FAILURE);
}

Client::Client(const std::string& serverIP, unsigned short serverPort) : socket_(0) {
	this->socket_ = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (this->socket_ < 0) {
		fatalError("socket()");
	}
	memset(&this->serverAddr_, 0, sizeof(this->serverAddr_));
	this->serverAddr_.sin_family = AF_INET;
	this->serverAddr_.sin_addr.s_addr = inet_addr(serverIP.c_str());
	this->serverAddr_.sin_port = htons(serverPort);
}

Client::~Client() {
	close(this->socket_);
}

void Client::connectToServer() {
	if (connect(this->socket_, reinterpret_cast<struct sockaddr *>(&this->serverAddr_), sizeof(this->serverAddr_)) < 0) {
		fatalError("connect()");
	}
}

void Client::sendMessage(const std::string& message) {
	if (send(this->socket_, message.c_str(), message.size(), 0) != static_cast<ssize_t>(message.size())) {
		fatalError("send()");
	}
}

void Client::receiveMessage() {
	char	echoBuffer[RCVBUFSIZE] = {0};
	int		bytesRecved = 0;

	std::cout << "Received: ";
	while ((bytesRecved = recv(this->socket_, echoBuffer, RCVBUFSIZE - 1, 0)) > 0) {
		echoBuffer[bytesRecved] = '\0';
		std::cout << echoBuffer << std::flush;
		memset(echoBuffer, 0, sizeof(echoBuffer));
		bytesRecved = 0;
    }

	if (bytesRecved < 0) {
		fatalError("recv()");
	}
	std::cout << std::endl;
}

int main(int argc, char *argv[]) {
	if (argc != 2) {
		std::cerr << "Usage: " << argv[0] << " <Echo Word>\n";
		exit(1);
	}

	const std::string	message = argv[1];

	Client	client("127.0.0.1", 8080);

	client.connectToServer();
	client.sendMessage(message);
	client.receiveMessage();

	return (0);
}

挙動の確認

実行ファイル(server, client)の作成

shell
~$ c++ Server.cpp -o server
~$ c++ Client.cpp -o client

サーバーの起動

shell
~$ ./server

別プロセスにて(またはサーバーをバックグラウンドで起動)

shell
~$ ./client "hello, world"

最後に

今後は、このエコーサーバーに様々な変更を加えていきたいと思います。
ありがとうございました。

参考

『TCP/IP ソケットプログラミング C言語編』

次回

「I/O多重化(システムコールpoll();を使用)」を施したサーバを作成してみた。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1