OANDA(FX 業者)が提供しているストリーミング API を使って,リアルタイムの為替レートを取得してみます。 データは HTTP の Transfer-Encoding: chunked を利用して送られてきます。
サーバとの通信は SSL/TLS で暗号化されているので,クライアント側にも暗号化と復号の処理が必要です。 OpenSSL を使えばよさそうですが,今回は簡単に済ませるために stunnel を使いました。
この場合の通信経路は,
クライアント ⇔ stunnel ⇔ インターネット ⇔ サーバ
となって,暗号化と復号の処理は stunnel が担ってくれます。
設定ファイルは以下のようになりました。
[oanda-stream]
client = yes
accept = 127.0.0.1:8081
connect = stream-fxtrade.oanda.com:443
ただし,デモ口座を使う場合は,
connect = stream-fxpractice.oanda.com:443
とします。
以下のプログラムを作成しました。 処理の流れがわかりやすいようにエラー処理は省略しています。
#include "stdafx.h"
#include <Winsock2.h>
#include <ws2tcpip.h>
#include <string>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
const std::string host = "127.0.0.1";
const int port = 8081;
const std::string accountId = "???????"; // アカウント ID
const std::string bearer = "???????"; // アクセストークン
const std::string instruments = "USD_JPY%2CEUR_JPY"; // レートを取得したい通貨ペアのリスト
SOCKET sock; // ソケット
// 1行送信
void SendLine(const std::string& line)
{
std::string s = line + "\r\n";
send(sock, s.c_str(), s.length(), 0);
}
// 1行受信
void RecvLine(std::string& line)
{
line.clear();
char c;
do
{
recv(sock, &c, 1, 0);
line += c;
} while (c != '\n');
}
int main()
{
// winsock の初期化
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// ソケットの作成
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 接続
sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, host.c_str(), &addr.sin_addr.s_addr);
connect(sock, (SOCKADDR *)& addr, sizeof(addr));
// リクエストの送信
SendLine("GET /v1/prices?accountId=" + accountId + "&instruments=" + instruments + " HTTP/1.1");
SendLine("Host: " + host);
SendLine("Authorization: Bearer " + bearer);
SendLine("");
// ステータスラインとヘッダの受信
while (1)
{
std::string line;
RecvLine(line);
if (line == "\r\n") // 空行を読んだらヘッダの終わり
break;
}
// ボディの受信
while (1)
{
// チャンクサイズの受信
std::string line;
RecvLine(line);
long cb = strtol(line.c_str(), nullptr, 16); // チャンクサイズは 16 進数
// チャンク本体の受信
char c;
for (int i = 0; i < cb; i++)
{
recv(sock, &c, 1, 0);
if (c != '\r')
{
std::cout.put(c);
}
}
// CRLF を読み飛ばす
recv(sock, &c, 1, 0);
recv(sock, &c, 1, 0);
//
std::cout.flush();
}
// 到達しないけど一応...
closesocket(sock);
WSACleanup();
return 0;
}
以下のように,JSON 形式のデータが取得できました。
{"tick":{"instrument":"USD_JPY","time":"2018-05-23T09:28:23.993870Z","bid":109.681,"ask":109.689}}
{"tick":{"instrument":"EUR_JPY","time":"2018-05-23T09:28:23.189355Z","bid":128.669,"ask":128.682}}