LoginSignup
13
12

More than 5 years have passed since last update.

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

Posted at

はじめに

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

進め方

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

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

クライアント

とりあえず一回繋ぐ!!

条件

  • host, portをコマンドライン引数で渡せる
  • IPv4とIPv6両対応
  • addrinfoじゃなくてsockaddr系を使います。(講義の流れ的に)

実装(python:github)

  • TCP通信の仕方:IPv4
    socketモジュールを使う。使い方はここより引用
    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モジュールを使う。使い方はここを参照。ポート指定は-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)を用いて、不正な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)

  • TCP通信の仕方:IPv4
  1. int socket(int domain, int type, int protocol);でsocketを作成する。
  2. sockaddr_in構造体にAddress Familyとportを指定する。
  3. int inet_pton(int af, const char *src, void *dst);で文字列 src を、アドレスファミリー af のネットワークアドレス構造体に変換し、dst にコピーする。
  4. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);でサーバに繋ぐ。
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を使う。使い方はここを参照。ポート指定は-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を判定した参考ページ。pythonと違って、inet_ptonの引数にAddress Familyに適した構造体を渡さないといけないため、いろいろややこしくなった。また、connectの第三引数のsizeはsockaddr構造体のsizeでなくsockaddr_inまたはsockaddr_in6のsizeである必要がある。詳しくはgithubのコードで。
13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12