/tags/0.2-release/src/conn.cc
C++ | 555 lines | 386 code | 102 blank | 67 comment | 72 complexity | 80915c0aab3995164c3b048c1b0015f5 MD5 | raw file
- // daemonshogi - a shogi program.
- // TCP/IP library.
- // Copyright (c) 2008 HORIKAWA Hisashi.
- // リファレンス
- // http://msdn.microsoft.com/en-us/library/ms740673(VS.85).aspx
- #define GTK_DISABLE_DEPRECATED 1
- #include <config.h>
- #ifdef _WINDOWS
- #define WIN32_LEAN_AND_MEAN
- #define _WIN32_WINNT 0x0501
- #include <winsock2.h>
- #include <ws2tcpip.h>
- #else // UNIX
- #include <sys/socket.h>
- #include <netdb.h>
- #include <unistd.h>
- #include <errno.h>
- #define closesocket close
- #define WSAGetLastError() errno
- #define WSACleanup()
- #endif
- #include <gtk/gtk.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <assert.h>
- #include <string>
- #include <list>
- using namespace std;
- G_BEGIN_DECLS
- #include "support.h"
- G_END_DECLS
- #include "conn.h"
- #include "canvas.h"
- #include "si/ui.h"
- /** ログウィンドウに表示 */
- void network_log_append_message(const char* format, ...)
- {
- char buf[1000];
- va_list ap;
- GtkTextBuffer* textbuf;
- GtkWidget* network_textview;
- va_start(ap, format);
- vsprintf(buf, format, ap);
- va_end(ap);
- network_textview = lookup_widget(g_canvas->network_log_window,
- "network_textview");
- assert(network_textview);
- // TODO: きちんと末尾に追加する。
- textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(network_textview));
- gtk_text_buffer_insert_at_cursor(textbuf, buf, -1);
- }
- /** 受信データを行に分ける */
- typedef list<string> RecvList;
- /** 行に分けた受信データ */
- static RecvList recv_list;
- static int wait_state = 0;
- static gint io_watch = 0;
- static int server_fd = -1;
- static guint idle_id = 0;
- /** 受信した指し手 */
- struct NetTe {
- int special; // 投了、時間切れなど
- TE te;
- int sec; // 秒数
- NetTe(): special(0), sec(0) { memset(&te, 0, sizeof(TE)); }
- };
- typedef list<NetTe> NetTeList;
- static NetTeList net_te_list;
- /** サーバとの接続を切断。 */
- void disconnect_server()
- {
- if (server_fd < 0)
- return;
- recv_list.clear();
- wait_state = 0;
- net_te_list.clear();
- g_source_remove(idle_id);
- idle_id = 0;
- gdk_input_remove(io_watch);
- io_watch = 0;
- closesocket(server_fd);
- server_fd = -1;
- GtkWidget* item = lookup_widget(g_canvas->window, "connect_server");
- assert(item);
- gtk_widget_set_sensitive(item, TRUE);
-
- daemon_canvas_change_mode(g_canvas, D_CANVAS_MODE_BOOK);
- }
- /** 0終端の文字列を送信 */
- static void csa_send(const char* line)
- {
- printf("send: %s", line); // DEBUG
- int ret = ::send(server_fd, line, strlen(line), 0);
- if (ret < 0) {
- printf("%s: socket write error.\n", __func__);
- exit(1);
- }
- }
- static string username, password;
- extern int network_game_start;
- /** ログイン待ち */
- static void state_1_read()
- {
- assert(recv_list.size() > 0);
- printf("%s: first line = %s\n", __func__, recv_list.front().c_str());
- string s = "LOGIN:" + username + " OK";
- if (recv_list.front() == s) {
- network_log_append_message("%s login ok.\n", username.c_str());
- recv_list.pop_front();
- wait_state = 2;
- // 引き続き待つ
- }
- else {
- network_log_append_message("'%s' fail login.\n", username.c_str());
- network_game_start = -1;
- }
- }
- extern char net_playername[2][100];
- extern char net_my_turn;
- static int game_total_time;
- /** ゲーム開始 */
- static void state_2_read()
- {
- assert(g_canvas);
- printf("%s\n", __func__); // DEBUG
- bool finished = false;
-
- RecvList::iterator it;
- for (it = recv_list.begin(); it != recv_list.end(); it++) {
- if (!it->find("Name+:", 0)) {
- strcpy(net_playername[0], string(*it, 6).c_str());
- }
- else if (!it->find("Name-:", 0)) {
- strcpy(net_playername[1], string(*it, 6).c_str());
- }
- else if (!it->find("Your_Turn:", 0)) {
- net_my_turn = string(*it, 10)[0];
- assert(net_my_turn == '+' || net_my_turn == '-');
- }
- else if (!it->find("Total_Time:", 0)) {
- game_total_time = 0;
- // TODO:
- }
- else if (*it == "END Game_Summary") {
- finished = true;
- break;
- }
- }
- if (!finished) {
- printf("data partial.\n"); // DEBUG
- return; // まだ全部届いていない
- }
- printf("receive summary.\n"); // DEBUG
- recv_list.erase(recv_list.begin(), ++it);
- csa_send("AGREE\n");
- wait_state = 3;
- }
- /** ゲーム開始 */
- static void state_3_read()
- {
- printf("%s: line = %s\n", __func__, recv_list.front().c_str()); // DEBUG
- string line = recv_list.front();
- recv_list.pop_front();
- if (line.find("START:", 0) == string::npos) {
- // reject ?
- network_log_append_message("reject game.\n");
- network_game_start = -1;
- return;
- }
- network_game_start = 1;
- wait_state = 4;
- }
- const char csa_koma_str[][3] = {
- "", "FU", "KY", "KE", "GI", "KI", "KA", "HI",
- "OU", "TO", "NY", "NK", "NG", "", "UM", "RY",
- };
- /** 手を作る. daemon_record_load_csa_te() を参考に。 */
- static void parse_te(const char* line, TE* te, int* sec)
- {
- char c;
- char koma_str[3];
- int p;
- assert(te);
- assert(sec);
- // xyが逆
- c = line[0];
- te->fm = ((line[2] - 0x30) << 4) + line[1] - 0x30;
- te->to = ((line[4] - 0x30) << 4) + line[3] - 0x30;
- sscanf(line + 5, "%2s,T%d", koma_str, sec);
- for (p = 1; p < 0xf; p++) {
- if (strcmp(koma_str, csa_koma_str[p]) == 0)
- break;
- }
- if (te->fm == 0) {
- // 駒打ち
- te->nari = 0;
- te->uti = p;
- }
- else {
- // 移動
- te->uti = 0;
- int pf = daemon_dboard_get_board(&g_canvas->board,
- te->fm & 0xf,
- te->fm >> 4) & 0xf;
- te->nari = pf <= 8 && p >= 9 ? 1 : 0;
- }
- printf("%s: from = %x, to = %x\n", __func__, te->fm, te->to);
- }
- extern GAME g_game;
- /** 相手の手を受信 */
- static void state_4_read()
- {
- printf("%s: front = '%s'\n", __func__, recv_list.front().c_str()); // DEBUG
- string line = recv_list.front();
- recv_list.pop_front();
- // TODO: 時間切れなどの検査
- // 勝ったとき
- // %TORYO
- // #RESIGN
- // #WIN
- NetTe net_te;
- if (!line.find("%TORYO", 0))
- net_te.special = SI_TORYO;
- else if (line == "#RESIGN")
- net_te.special = -1;
- else if (line == "#WIN")
- net_te.special = -2;
- else if (line == "#LOSE")
- net_te.special = -3;
- else {
- net_te.special = 0;
- parse_te(line.c_str(), &net_te.te, &net_te.sec);
- }
- net_te_list.push_back(net_te);
- }
- /** アイドル状態のコールバック */
- static gboolean on_idle(gpointer data)
- {
- if (recv_list.size() == 0) {
- printf("%s: remove on_idle.\n", __func__); // DEBUG
- idle_id = 0;
- return FALSE; // 受信するまで休む
- }
- switch (wait_state) {
- case 1:
- // ログイン待ち
- state_1_read();
- break;
- case 2:
- // ゲーム情報待ち
- state_2_read();
- break;
- case 3:
- // ゲーム開始待ち
- state_3_read();
- break;
- case 4:
- // 相手の手を待つ
- state_4_read();
- break;
- default:
- printf("wait_state = %d\n", wait_state); // DEBUG
- // assert(0);
- break;
- }
- return TRUE; // 引き続き呼び出してもらう
- }
- extern GAME g_game;
- /** ネットワーク経由の手を格納.
- ブロックする */
- INPUTSTATUS daemon_input_next_network_impl(BOARD* bo, TE* te)
- {
- // 手が設定されるまで待つ
- while (net_te_list.size() == 0) {
- pending_loop();
- usleep(50);
- }
- NetTe net_te = net_te_list.front();
- net_te_list.pop_front();
- if (net_te.special != 0) {
- // 中断または投了
- disconnect_server(); // TODO: もう少し丁寧に
- return (INPUTSTATUS) net_te.special;
- }
- else {
- *te = net_te.te;
- return SI_NORMAL;
- }
- }
- static string sock_buffer;
- /** ソケットのコールバック関数.
- 行を切り出して、recv_list に追加する. */
- static void on_socket_read(gpointer data, gint source,
- GdkInputCondition condition)
- {
- char buf[1000];
- int r = ::recv(server_fd, buf, sizeof(buf) - 1, 0);
- if (r < 0) {
- // エラー発生
- printf("%s: socket recv error.\n", __func__); // DEBUG
- exit(1);
- }
- else if (r == 0) {
- // 接続先が閉じた
- network_log_append_message("server shutdown.\n");
- disconnect_server();
- return;
- }
- buf[r] = '\0';
- sock_buffer += buf;
- // 行を切り出す. 末尾の改行は削除する
- int idx;
- while ((idx = sock_buffer.find("\n", 0)) != string::npos) {
- string l = string(sock_buffer, 0, idx);
- printf("recv: %s\n", l.c_str()); // DEBUG
- recv_list.push_back(l);
- sock_buffer.erase(0, idx + 1);
- }
- if (!idle_id && recv_list.size() > 0) {
- printf("%s: add on_idle.\n", __func__); // DEBUG
- idle_id = g_idle_add(on_idle, NULL);
- }
- }
- /** WinSockの初期化
- @return 成功したとき1
- */
- static int init_socket()
- {
- #ifdef _WINDOWS
- WSADATA wsaData;
- int r = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
- if (r != 0) {
- printf("WSAStartup() failed: %d\n", r);
- exit(1);
- }
- #endif
- return 1;
- }
- static int socket_inited = 0;
- /** サーバに接続する。
- @param hostname IPv4 or IPv6 ホスト名
- @param service ポート番号の文字列
- @return 成功したら1
- */
- int connect_to_server(const char* hostname, const char* service)
- {
- struct addrinfo hints;
- struct addrinfo* res = NULL;
- struct addrinfo* ai;
- if (!socket_inited) {
- socket_inited = init_socket();
- if (!socket_inited)
- return 0;
- }
- // ホスト情報を得る
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC; // IPv4/IPv6両対応
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_flags = NI_NUMERICSERV; // AI_NUMERICSERV // serviceはポート番号に限る
- int r = getaddrinfo(hostname, service, &hints, &res);
- if (r != 0) {
- printf("getaddrinfo() failed: %s\n", gai_strerror(r));
- return 0;
- }
- // 接続する
- int sockfd = -1;
- for (ai = res; ai; ai = ai->ai_next) {
- sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sockfd < 0) {
- printf("Error at socket(): %d\n", WSAGetLastError());
- break; // 致命的エラー
- }
- if (::connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
- closesocket(sockfd);
- sockfd = -1;
- continue; // 別のアドレスを試す
- }
- // ok
- break;
- }
- freeaddrinfo(res);
- if (sockfd > 0) {
- server_fd = sockfd;
- // コールバック関数を登録する
- io_watch = gdk_input_add(sockfd,
- GDK_INPUT_READ,
- on_socket_read,
- NULL);
- printf("io_watch = %d\n", io_watch); // DEBUG
- return 1;
- }
- else
- return 0;
- }
- ///////////////////////////////////////////////////////////////////
- // CSAプロトコル
- /** ログイン要求を送信する.
- @return 1 成功
- */
- int csa_send_login(const char* username_, const char* password_)
- {
- username = username_; password = password_;
- char buf[1000];
- sprintf(buf, "LOGIN %s %s\n", username_, password_);
- wait_state = 1;
- csa_send(buf);
- return 1;
- }
- /** 指し手を送信.
- daemon_record_output_csa_te() を参考に。 */
- int csa_send_move(const DBoard* board, int is_sente, const TE* te)
- {
- char buf[100];
- if (te->uti) {
- // 打ち
- sprintf(buf, "%c%d%d%d%d%s\n",
- is_sente ? '+' : '-',
- 0, 0,
- te->to & 0xf, te->to >> 4,
- csa_koma_str[te->uti]);
- }
- else {
- // 移動
- int p = daemon_dboard_get_board(board, te->fm & 0xf, te->fm >> 4);
- if (te->nari) p += 8;
- sprintf(buf, "%c%d%d%d%d%s\n",
- is_sente ? '+' : '-',
- te->fm & 0xf, te->fm >> 4,
- te->to & 0xf, te->to >> 4,
- csa_koma_str[p & 0xf]);
- }
- csa_send(buf);
- return 1;
- }
- /** 投了メッセージを送信 */
- int csa_send_toryo()
- {
- csa_send("%TORYO\n");
- return 1;
- }
- /** 手を送信した結果(経過秒数など)を得る.
- ブロックする。 */
- int csa_wait_for_result(int* elapsed_time, int* minmax_return)
- {
- printf("%s\n", __func__); // DEBUG
- while (net_te_list.size() == 0)
- pending_loop();
- NetTe net_te = net_te_list.front();
- net_te_list.pop_front();
- *minmax_return = net_te.special ? net_te.special : SI_NORMAL;
- *elapsed_time = net_te.sec;
- printf("my time = %d\n", *elapsed_time); // DEBUG
- return 1;
- }