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

# はじめに
どうも。こんにちは。
[42tokyo](https://42tokyo.jp/)といったところで、ソケットプログラミング？？を学んでいます。
前回は、[ノンブロッキングI/Oについて調べてみた。](https://qiita.com/gu-chi/items/1fbc8c05b1ef5a3ac5aa)を書きました。

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

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

間違い等があれば、ご指摘ください。
# コード
:::note warn
注意：このコードには、バグが多分に含まれております。
:::
```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
```
```c++: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);
}
```
```c++: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
```
```c++: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:shell
~$ c++ Server.cpp -o server
~$ c++ Client.cpp -o client
```
サーバーの起動
```shell:shell
~$ ./server
```
別プロセスにて（またはサーバーをバックグラウンドで起動）
```shell:shell
~$ ./client "hello, world"
```
# 最後に
今後は、このエコーサーバーに様々な変更を加えていきたいと思います。
ありがとうございました。

# 参考
[『TCP/IP ソケットプログラミング　C言語編』](https://www.ohmsha.co.jp/book/9784274065194/)

# 次回
[「I/O多重化（システムコールpoll();を使用）」を施したサーバを作成してみた。](https://qiita.com/gu-chi/items/243fa63e17617bb9ef77)