はじめに
講義でネットワークプログラミングをやり始めたのでノート代わりにまとめてみます。
当面は、プログラムをどう書いたとかをメモするだけで、基本概念とかまで突っ込むつもりは今の所ありません。
投稿は分けずに随時編集していく予定です。
プログラミング言語は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となる。
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アドレスの処理で問題が発生する場合があります。
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>
だったので...)
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を判定した。
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
- int socket(int domain, int type, int protocol);でsocketを作成する。
- sockaddr_in構造体にAddress Familyとportを指定する。
- int inet_pton(int af, const char *src, void *dst);で文字列 src を、アドレスファミリー af のネットワークアドレス構造体に変換し、dst にコピーする。
- int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);でサーバに繋ぐ。
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向けの構造体とかを利用するよう変更
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);
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のコードで。