はじめに
どうも。こんにちは。
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"
最後に
今後は、このエコーサーバーに様々な変更を加えていきたいと思います。
ありがとうございました。
