こんにちは。今回はソケットで通信する方法について解説します。
ソケットとは?
ソケットとは、異なるプロセス(同一端末上にあるとは限らない)間での通信を実現するものです。
主に以下の2種類があります。
-
TCP/UDPソケット -
UNIX Domainソケット
上記はIPアドレス等を通じたネットワークを通した通信、下記はファイルを用いた同一ファイルシステム上の通信になっています。
今回は汎用性の高いTCP/UDPソケットの例を示します。
Server側
まずはImport
#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {}
int main() {}の中身
signal handlerを設定
// SIGPIPEで死なないようにする
signal(SIGPIPE, SIG_IGN);
ソケットを立ち上げる
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
意味
-
AF_INET
→IPv4を使う -
SOCK_STREAM
→ ストリーム型(= TCP) -
0
→ プロトコルを自動選択(TCP が選ばれる)
server_fdにはファイルディスクリプタが代入される。
setsockoptは、ソケットの動作をカスタマイズする関数
| 引数 | 意味 |
|---|---|
server_fd |
設定対象のソケット |
SOL_SOCKET |
ソケット自体の設定 |
SO_REUSEADDR |
アドレス(IP+ポート)の再利用を許可 |
&opt |
有効化(1 = true) |
sizeof(opt) |
サイズ |
アドレスを設定する、バインドする、リッスンする
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (sockaddr *)&addr, sizeof(addr));
listen(server_fd, 5);
addr.sin_addr.s_addr = INADDR_ANY;はすべてのIPからのリクエストを受け付ける(0.0.0.0にバインドする)
listenの第二引数は、Queに入れる通信街の数の上限を定める(Handshake済み、Accept待ちの行列)
理論的な上限はSOMAXCONNに定義される
とりあえずAcceptする
int client_fd = accept(server_fd, nullptr, nullptr);
client_fdには新たなファイルディスクリプタが代入されます。
通信する
char buf[1024];
int n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
write(client_fd, buf, n); // エコー
}
nには飛んできたバイト数が代入される。
最後にはしっかりクローズ
close(client_fd);
close(server_fd);
Client側
まずは諸々宣言
#include <arpa/inet.h> // inet_pton
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {}
int main(){}の中身
まずは設定
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 接続先情報を用意
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(8080); // サーバのポート
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
inet_ptonは通信用のIPをソケット用のバイナリに変換する。成功したら1を返す関数。
接続して書いて閉じる
// サーバに接続
connect(sock, (sockaddr *)&server, sizeof(server));
// 送信
write(sock, "hi", 2);
// 受信
char buf[1024];
read(sock, buf, sizeof(buf));
// 終了
close(sock);
おまけ
コード全体
#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
// SIGPIPEで死なないようにする
signal(SIGPIPE, SIG_IGN);
// 通信口を作る
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// ポート再利用を許可
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// アドレス設定
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
// ポートを開放
bind(server_fd, (sockaddr *)&addr, sizeof(addr));
// 接続待ち
listen(server_fd, 5);
// クライアント接続を待つ
int client_fd = accept(server_fd, nullptr, nullptr);
// 通信
char buf[1024];
int n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
write(client_fd, buf, n); // エコー
}
// 終了処理
close(client_fd);
close(server_fd);
}
#include <arpa/inet.h> // inet_pton
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
// 通信口を作る
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 接続先情報を用意
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(8080); // サーバのポート
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
// サーバに接続
connect(sock, (sockaddr *)&server, sizeof(server));
// 送信
write(sock, "hi", 2);
// 受信
char buf[1024];
read(sock, buf, sizeof(buf));
// 終了
close(sock);
}
ファイルを使用してネットワークより早い通信をする場合(同一ファイルシステム上のプロセスなら可能)
使われるファイルのパスは/tmp/echo.sock
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
int main() {
// SIGPIPEで死なないようにする
signal(SIGPIPE, SIG_IGN);
const char* SOCKET_PATH = "/tmp/echo.sock";
// 通信口を作る
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
// アドレス設定
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
std::strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 既存ソケットファイルがあれば削除
unlink(SOCKET_PATH);
// バインド
bind(server_fd, (sockaddr*)&addr, sizeof(addr));
// 接続待ち
listen(server_fd, 5);
// クライアント接続を待つ
int client_fd = accept(server_fd, nullptr, nullptr);
// 通信
char buf[1024];
int n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
write(client_fd, buf, n); // エコー
}
// 終了処理
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
}
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
int main() {
const char* SOCKET_PATH = "/tmp/echo.sock";
// 通信口を作る
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// 接続先情報を用意
sockaddr_un server{};
server.sun_family = AF_UNIX;
std::strncpy(server.sun_path, SOCKET_PATH, sizeof(server.sun_path) - 1);
// サーバに接続
connect(sock, (sockaddr*)&server, sizeof(server));
// 送信
write(sock, "hi", 2);
// 受信
char buf[1024];
read(sock, buf, sizeof(buf));
// 終了
close(sock);
}
Server側が受け付けたリクエストをForkして処理する場合
作成されているものがファイルディスクリプタであることに注意して、適切にcloseする必要がある。
#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
int main() {
// SIGPIPEで死なないようにする
signal(SIGPIPE, SIG_IGN);
// ゾンビプロセス対策(簡易)
signal(SIGCHLD, SIG_IGN);
// 通信口を作る
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// ポート再利用を許可
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// アドレス設定
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
// ポートを開放
bind(server_fd, (sockaddr *)&addr, sizeof(addr));
// 接続待ち
listen(server_fd, 5);
while (true) {
// クライアント接続を待つ
int client_fd = accept(server_fd, nullptr, nullptr);
if (client_fd < 0) {
continue;
}
pid_t pid = fork();
if (pid == 0) {
// ===== 子プロセス =====
close(server_fd); // 子では不要
char buf[1024];
int n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
write(client_fd, buf, n); // エコー
}
close(client_fd);
_exit(0); // 子プロセス終了
} else if (pid > 0) {
// ===== 親プロセス =====
close(client_fd); // 親では不要
} else {
// fork失敗
close(client_fd);
}
}
close(server_fd);
}