このページではC言語でのソケット通信の仕方について解説していきたいと思います!
今や「通信」は生活になくてはならないものになりましたよね!
ウェブページやスマホのゲーム、SNS なども「通信」があってこそ成り立つ物ばかりです。
このページでは、その通信をC言語で行うための「ソケット通信」について解説していきたいと思います!
このページはC言語向けの解説になりますが、ソケット通信は他の多くのプログラミング言語でも用意されています。
ソケット通信をする際の処理の流れはどの言語でも同じになりますし、使用する関数(メソッド)もほとんど同じですので、他のプログラミング言語を利用されている方でも役に立つ内容になっていると思います!
また、紹介する関数は MacOSX のものを紹介していますが、おそらく Linux でも同じものが使用できると思います。
Windows の場合はちょっと関数名が違ったり、インクルードするファイルが違ったりするので注意が必要ですが、使い方や関数の動作はほぼ同じだと思いますので参考にはなると思います!
Contents
ソケット通信とは
ソケット通信とは、ソケットを用いた通信のことです。
で、ソケットっていうのは通信端点のことで、アプリとアプリ(プログラムとプログラム)の通信の出入り口のことです。
ソケット通信を行う際には、まずアプリやプログラム内でソケットを作成し、それを他のアプリやプログラムのソケットに接続してデータのやり取りを行います。
ですので、C言語プログラムでソケット通信を行うためにも、まずこのソケットを作成する必要があります。
そして、このソケットを使用して通信を行うようにプログラミングしていくことになります。
C言語ではこのソケットを、後述する socket
関数により作成することができます。
C言語では、このソケットを利用して接続相手に接続したり、データの送受信を行なったりする関数もたくさん用意されています。
サーバーとクライアント
今回作成するプログラムもサーバーとクライアント間でデータのやり取りを行うものなので、ここで「サーバーとクライアント」について説明しておきます。
サーバーとクライアントはそれぞれ下記のようなものになります。
- サーバー:クライアントにサービスを提供するソフトウェアやコンピュータ
- クライアント:サーバーからサービスを受けるソフトウェアやコンピュータ
このサーバーとクライアントで通信を行うことでさまざまなサービスや機能が実現されています。
例えばウェブブラウザでのウェブページの閲覧を考えると分かりやすいです。
この場合、ウェブページを閲覧しようとしている PC やスマホなどがクライアントで、ウェブサーバーがサーバーとなります。
クライアントはウェブサーバーに対して、ウェブページを表示するのに必要なデータをサーバーに要求します。
より具体的には、ページの表示を要求するためのデータを送信します。
サーバーはそのデータを受信し、その応答としてクライアントに対してそのページを表示するのに必要なデータを送信します。
そして、クライアントはそのデータを受信し、受信したデータ用いてウェブブラウザ上にウェブページを表示します。
こんな感じでクライアントがサーバーに対してサービスを要求し、サーバーがその応答としてサービスを提供するのが「サーバーとクライアント」間の通信の流れになります。
で、この場合は「サービス」がウェブページの表示(のためのデータの供給)になります。
スポンサーリンク
ソケット通信の簡単な流れ
次は、先ほど説明したような通信を行うための処理の流れを簡単に説明をしていきたいと思います。
ここからは、TCP での通信を行うことを前提にソケット通信について解説していきます。ちなみに、TCP と対をなす存在の UDP については下記ページで解説していますので、UDP 通信について学びたい方は、このページを読み終わった後にでも下記ページを読んでみていただければと思います。
【C言語】UDP通信を行うサーバーとクライアントとでは大きく分けると下記の3つの手順で通信が実現されます。
- サーバーとクライアント間で接続を確立する
- サーバーとクライアント間でデータのやり取りをする
- サーバーとクライアント間の接続を閉じる
通常、通信を行う目的は 2. になると思います。
先程のウェブページ閲覧の例であれば、通信の目的はウェブページを閲覧するための「データのやり取り」になりますよね。
で、その 2. を行うために 1. と 3. を行う必要があります。これらの処理の詳細について解説していきたいと思います。
接続を確立する
データをサーバーとクライアント間で行うためには、まずこの2つの間で接続を行う必要があります。
ソケットを作成する
まず通信を行うためにソケットを作成します。ソケット通信を行うためには、通信の出入り口になるソケットが必要です。
そして、サーバーとクライアント両方にソケットが必要なので、サーバークライアント両方でソケットを作成する必要があります。
このソケットの作成は socket
関数により行うことができます。
サーバーが接続を待つ
サーバーが接続を確立するときに次に行うのが「接続待ち」です。
要はクライアントから接続要求がくるまで待機している感じです。
この接続待ちは listen
関数により行うことができます。
クライアントが接続を要求する
クライアントはサーバーに対して「接続要求」を行います。これによりサーバーが接続要求を受け取ります。
この接続要求は connect
関数により行うことができます。
サーバーが接続を受け付ける
そして、サーバーは受け取った接続要求を受け付けます。
この接続の受け付けは accept
関数により行うことができます。
実際にここでサーバークライアント間で接続が確立されたことになります。
ソケットとソケットの間が線で結ばれるイメージです。
データのやり取りを行う
ソケット同士の接続が確立されたら、次はそのソケットを用いて通信の目的となるデータのやり取りを行います。
データのやり取りとは、具体的には次の2つになります。
- データの送信(
send
関数) - データの受信(
recv
関数)
要は、サーバーもしくはクライアントの一方がデータを送信し、他方がその送信されたデータを受信するという流れですね。
で、これを必要なデータを送信 or 受信するまで繰り返します。
途中で送信する側と受信する側が入れ替わることもあります。
で、ここでのポイントは「サーバーとクライアントで息を合わせる必要がある」という点になります。
要はサーバーとクライアントとで受信と送信のタイミングを合わせる必要があります。
例えば接続確立後にサーバーとクライアント双方でデータの受信を行おうとしても、データが送信されて来ないのでデータのやり取りが上手く行きません。
そういうことがないように、通信によっては例えば下記のようなことが定められている場合があります。
- サーバーとクライアントのどちらが
- どのタイミングで
- どんなデータを
- 送信 or 受信するか
このようなデータのやり取りの仕方は「プロトコル」によって定められています。
例えばウェブページを要求するときには HTTP というプロトコルに従ってデータのやり取りを行います(HTTP の P はプロトコルの頭文字です)。
ですので、例えばウェブページを要求するクライアントプログラムを作成する場合は、このプロトコルに従ってデータの送受信しないとウェブサーバーと話が合わなくて上手くデータのやり取りが行えません。
が、今回は単にサーバーとクライアントの通信のサンプルプログラムを作るだけですので、プロトコルは気にせずにプログラミングを行っていきたいと思います(ただしサーバーとクライアントで息を合わせる必要はあります)。
でも、例えばウェブサイトごとに好き勝手データの通信をやってしまうと、ウェブブラウザをサイトごとに適用させなきゃいけないから大変だよね
このプロトコルがあるから、どのウェブサイトも同じデータのやり取りでページ表示ができるんだよ
スポンサーリンク
接続を閉じる
一通りデータのやり取りが終われば、最後に接続を閉じます。これによりサーバーとクライアント間の接続が解消されます。
接続を閉じる処理は close
関数により行うことができます。
一連の流れを簡単に説明するとこんな感じです。
ソケット通信で利用する関数
では、ここまで解説してきた通信の流れを実現するために必要なソケット通信で利用する関数について解説しておきたいと思います。
ソケットを作成する:socket
ソケット通信を行うためにはソケットが必要で、このソケットを作成するのが socket
関数になります。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket
関数の戻り値は int
型のデータになりますが、これが作成した「ソケットの識別子」となり、後に説明する関数ではこのソケットの識別子を指定して通信を行うことになります。
socket
関数の引数は下記の3つになります。
domain
:プロトコルファミリー(アドレスファミリー)を指定type
:ソケットのタイプを指定protocol
:使用するプロトコルを指定
正直私もこの引数については詳しくないです…。
今回は下記の引数設定で socket
関数を実行します。要は IPv4 で TCP プロトコルを使用する設定になります。
sock = socket(AF_INET, SOCK_STREAM, 0);
スポンサーリンク
ソケットを関連付ける:bind
特にサーバー側で、ソケット作成後に行うのが bind
です。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind
はソケットに IP アドレスやポート番号を設定する関数です。
なので、引数には設定先のソケットとソケットに設定する情報(さらにはその情報のサイズ)を指定します。
sockfd
:ソケットaddr
:ソケットに割り当てるアドレスやポート番号の情報addrlen
:addr
のサイズ(バイト数)
サーバー側では、どの IP アドレス&どのポート番号で接続を待つかを設定することになります。
例えばパソコンはいくつも IP アドレスを持っていたりするので、その中のどの IP アドレスに対しての接続を受け付けるかをソケットに設定する必要があります。
ポート番号も同様ですね。どのポート番号に対する接続を受け付けるかを設定する必要があります。
で、この設定を行うのが bind
になります。
接続を待つ:listen
bind
後にサーバーが行うのがこの listen
です。
listen
を実行することで、ソケットが接続待ち状態になります。
そして、このソケットが接続待ち状態になることで、次に説明するクライアントからの connect
による接続要求が可能になります。
listen
関数の定義は下記のようになります。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
listen
関数の引数は下記のようになります。
sockfd
:接続を待つソケットbacklog
:接続要求を保持する数
backlog
については accept
の説明で詳細を説明します。
接続を要求する:connect
この connect
はソケット作成後にクライアントが実行する関数です。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
引数は下記のようになります。
sockfd
:接続を行うソケットaddr
:接続先(アドレスやポート番号など)の情報addrlen
:addr
のサイズ(バイト数)
connect
関数では「どこに接続するか」の情報を指定する必要があり、それを第2引数 addr
に設定する必要があります。
より具体的には、接続先はサーバーなので、その接続先のサーバーの IP アドレスやポート番号を addr
で指定します。
スポンサーリンク
接続を受け付ける:accept
で、サーバーが listen
で接続待ち状態になり、さらにクライアントが connect
を実行することで、サーバーに接続要求が送られてくることになります。
次にサーバーが行うのが接続の受け付けです。この接続の受け付けを行うのが accept
関数です。
listen
では接続待ち状態になるだけなので、クライアントから connect
が来ても、単純に接続要求がサーバーに溜まるだけです。
で、その接続要求を受け付けるのが accept
関数です。これを実行することで接続が確立され、その後のデータのやり取りを行うことができるようになります。
listen
と accept
の関係はキューで考えるとわかりやすいです。
listen
は接続要求を溜めておくためのキューを作成する関数です。
で、connect
によりクライアントから来た接続要求はこのキューに溜められていきます。
さらに accept
ではそのキューからその接続要求を取得し、その接続要求を受け付け、実際のデータのやり取りを行います。
サーバーは複数のクライアントを相手にすることも多いので、接続要求が来たときにすぐにデータのやり取りを行ったりすると処理が追いつかないような場合があり得ます。
ですが、listen
でキューを作成することで接続要求を溜めておくことができるようになります。
ですので、サーバーは接続要求が来たタイミングではなく、サーバー自身のタイミングが良いとき(例えば他のクライアントとデータのやり取りしていないとき)に接続を受け付け、実際のデータのやり取りを行うようなことが可能になります。
で、このキューのサイズは listen
の第2引数で指定する backlog
で指定した値となります。
要は、accept
していない接続要求を最大いくつまで溜めておくかを指定するのが backlog
になります。
この accept
関数の定義は下記のようになります。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
引数は下記のようになります。
sockfd
:接続待ちを行なっているソケットaddr
:接続先の情報へのポインタaddrlen
:addr
のサイズ(バイト数)へのポインタ
accept
関数のポイントは2つです。
1つ目は「接続要求がくるまでは関数が終了しない」ことです。要はクライアントから connect
が実行されないと関数が終了せず、関数の中でプログラムが待たされることになります。
2つ目は accept
関数はソケットを作成し「戻り値がその作成したソケットの識別子」であることです。
この作成されたソケットが接続要求を受け付けたクライアントと「接続済のソケット」になります。
一方で、accept
関数の第1引数 sockfd
で指定したソケットは、まだ listen
で接続待ちの状態になっているソケットです。
ですので、接続済のクライアントとデータのやり取りを行う際には、accept
関数の戻り値(つまり接続済のソケット)を用いてデータのやり取りを行うことになります。
データを送信する:send
ここまででサーバーとクライアントとの接続が確立された(つまりソケットが接続済になった)ので、続いて通信の目的でもあるデータのやり取りを行う関数を紹介していきます。
まず紹介するのが send
関数です。send
関数は、接続先に対してデータを送信する関数です。
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send
関数の引数は下記のようになります。
sockfd
:接続済のソケットbuf
:送信するデータへのポインタlen
:送信するデータのサイズ(バイト数)flags
:送信時の動作の詳細設定
sockfd
は接続済のソケットで、このソケットに送信先の情報(IP アドレスやポート番号など)が設定されています。
接続待ち状態のソケットなど、接続済でないソケットを指定するとエラーになるので注意してください。
send
関数の戻り値は、実際に接続先に送信したデータのバイト数になります。
データを受信する:recv
次に紹介するのが recv
関数です。recv
関数は、接続先が送信したデータを受信する関数です。
#include <sys/socket.h>
ssize_t recv(int sockfd, const void *buf, size_t len, int flags);
recv
関数の引数は下記のようになります。
sockfd
:接続済のソケットbuf
:受信データを格納するバッファのアドレスlen
:buf
のサイズ(バイト数)flags
:受信時の動作の詳細設定
recv
関数の戻り値は、実際に接続先から受信したデータのバイト数になります。
recv
関数はデータが到着するまで待ち続ける関数になります。
クライアントサーバー間では、一方が send
関数で送信したデータを、もう一方が recv
関数でデータを受信することを繰り返すことで、データのやり取りを行います。
このときに重要なのは前述の通り「サーバークライアント間で息を合わせる」ことです。
どちらがどのタイミングで send
関数 or recv
関数を実行するかを上手く設計しないと、お見合い状態になるようなこともあるので注意してください。
例えば両方が recv
関数でデータ受信待ちになるとデータのやり取りがそこで止まってしまいます。
スポンサーリンク
接続を閉じる:close
データのやり取りが一通り終わった際には、最後に close
関数で接続を閉じます。
#include <unistd.h>
int close(int fd);
引数 fd
にはソケットの識別子を指定します。これにより接続が閉じられ、一連の通信処理が終了します。
ソケット通信の簡単なプログラム例
では、ここまで解説してきた内容を踏まえてソケット通信の簡単なプログラムを紹介していきたいと思います。
プログラムは MacOSX で作成・動作確認したものになりますが、おそらく Linux でも動作するのではないかと思います
Windows の場合はインクルードするファイルが違ったりするので注意が必要ですが、各関数の名前や実行するタイミングなどはほぼ同じになると思います
作成するプログラムは「サーバー用」「クライアント用」の2つになります。つまりソースコードも2つ紹介します。
サーバープログラム
サーバー用のプログラムは下記のようになります。
サーバーとクライアントが同じ PC 上で動作することを前提に SERVER_ADDR
を設定しています。
もしサーバーとクライアントを別の PC 上で実行したい場合、SERVER_ADDR
にはサーバー自身の IP アドレスを指定してください(ループバックアドレスはダメです)。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 8080
#define BUF_SIZE 1024
int transfer(int);
int transfer(int sock) {
int recv_size, send_size;
char recv_buf[BUF_SIZE], send_buf;
while (1) {
/* クライアントから文字列を受信 */
recv_size = recv(sock, recv_buf, BUF_SIZE, 0);
if (recv_size == -1) {
printf("recv error\n");
break;
}
if (recv_size == 0) {
/* 受信サイズが0の場合は相手が接続閉じていると判断 */
printf("connection ended\n");
break;
}
/* 受信した文字列を表示 */
printf("%s\n", recv_buf);
/* 文字列が"finish"ならクライアントとの接続終了 */
if (strcmp(recv_buf, "finish") == 0) {
/* 接続終了を表す0を送信 */
send_buf = 0;
send_size = send(sock, &send_buf, 1, 0);
if (send_size == -1) {
printf("send error\n");
break;
}
break;
} else {
/* "finish"以外の場合はクライアントとの接続を継続 */
send_buf = 1;
send_size = send(sock, &send_buf, 1, 0);
if (send_size == -1) {
printf("send error\n");
break;
}
}
}
return 0;
}
int main(void) {
int w_sock, c_sock;
struct sockaddr_in a_addr;
/* ソケットを作成 */
w_sock = socket(AF_INET, SOCK_STREAM, 0);
if (w_sock == -1) {
printf("socket error\n");
return -1;
}
/* 構造体を全て0にセット */
memset(&a_addr, 0, sizeof(struct sockaddr_in));
/* サーバーのIPアドレスとポートの情報を設定 */
a_addr.sin_family = AF_INET;
a_addr.sin_port = htons((unsigned short)SERVER_PORT);
a_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
/* ソケットに情報を設定 */
if (bind(w_sock, (const struct sockaddr *)&a_addr, sizeof(a_addr)) == -1) {
printf("bind error\n");
close(w_sock);
return -1;
}
/* ソケットを接続待ちに設定 */
if (listen(w_sock, 3) == -1) {
printf("listen error\n");
close(w_sock);
return -1;
}
while (1) {
/* 接続要求の受け付け(接続要求くるまで待ち) */
printf("Waiting connect...\n");
c_sock = accept(w_sock, NULL, NULL);
if (c_sock == -1) {
printf("accept error\n");
close(w_sock);
return -1;
}
printf("Connected!!\n");
/* 接続済のソケットでデータのやり取り */
transfer(c_sock);
/* ソケット通信をクローズ */
close(c_sock);
/* 次の接続要求の受け付けに移る */
}
/* 接続待ちソケットをクローズ */
close(w_sock);
return 0;
}
スポンサーリンク
クライアントプログラム
クライアント用のプログラムは下記のようになります。
サーバーとクライアントが同じ PC 上で動作することを前提に SERVER_ADDR
を設定しています。
もしサーバーとクライアントを別の PC 上で実行したい場合、下記ソースコードの SERVER_ADDR
には接続先のサーバーの IP アドレスを指定してください(ループバックアドレスはダメです)。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 8080
#define BUF_SIZE 1024
int transfer(int);
int transfer(int sock) {
char send_buf[BUF_SIZE], recv_buf;
int send_size, recv_size;
while (1) {
/* サーバーに送る文字列を取得 */
printf("Input Message...\n");
scanf("%s", send_buf);
/* 文字列を送信 */
send_size = send(sock, send_buf, strlen(send_buf) + 1, 0);
if (send_size == -1) {
printf("send error\n");
break;
}
/* サーバーからの応答を受信 */
recv_size = recv(sock, &recv_buf, 1, 0);
if (recv_size == -1) {
printf("recv error\n");
break;
}
if (recv_size == 0) {
/* 受信サイズが0の場合は相手が接続閉じていると判断 */
printf("connection ended\n");
break;
}
/* 応答が0の場合はデータ送信終了 */
if (recv_buf == 0) {
printf("Finish connection\n");
break;
}
}
return 0;
}
int main(void) {
int sock;
struct sockaddr_in addr;
/* ソケットを作成 */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
printf("socket error\n");
return -1;
}
/* 構造体を全て0にセット */
memset(&addr, 0, sizeof(struct sockaddr_in));
/* サーバーのIPアドレスとポートの情報を設定 */
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned short)SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
/* サーバーに接続要求送信 */
printf("Start connect...\n");
if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) {
printf("connect error\n");
close(sock);
return -1;
}
printf("Finish connect!\n");
/* 接続済のソケットでデータのやり取り */
transfer(sock);
/* ソケット通信をクローズ */
close(sock);
return 0;
}
プログラムの解説
基本的にここまで解説してきた内容や関数に基づいて作成しているので、ソースコードを読んでいただければ何をやっているかは理解していただけるのではないかと思います。
ちょっとややこしいのが sockaddr_in
構造体の設定の仕方だと思います。
またデータのやり取り部分はプロトコルは意識せずに私が独自に決めたやり取りを行なっているので、この2点についてのみ解説しておきたいと思います。
sockaddr_in
構造体の設定
サーバー・クライアントともに sockaddr_in
構造体を利用し、この構造体のメンバに情報を詰め、サーバーでは bind
、クライアントでは connect
を行なっています(実際には関数の引数指定時に sockaddr
構造体にキャストされている)。
bind
ではサーバー自身のソケットの設定を行なうために sockaddr_in
のメンバを指定しています。なので、sockaddr_in
に設定する情報は、サーバーの IP アドレスとポート番号になります。
また、connect
では接続先(つまりサーバー)を設定するために sockaddr_in
のメンバをしています。なので、sockaddr_in
に設定する情報は、サーバーの IP アドレスとポート番号になります。
つまり、意味合いは違いますが、両方で sockaddr_in
に設定する情報は同じで、サーバーの IP アドレスとポート番号を設定しています。
サーバーの IP アドレスとしては "127.0.0.1"(SERVER_ADDR)
を設定しています。これはループバックアドレスと呼ばれ、プログラムを実行する PC 自身のアドレスを示すアドレスになります。
なので、今回のプログラムでは、サーバーはソケットに PC 自身のアドレスを設定し、クライアントは connect
で接続要求を送る先のアドレスを PC 自身のアドレスに設定している事になります。
つまり、クライアントが connect
を実行すると、同じ PC のサーバーのソケットに接続しにいくようになります。
こんな感じで1台でサーバークライアント間での通信を行う場合などに "127.0.0.1"
を使うと便利なので覚えておくと良いと思います。
ただし、IP アドレスを設定する sockaddr_in
構造体の sin_addr.s_addr
メンバに設定する必要があるのは数値であり、"127.0.0.1"
といった文字列ではありません。
この IP アドレスを表した文字列を数値に変換する必要があるので、inet_addr
関数を使ってこの変換を行なっています(同時に inet_addr
関数では次に説明する htons
同様にホストバイトオーダーをネットワークバイトオーダーに変換する効果もあります)。
ポート番号としては 8080(SERVER_PORT)
を指定しています。PC 上で利用されている他のポートと被らないように設定すれば良いので、他の数値でも問題ありません。
ただし、sockaddr_in
構造体の sin_port
にこのままポート番号を設定してはダメです。
これは、sin_port
にはネットワークバイトオーダーのデータを設定する必要があるためです。要は PC 上でのデータの扱い方(ホストバイトオーダー)とネットワーク上でのデータの扱い方が異なるので、ネットワーク上でのデータに変換してから sin_port
に設定する必要があります。
この変換を行うのが htons
になります。
上記を考慮して、クライアントサーバーともに下記のように sockaddr_in
構造体を設定しています。
/* サーバーのIPアドレスとポートの情報を設定 */
a_addr.sin_family = AF_INET;
a_addr.sin_port = htons((unsigned short)SERVER_PORT);
a_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
AF_INET
は設定する IP アドレスが IPv4 であることを指定するためのものになります。
こんな感じで、sockaddr_in
構造体のメンバに設定する項目自体は割と分かりやすいですが、設定する前に htons
や inet_addr
等を用いて sockaddr_in
構造体が期待する形式に変換する必要がある点に注意です。
データのやり取り(transfer
)
接続確立後に行うデータのやり取りはサーバー・クライアントともに transfer
関数の中で行なっています。
この transfer
関数で行う具体的なデータのやり取りは下記になります。
- クライアントがサーバーに文字列を送信する
- サーバーがクライアントが送信した文字列を受信する
- サーバーは受信した文字列を表示する
- サーバーがクライアントに接続を継続するかどうかを示すデータを送信する
- 受信した文字列が
"finish"
の場合は接続を継続しないことを示すデータ(0
)を送信する - それ以外の文字列の場合は接続を継続することを示すデータ(
1
)を送信する
- 受信した文字列が
- クライントが接続を継続するかどうかを示すデータを受信する
0
を受信した場合は接続を閉じる1
を受信した場合は最初に戻って文字列を送信する
なので、このプログラムにおけるサーバーが提供するサービスは「受信した文字列を表示する」という単純なものになります。
また、このやり取りを行うために、サーバーはまず recv
でデータ(文字列)が送信されてくるのを待機し、逆にクライアントはまず send
でデータ(文字列)の送信を行うようにしています。
さらに、クライアントは send
実行後は接続を継続するかどうかを示すデータを受信するために recv
を行い、逆にサーバーは文字列受信後に文字列を表示した後に、接続を継続するかどうかを示すデータを送信するために send
を実行しています。
こんな感じで、両方が同じタイミングで recv
を実行したりしてデータのやり取りが止まってしまわないように、お互いに send
と recv
を実行するタイミングの息を合わせるように transfer
関数を作成しています。
プログラムの実行方法
実行するプログラムはサーバー用のものとクライアント用のものの2つですので、2つのソースコードのコンパイルと2つのプログラムの実行が必要になります。
ポイントは2つのプログラムを同時に実行する必要があるところです。
例えば Mac や Linux のターミナルでは、基本的に1つのウィンドウ(タブ)で実行できるのは1つのプログラムだけです。
なので、ターミナルウィンドウを2つ立ち上げ、それぞれのウィンドウでサーバー用のプログラムとクライアント用のプログラムを実行する必要があります。
上記はターミナルでプログラムを実行するときの説明ですが、要はプログラムを同時に2つ実行できればそれで良いです。
コンパイル
コンパイルはいつも通りに行うことが可能です。
例えばターミナルで gcc
を用いてコンパイルするのであれば、ソースコード server.c
と client.c
を置いている場所に移動し、下記を実行することでコンパイルが行われます。
gcc server.c -o server.exe gcc client.c -o client.exe
プログラムの実行
上記を実行することで server.exe
と client.exe
が生成されますので、これらを実行することでプログラムを起動することができます。
例えばターミナルからプログラムを実行するのであれば、まず server.exe
を1つ目のターミナルウィンドウから実行します。
server.exe
では listen
が実行され、ソケットは接続待ち状態になり、さらには accept
が実行されて接続要求がくるまで待機している状態になります。
% ./server.exe Waiting connect...
続いて2つ目のターミナルウィンドウから client.exe
を実行します。
% ./client.exe Start connect... Finish connect! Input Message...
client.exe
は server.exe
が作成して bind
したソケットに対して connect
を行います。server.exe
のソケットはすでに接続待ちの状態ですので、client.exe
は connect
に成功して「データのやり取り」に移行します。
一方で、server.exe
も client.exe
の接続要求を受け付けることで accept
が完了し、データのやり取りに移行します。
% ./server.exe Waiting connect... Connected!!
接続確立後は、client.exe
に文字列を入力すれば、それを client.exe
が server.exe
に送信し、それを受信した server.exe
が受信した文字列をターミナル表示する、というデータのやり取りを行います(実際にまだ接続を継続するかどうかのデータのやり取りも行なっています)。
client.exe
に文字列を入力してからエンターキーを押すと、server.exe
側でその文字列が表示されることが確認できると思います。
また、server.exe
が "finish"
を受信した場合は、接続が終了するようにしています。
また、このプログラムでは下記のような現象が発生します。なぜこのような事になるかは解説の中で説明していますので、気になる方&理由がわからない方はぜひ解説を読み返してみてください。
server.exe
よりもclient.exe
の方を先に起動すると接続できないclient.exe
をたくさん立ち上げると接続が行われなくなる
スポンサーリンク
まとめ
このページではC言語でのソケット通信を行う流れの説明や、ソケット通信に使用する関数の紹介、さらには実際のソケット通信を行うプログラムの紹介を行いました!
サーバーやクライアントの役割や、ソケット通信を行う際にそれぞれで実行する関数について理解していただけたのではないかと思います。
通信は今や生活になくてはならないものですよね!
このサイトも通信によって閲覧していただいていますし、今やいろんな物が通信で繋がることで便利な機能がたくさん実現されています。で、この通信はソケット通信で行われているケースが多いです。
今後もさらに通信の需要も増えていくと思いますし、プログラマーやエンジニアにとっても必要な知識になっていますので、是非このページで解説したソケット通信についても覚えておいていただければと思います!
また、このページではソケット通信で TCP 通信を行う流れを説明しましたが、ソケット通信で UDP 通信を行うことも可能です。これに関しては下記ページで解説していますので、UDP 通信について学びたい方はぜひ下記ページを読んでみてください!
【C言語】UDP通信を行う