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