今までサーバ―クライアント間の通信を挟むようなプログラムを書いたことがなかったのですが、今後、そういう技能も必要になりそうなので、試しで書いてみました。
将来的にはC++でサーバ-クライアント間で画像通信などができたら良いと考えていますが、全く知識がないので、まずはサーバ側のプログラムとクライアント側のプログラムを使って、その間のデータをやり取りするということをやってみたいと思います。
環境がWindowsなので、Winsock2を使ってみることにしました。WinsockはWindows環境でソケットを用いてネットワークプログラミングする際のAPIで、TCP/IPなどを用いた通信ができるようになります。
ソケットとは何ぞや? という話ですが、通信する際の口になるようなもので、もっと具体的に例えると電話機のようなものです。
今回は、まず簡単なプログラムを書いてみるということで、クライアント側から2つの数字を与え、サーバ側でこの2つの数字を加算して返してくれるプログラムを書きました。何に使うかとかは考えておらず、あくまで学習用のテストプログラムという位置づけです。
本来は2台のPCでやるべきなんでしょうが、2台PCを用意してネットワーク環境を用意してやるのが面倒だったので、今回は同じPCの中にVisual Studioで2つのソリューションを作り、それぞれ実行しました。
よって、サーバとクライアントのPCは同じで、繋ぐときのIPアドレスはこのPCのIPアドレスを指定してやっています。
ソースコード
以下に、サーバ側とクライアント側のソースコードを示します。
// クライアント側 WinSock2 | |
#include <stdio.h> | |
#include <winsock2.h> | |
#include <ws2tcpip.h> | |
#include <iostream> | |
int main() { | |
char server_ip_addr[32]; | |
int port_number; | |
// Windows Sockets仕様に関する情報を格納する構造体 | |
WSADATA wsa_data; | |
// WinSockの初期化処理(Version 2.0) | |
if (WSAStartup(MAKEWORD(2, 0), &wsa_data) != 0) { | |
std::cerr << "Winsockの初期化失敗(WSAStartup)" << std::endl; | |
} | |
// ユーザ入力 | |
std::cout << "接続先IPアドレスを入力してください(xxx.xxx.xxx.xxx)" << std::endl; | |
std::cin >> server_ip_addr; | |
std::cout << "ポート番号を入力してください" << std::endl; | |
std::cin >> port_number; | |
// sockaddr_in構造体の作成とポート番号、IPタイプの入力 | |
struct sockaddr_in dst_addr; | |
memset(&dst_addr, 0, sizeof(dst_addr)); | |
dst_addr.sin_port = htons(port_number); // ポート番号 | |
dst_addr.sin_family = AF_INET; // AF_INETはipv4を示す | |
// 引数は (1) Type(ipv4 or v6) (2) IPアドレスのテキスト形式 (3) IPアドレスのバイナリ形式【(2)→(3)に変換】 | |
inet_pton(dst_addr.sin_family, server_ip_addr, &dst_addr.sin_addr.s_addr); | |
// AF_INETはipv4のIPプロトコル & SOCK_STREAMはTCPプロトコル | |
int dst_socket = socket(AF_INET, SOCK_STREAM, 0); | |
// 接続処理 | |
if (connect(dst_socket, (struct sockaddr *) &dst_addr, sizeof(dst_addr))) { | |
std::cerr << "接続失敗(サーバIPアドレス" << server_ip_addr << "/接続先ポート番号" << port_number << std::endl; | |
exit(0); | |
} | |
std::cout << "接続完了(サーバIPアドレス" << server_ip_addr << "/接続先ポート番号" << port_number << std::endl << std::endl;; | |
char send_buf1[256], send_buf2[256]; | |
char recv_buf[256]; | |
while (1) { | |
std::cout << "数字を2個入力してください" << std::endl; | |
std::cin >> send_buf1 >> send_buf2; | |
// Packetの送信(SOCKET, Buffer, Datasize, 送信方法) | |
send(dst_socket, send_buf1, 256, 0); | |
send(dst_socket, send_buf2, 256, 0); | |
// Packetの受信 | |
recv(dst_socket, recv_buf, 256, 0); | |
// 受信結果の表示 | |
std::cout << "合計は" << atoi(recv_buf) << std::endl << std::endl; | |
} | |
// 解放処理 | |
closesocket(dst_socket); | |
// WinSockの終了処理 | |
WSACleanup(); | |
return 0; | |
} |
// サーバ側 WinSock2 | |
#include <stdio.h> | |
#include <winsock2.h> | |
#include <ws2tcpip.h> | |
#include <iostream> | |
int main() { | |
// ポート番号 | |
int port_number = 12345; | |
// Windows Sockets仕様に関する情報を格納する構造体 | |
WSADATA wsa_data; | |
// WinSockの初期化処理(Version 2.0) | |
if (WSAStartup(MAKEWORD(2, 0), &wsa_data) != 0) { | |
std::cerr << "Winsockの初期化失敗(WSAStartup)" << std::endl; | |
} | |
// サーバ側ソケット作成 | |
int src_socket; | |
// sockaddr_in構造体の作成とポート番号、IPタイプの入力 | |
struct sockaddr_in src_addr; | |
memset(&src_addr, 0, sizeof(src_addr)); | |
src_addr.sin_port = htons(port_number); | |
src_addr.sin_family = AF_INET; | |
src_addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
// AF_INETはipv4のIPプロトコル & SOCK_STREAMはTCPプロトコル | |
src_socket = socket(AF_INET, SOCK_STREAM, 0); | |
// サーバ側のソケットを特定のIPアドレスとポートに紐付ける | |
bind(src_socket, (struct sockaddr *) &src_addr, sizeof(src_addr)); | |
// クライアント側のソケット設定 | |
int dst_socket; | |
struct sockaddr_in dst_addr; | |
int dst_addr_size = sizeof(dst_addr); | |
// 接続の待受を開始する | |
listen(src_socket, 1); | |
// 送受信に使用するバッファ | |
char recv_buf1[256], recv_buf2[256]; | |
char send_buf[256]; | |
// クライアントからの接続待ちループ関数 | |
while (1) { | |
std::cout << "クライアントからの接続待ち" << std::endl; | |
// クライアントからの接続を受信する | |
dst_socket = accept(src_socket, (struct sockaddr *) &dst_addr, &dst_addr_size); | |
std::cout << "クライアントからの接続有り" << std::endl; | |
// 接続後の処理 | |
while (1) { | |
int status; | |
//パケットの受信(recvは成功すると受信したデータのバイト数を返却。切断で0、失敗で-1が返却される | |
int recv1_result = recv(dst_socket, recv_buf1, sizeof(char) * 256, 0); | |
if (recv1_result == 0 || recv1_result == -1) { | |
status = closesocket(dst_socket); break; | |
} | |
std::cout << "受信した数字1個目 : " << recv_buf1 << std::endl; | |
int recv2_result = recv(dst_socket, recv_buf2, sizeof(char) * 256, 0); | |
if (recv2_result == 0 || recv2_result == -1) { | |
status = closesocket(dst_socket); break; | |
} | |
std::cout << "受信した数字2個目 : " << recv_buf2 << std::endl; | |
// 受信した数字を加算 | |
int sum = atoi(recv_buf1) + atoi(recv_buf2); | |
snprintf(send_buf, 256, "%d", sum); | |
std::cout << "結果 : "<< atoi(recv_buf1) << "+" << atoi(recv_buf2) << "=" << sum << std::endl; | |
// 結果を格納したパケットの送信 | |
send(dst_socket, send_buf, sizeof(char) * 256, 0); | |
} | |
} | |
// WinSockの終了処理 | |
WSACleanup(); | |
return 0; | |
} |
実行例
まず、接続を待ち受けるサーバ側のプログラムを起動してみます。
以下のように、サーバ側プログラムを立ち上げると受信待ちの状態になります。

ここで、クライアント側のプログラムを立ち上げていきます。ここで、サーバのIPアドレスとポート番号を求められるので、それぞれ入力していきます。サーバとクライアントに同じPCを使っているので、このPCのIPアドレスがサーバのIPアドレスになります。


数字の入力を求められるので、2個入力するとその加算結果がサーバで計算されて返却されてきます。

サーバ側のコンソールは、このとき以下のような画面になっています。

正常に送受信ができているように見えます。
まとめ
今回は簡単にソケットプログラミングのコードを書いてみました。
応用することで画像データなども送れそうなので、今度は画像データを送ったり、サーバ用、クライアント用のPCを作って送受信したりしてみたいですね。
また、C++やPythonの間でデータのやり取りをするなど、違う言語で書いたプログラム間のデータのやり取りなどにも応用できるかもしれません。