ネットワークプログラミングにおける最も基本的な要素の1つに、ソケット通信があります。
この記事では、C言語を用いてソケット通信のサーバー側プログラムを作成する方法を詳しく説明します。
これからソケット通信のプログラムを作成する方、あるいは既にソケット通信についての基礎的な知識を持っている方でも、具体的なコードを通じて理解を深めることができるでしょう。
ソケット通信とは
ソケット通信は、ネットワークを介してコンピュータ間でデータを送受信するための一般的な方法です。ソケットは、ネットワーク上の2つのエンドポイント間の通信リンクを抽象化したもので、通常はIPアドレスとポート番号の組み合わせで識別されます。
ソケット通信の一般的なフローは以下のようになります:
- ソケットの作成:ソケット通信を始めるには、まずソケットを作成する必要があります。ソケットは、特定の通信プロトコル(TCP、UDPなど)に基づいて作成されます。
- ソケットのバインド:サーバ側では、ソケットを特定のIPアドレスとポート番号にバインド(関連付け)します。これにより、そのソケットは指定したIPアドレスとポート番号で通信を待つようになります。
- 接続の待機と受け入れ:サーバ側のソケットは、クライアントからの接続を待つ状態になります。クライアントから接続要求があった場合、サーバはその接続を受け入れ、クライアントと通信するための新しいソケットを生成します。
- データの送受信:一旦接続が確立すると、クライアントとサーバはデータを送受信することができます。データはバイト列として送受信され、受信側では必要に応じて適切な形式(文字列、数値、構造体など)に変換します。
- ソケットのクローズ:通信が終了したら、ソケットは閉じられます。これにより、ソケット通信に使用されていたリソースが解放されます。
この基本的なフローは、ほとんどのソケット通信のシナリオに適用できます。しかし、実際のソケット通信では、非同期通信、エラーハンドリング、タイムアウトの設定など、より高度なテクニックが必要になる場合もあります。
ハードウェア構成
今回使用するハードウェア構成は以下の通りです。
サーバー側
サーバー側のハードウェアはマイコンボードRaspberry Pi 3を使用します。
OSはRaspberry Pi OS(Linux)となります。
C言語のコンパイル、実行方法については、以下の記事で解説しています。
クライアント側
クライアント側はIntel製 x86 CPUを搭載したノートPCです。
OSはWindowsとなります。
C#でのクライアント側プログラムの実装方法については、以下の記事で解説しています。
作成したCソースコード
今回はRaspberry Pi上で動作させるCのプログラムを作成します。
処理説明
- プログラムはまず、
socket()
関数を用いて新しいソケットを作成します。これにより、ソケット通信のためのリソースが確保されます。 - 次に、サーバーのアドレス情報を設定します。これは、IPアドレスとポート番号を含みます。このプログラムでは、任意のIPアドレスからの接続を受け入れるため、IPアドレスには
INADDR_ANY
を指定しています。ポート番号は8080に設定しています。 bind()
関数を用いて、設定したアドレス情報を先ほど作成したソケットに関連付けます。これにより、そのソケットが指定したIPアドレスとポート番号で通信を待ち受けるようになります。listen()
関数によって、ソケットが受信接続を待機する状態になります。ここでの引数10は、未処理の接続の最大数を示しています。accept()
関数を用いてクライアントからの接続要求を受け入れます。この関数は、新しい接続が確立するまでブロック(プログラムの進行を停止)します。接続が確立したら、その接続用の新しいソケットディスクリプタを返します。recv()
関数を使用して、クライアントからのメッセージを受信します。メッセージはバッファに保存され、その内容が画面に表示されます。これをクライアントからの”quit”メッセージが受信されるまで続けます。- 最後に、
close()
関数を用いてクライアントソケットとサーバーソケットを閉じ、ソケット通信に使用されていたリソースを解放します。
作成した全体のソースコード
全体のソースコードは以下の通りです。
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// 新しいソケットを作成
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// サーバーアドレスを設定
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// ソケットをサーバーアドレスにバインド
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(1);
}
// 受信接続を待機
if (listen(server_fd, 10) == -1) {
perror("listen");
exit(1);
}
printf("Waiting for connection...\n");
// 受信接続を受け入れる
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("accept");
exit(1);
}
printf("Connected.\n");
// クライアントからのメッセージを受信
while (1) {
memset(buffer, 0, BUFFER_SIZE);
if (recv(client_fd, buffer, BUFFER_SIZE, 0) == -1) {
perror("recv");
exit(1);
}
// メッセージが "quit" の場合、終了
if (strcmp(buffer, "quit") == 0) {
break;
}
printf("Received message: %s\n", buffer);
}
// クライアントとサーバーのソケットを閉じる
close(client_fd);
close(server_fd);
return 0;
}
まとめ
以上で、C言語を用いたソケット通信のサーバー側プログラムについての説明を終わります。
今回はソケットの作成からバインド、接続の待機と受け入れ、データの送受信、そしてソケットのクローズまで、基本的なソケット通信のフローを一通り学びました。
この知識を基に、さまざまなネットワークアプリケーションの開発にチャレンジしてみてください。
対応するクライアント側のプログラムをC#で作成する方法については以下の記事で解説していますので、あわせてご覧ください。
それでは、また次の記事でお会いしましょう。。
コメント