#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFSIZE      8192
#define DEFAULT_PORT "5320"
#define CMAX         256

enum {CMD_NAME, DST_IP, DST_PORT};

int main(int argc, char *argv[])
{
  struct addrinfo info, *res; /* アドレス情報                   */
  char ip[CMAX];              /* IPアドレスを文字列として格納   */
  int s;                      /* ソケットディスクリプタ         */
  int n;                      /* 入力データのバイト数           */
  char buf[BUFSIZE];          /* 受信バッファ                   */
  char cmd[BUFSIZE];          /* 送信バッファ                   */
  struct timeval tv;          /* selectのタイムアウト時間       */
  fd_set readfd;              /* selectで検出するディスクリプタ */

  /* 引き数のチェック */
  if (argc !=2 && argc != 3) {
    fprintf(stderr, "Usage: %s  hostname  [port]\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  memset(&info, 0, sizeof(info));
  info.ai_flags    = AI_CANONNAME;
  info.ai_family   = AF_UNSPEC;
  info.ai_socktype = SOCK_STREAM;

  if (getaddrinfo(argv[DST_IP], (argc == 3)?argv[DST_PORT]:DEFAULT_PORT, &info,
      &res) != 0) {
    perror("getaddrinfo");
    exit(EXIT_FAILURE);       
  }

  /* ソケットを開く */
  if ((s = socket(res->ai_family, res->ai_socktype, 0)) < 0) {
    perror("socket");
    exit(EXIT_FAILURE);
  }

  /* コネクションを確立する */
  if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
    perror("connect");
    exit(EXIT_FAILURE);
  }

  if (res->ai_family == AF_INET6)
    printf("connected to '%s' by IPv6\n", 
           inet_ntop(AF_INET6,
                     &(((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr),
           ip, CMAX));
  else if (res->ai_family == AF_INET)
    printf("connected to '%s' by IPv4\n", 
           inet_ntop(AF_INET,
                     &(((struct sockaddr_in *)(res->ai_addr))->sin_addr),
           ip, CMAX));
  else
    printf("connected by no IPv4/IPv6.\n");

  /* クライアント処理メインルーチン */
  while (1) {
    /* selectのタイムアウトの設定 */
    tv.tv_sec  = 600;
    tv.tv_usec = 0;

    /* 標準入力、サーバからのメッセージがあるか */
    FD_ZERO(&readfd);
    FD_SET(0, &readfd);
    FD_SET(s, &readfd);
    if ((select(s + 1, &readfd, NULL, NULL, &tv)) <= 0) {
      fprintf(stderr, "\nTimeout\n");
      break;
    }

    /* 標準入力 */
    if (FD_ISSET(0, &readfd)) {
      if ((n = read(0, buf, BUFSIZE-1)) <= 0)
        break;
      buf[n]='\0';
      sscanf(buf, "%s", cmd);
      if (strcmp(cmd, "quit") == 0)
        break;
      if (send(s, buf, n, 0) <= 0)
        break;
    }

    /* サーバ */
    if (FD_ISSET(s, &readfd)) {
      if ((n = recv(s, buf, BUFSIZE-1, 0)) <= 0) {
        fprintf(stderr, "connection closed.\n");
        exit(EXIT_FAILURE);
      }
      buf[n]='\0';
      printf("%s", buf);
      fflush(stdout);
    }
  }

  strcpy(buf, "quit");
  send(s, buf, n, 0);
  close(s);
  printf("connection closed.\n");

  return EXIT_SUCCESS;
}