[実習] TCPプログラミング

# はじめに
講義でネットワークプログラミングをやり始めたのでノート代わりにまとめてみます。
当面は、プログラムをどう書いたとかをメモするだけで、基本概念とかまで突っ込むつもりは今の所ありません。
投稿は分けずに随時編集していく予定です。
プログラミング言語はpython 2.7(研究で使っているので), c言語(講義指定)で書きます。ただし、全体ではなく一部なので参考にする場合は、読み込むライブラリや変数宣言は各自で汲み取ってください。

# 進め方
講義や課題に合わせて書いていきます。
現在は、下記の内容を書いてます。なお、接続先サーバーの情報に関しては先生に公開の確認をしてみます。

* クライアントがサーバーにTCP接続をするプログラム

# クライアント
## とりあえず一回繋ぐ！！
### 条件
* host, portをコマンドライン引数で渡せる
* IPv4とIPv6両対応
* addrinfoじゃなくてsockaddr系を使います。(講義の流れ的に)

### 実装(python:[github](https://github.com/kojisato118/computer_networking_report1/blob/master/tcp_client.py))
* TCP通信の仕方:IPv4  
[socketモジュール](http://docs.python.jp/2/library/socket.html)を使う。使い方は[ここ](http://memo.saitodev.com/home/python_network_programing/)より引用  
socket.AF_INETがIPv4プロトコルの指定で、SOCK_STREAMがストリームソケット(TCP)の指定となる。IPv6の場合、AF_INET6となり、UDPの場合はSOCK_DGRAMとなる。

```ipv4_simple_client.py
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with closing(sock):
  sock.connect((host, port))
  sock.send(b'Hello world')
  print(sock.recv(bufsize))
return
```
* TCP通信の仕方:IPv6  
基本的にIPv4と同じ。connectメソッドの引数が(host, port, flowinfo, scopeid)の長さ4のタプルになる。flowinfoとscopeidに何を入れたら良いのかは現状まだ理解できていない。。。  

> AF_INET6 アドレスファミリは (host, port, flowinfo, scopeid) の長さ4のタプルで示し、 flowinfo と scopeid にはそれぞれCの struct sockaddr_in6 における sin6_flowinfo と sin6_scope_id の値を指定します。後方互換性のため、 socket モジュールのメソッドでは sin6_flowinfo と sin6_scope_id を省略する事ができますが、 scopeid を省略するとスコープを持ったIPv6アドレスの処理で問題が発生する場合があります。

```ipv6_simple_client.py
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
with closing(sock):
  sock.connect((host, port, flowninfo, scopeid))
  sock.send(b'Hello world')
  print(sock.recv(bufsize))
return
```


* 引数の渡し方  
[argparse](http://docs.python.jp/2/library/argparse.html)モジュールを使う。使い方は[ここ](http://python.civic-apps.com/argparse/)を参照。ポート指定は```-p```と```--port```とし、host指定は```-b```と```--binding```にした(-hがhelpで埋まってて、railsでIPサーバー起動時のコマンドが```rails s -b <IP Address>```だったので...)

```argparse_sample.py
p = argparse.ArgumentParser()

p.add_argument('-b', '--binding', default='127.0.0.1')
p.add_argument('-p', '--port', default=3000, type=int)
args = p.parse_args()

print(args.binding)
print(args.port)
```

* IPv4とIPv6両対応  
注)正しい方法かはわからないです。  
[```socket.inet_pton(address_family, ip_string)```](http://docs.python.jp/2/library/socket.html#socket.inet_pton)を用いて、不正なIPアドレスの場合socket.errorが発生することを利用して、IPv4かIPv6を判定した。

```confirm_address_family.py
address_family = socket.AF_INET

try:
  socket.inet_pton(address_family, host)
except:
  address_family = socket.AF_INET6
  try:
    socket.inet_pton(address_family, host)
  except:
    raise Exception , "invalid host. please confirm the value of -b or --binding"
finally:
  print('address_family is %s' %address_family)
```

### 実装(c言語:[github](https://github.com/kojisato118/computer_networking_report1/blob/master/tcp_client.c))
* TCP通信の仕方:IPv4  

1. [int socket(int domain, int type, int protocol);](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/socket.2.html)でsocketを作成する。
2. sockaddr_in構造体にAddress Familyとportを指定する。
3. [int inet_pton(int af, const char *src, void *dst);](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/inet_pton.3.html)で文字列 src を、アドレスファミリー af のネットワークアドレス構造体に変換し、dst にコピーする。
4. [int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/connect.2.html)でサーバに繋ぐ。

```ipv4_simple_client.c
int family = AF_INET;
int sock = socket(family, SOCK_STREAM, 0);

struct sockaddr_in server;
server.sin_family = family;
server.sin_port = htons(port);

int pton = inet_pton(family, host, &server.sin_addr);
if (s != 1){
  perror("invalid host.");
  exit(1);
}

int  con = connect(sock, (struct sockaddr *)&server, sizeof(server));
if (con < 0){
  perror("connection failure.");
  exit(1);
}

char buf[244];
memset(buf, 0, sizeof(buf));
read(sock, buf, sizeof(buf));
printf("%s\n", buf);
```
* TCP通信の仕方:IPv6  
基本的にIPv4と同じ。IPv6向けの構造体とかを利用するよう変更 

```ipv6_simple_client.c
int family = AF_INET6;
int sock = socket(family, SOCK_STREAM, 0);

struct sockaddr_in6 server;
server.sin6_family = family;
server.sin6_port = htons(port);

int pton = inet_pton(family, host, &server.sin6_addr);
if (s != 1){
  perror("invalid host.");
  exit(1);
}

int  con = connect(sock, (struct sockaddr *)&server, sizeof(server));
if (con < 0){
  perror("connection failure.");
  exit(1);
}

char buf[244];
memset(buf, 0, sizeof(buf));
read(sock, buf, sizeof(buf));
printf("%s\n", buf);
```


* 引数の渡し方  
[getopt](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getopt.3.html)を使う。使い方は[ここ](http://www.fireproject.jp/feature/c-language/basic-library/getargs.html)を参照。ポート指定は```-p```とし、host指定は```-b```にした。

```getopt_sample.c
int result;
char *host;
int port;

while((result=getopt(argc,argv,"b:p:"))!=-1){
  switch(result){

  case 'b':
    host = optarg;
    break;
  case 'p':
    port = atoi(optarg);
    break;
  }
}
```

* IPv4とIPv6両対応  
注)正しい方法かはわからないです。  
inet_ptonを用いて、成功の場合は返り値が1となることを利用して、IPv4かIPv6を判定した[参考ページ](http://d.hatena.ne.jp/harre_orz/20120511/1336664448)。pythonと違って、inet_ptonの引数にAddress Familyに適した構造体を渡さないといけないため、いろいろややこしくなった。また、connectの第三引数のsizeはsockaddr構造体のsizeでなくsockaddr_inまたはsockaddr_in6のsizeである必要がある。詳しくは[githubのコード](https://github.com/kojisato118/computer_networking_report1/blob/master/tcp_client.c)で。