ソケット通信とは
ソケットは通信の出入口のようなものです.TCP/IPなどに代表されるHTTP通信などもソケット通信の一つです.すなわち,ソケットを使用することで異なるマシン間(もしくは同一マシン上)の異なるプロセス間で通信を可能にします.
ソケットプログラミング
簡単なサンプル問題として,次のようなプログラムを作成します.
- ソケットを作成
- 接続を確立
- クライアントが文字列を送信し,サーバが受け取る
- サーバが文字列を送信し,クライアントが受け取る
- 接続を閉じる
サーバプログラム
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char const *argv[])
{
const int port_number = 12345;
// create socket
int sockfd = 0;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
// setup socket
struct sockaddr_in server;
int addrlen = sizeof(server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("0.0.0.0"); // or INADDR_ANY
server.sin_port = htons(port_number);
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
perror("Bind error");
exit(EXIT_FAILURE);
}
// start to listen requests
if (listen(sockfd, SOMAXCONN) < 0)
{
perror("Listen error");
exit(EXIT_FAILURE);
}
// accept request from client
struct sockaddr_in client;
int client_len = sizeof(client);
int sock = 0;
if ((sock = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&client_len)) < 0)
{
perror("Error: accept");
exit(EXIT_FAILURE);
}
// read data from client
char buff[1024] = {0};
recv(sock, buff, sizeof(buff), 0);
printf("recived message is \"%s\"\n", buff);
// send data to client
char *hello = "Hello from server";
send(sock, hello, strlen(hello), 0);
printf("Message sent to client\n");
// close connecting socket
close(sock);
// close listening socket
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
return 0;
}
特定のIPアドレスのみを許可したい場合は,server.sin_addr.s_addr
でListenするIPアドレスを指定します.今回は,すべてのIPアドレス許可しているため,inet_addr("0.0.0.0")
(もしくはINADDR_ANY
)を与えています.
server.sin_port
でポート番号を指定します.今回は,12345
を指定しています.
クライアントプログラム
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char const* argv[])
{
const int port_number = 12345;
int sock = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Socket creation error \n");
exit(EXIT_FAILURE);
}
// setup socket
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(port_number);
// connect to server
if ((connect(sock, (struct sockaddr*)&server, sizeof(server))) < 0)
{
printf("\nConnection Failed \n");
exit(EXIT_FAILURE);
}
// send data to server
char* hello = "Hello from client";
send(sock, hello, strlen(hello), 0);
printf("Message sent to server\n");
// read data from server
char buff[1024] = {0};
recv(sock, buff, sizeof(buff), 0);
printf("recived message is \"%s\"\n", buff);
// close connecting socket
close(sock);
return 0;
}
server.sin_addr.s_addr
でサーバのIPアドレスを指定します.この場合,同一マシンのため127.0.0.1
を与えています.
実行
サーバとクライアントプログラムをそれぞれビルドします.
gcc -o server server.c
gcc -o client client.c
サーバプログラムを実行し,待ち受け状態にします.
./server
サーバが立ち上がったことでポート12345が待ち受け状態になっているはずなので,下記コマンドで確認します.
netstat -tlnw
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN
raw6 0 0 :::58
別マシンの場合,サーバとの疎通確認は下記の方法でも行うことができます.
クライアントプログラムを実行します.
./client
Message sent to server
recived message is "Hello from server"
サーバプログラムでは下記のような出力が得られます.
recived message is "Hello from client"
Message sent to client
プログラム例
入力した数値が素数判定するプログラム
ここでは,サンプルプログラムを少し変更して,クライアントから与えられた数値が素数かどうかの判定を行い,その結果を返すサーバを作ります.コードは,C++で記述します.
server.cpp
#include <unistd.h>
#include <cmath>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
bool ToValue(unsigned long &val, const std::string &str)
{
try
{
val = std::stoul(str);
return true;
}
catch (...)
{
return false;
}
}
bool IsPrime(const unsigned long &val)
{
if (val < 2)
return false;
if (val == 2)
return true;
if (val % 2 == 0)
return false;
auto sq = static_cast<unsigned long>(std::sqrt(val));
for (unsigned long i = 3; i <= sq; i += 2)
{
if (val % i == 0)
return false;
}
return true;
}
int main(int argc, char const *argv[])
{
const int port_number = 12345;
// create socket
int sockfd = 0;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
// setup socket
struct sockaddr_in server;
int addrlen = sizeof(server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("0.0.0.0");
server.sin_port = htons(port_number);
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
perror("Bind error");
exit(EXIT_FAILURE);
}
// start to listen requests
if (listen(sockfd, SOMAXCONN) < 0)
{
perror("Listen error");
exit(EXIT_FAILURE);
}
// accept request from client
struct sockaddr_in client;
int client_len = sizeof(client);
int sock = 0;
if ((sock = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&client_len)) < 0)
{
perror("Error: accept");
exit(EXIT_FAILURE);
}
// read data from client
char buff[1024] = {0};
recv(sock, buff, sizeof(buff), 0);
const auto str = std::string(buff);
unsigned long val = 0;
std::string msg = "";
if (ToValue(val, str))
{
if (IsPrime(val))
{
msg = "Prime Number";
}
else
{
msg = "Not Prime Number";
}
}
else
{
msg = "Please send value!";
}
auto cmsg = msg.c_str();
send(sock, cmsg, 1024, 0);
close(sock);
// close listening socket
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
return 0;
}
ToValue
関数は下記記事で詳細を説明しています.
client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char const* argv[])
{
const int port_number = 12345;
int sock = 0, client_fd;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Socket creation error \n");
exit(EXIT_FAILURE);
}
// setup socket
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(port_number);
// connect to server
if ((client_fd = connect(sock, (struct sockaddr*)&server, sizeof(server))) < 0)
{
printf("\nConnection Failed \n");
exit(EXIT_FAILURE);
}
std::string str;
std::cin >> str;
// send data to server
auto cstr = str.c_str();
send(sock, cstr, 1024, 0);
// read data from server
char buff[1024] = {0};
recv(sock, buff, sizeof(buff), 0);
printf("recived message is \"%s\"\n", buff);
// close connecting socket
close(client_fd);
return 0;
}
まとめ
この記事では,ソケットプログラミングのサンプルを紹介しました.また,少し応用して素数判定するプログラムを作りました.
このプログラムでは,暗号化を行っていないため,運用時にはSSL/TLSなどの暗号化をすべきでしょう.
暗号化したソケット通信に関しては下記の記事にまとめましたので,興味があればご覧ください.