和人くん、マジで知ってる!? socketのSO_REUSEADDRオプション
最近マジ、和人のソケット使いが荒すぎて困ってます、そんな時は
「まだ使われていますので、後ほどご利用ください...」
と丁寧にお断りしてください。
※画像はイメージです
それでは本題です
サーバー系のコードを見たりしてると、socketを作成しbind()する前にsetsockopt()でSO_REUSEADDRするのがほとんどですが、素直に...
「何でだよ、何でだよ、」
って2回ぐらいおもってしまいましたので、調べました、というか、ほとんど忘れてました。
後から気づきましたが、このソケットオプションを試すのに、こんなやりすぎたコードは必要なかったのですが、書いてしまったので載せます、コードが汚いので何をやっているのか先に図をご確認ください
動作の流れは、
- 「和人かどうかわからないプロセス」が「たぶん和人だと思われるプロセス」をforkします(これがサーバープロセスです)
- 「たぶん和人だと思われるプロセス」はポート9000でLISTENします
- 「和人かどうかわからないプロセス」はその後さらに「和人の子」をforkします(これがクライアントプロセスです)
- 「和人の子」は最大5個まで創られます(どうやって創られたかは不明です)
- 「和人の子」がいなくなると「和人かどうかわからないプロセス」はSIGCHLDシグナルを受けとりますので、しっかりwaipid()で受け止めて葬り去ります
ビルド方法
Linux
$ gcc -g -O0 so_reuseaddr.c -o no_reuse ・・・ SO_REUSEADDRなし $ gcc -g -O0 so_reuseaddr.c -o no_reuse -DKAZUTO・・・SO_REUSEADDRあり
Solaris
cc -g -O0 so_reuseaddr.c -lsocket -lnsl -o no_reuse cc -g -O0 so_reuseaddr.c -lsocket -lnsl -DKAZUTO -o reuseとやりますとそれぞれの実行可能形式のファイルが出来上がります。
試してみる
* SO_REUSEADDRオプションを有効にしない場合
cuomo@karky7 ~ $ ./no_reuse 和人は創りました(1号) pid=4243 和人の子 pid=4243 は暇人なので 9秒だけ寝るw.. 和人は創りました(2号) pid=4245 和人の子 pid=4245 は暇人なので 9秒だけ寝るw.. 和人は創りました(3号) pid=4246 和人の子 pid=4246 は暇人なので 6秒だけ寝るw.. 和人は創りました(4号) pid=4264 和人の子 pid=4264 は暇人なので 7秒だけ寝るw.. 和人は創りました(5号) pid=4265 和人の子 pid=4265 は暇人なので 3秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58599 <-- accept()が起きた 和人の子供がオワタ(5号) 4246 和人は創りました(5号) pid=4266 和人の子 pid=4266 は暇人なので 3秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58600 ... ... 和人が親なんて信じられるかっ!! もう出るわ! 和人の子供がオワタ(5号) 4266 和人は創りました(5号) pid=4273 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58605 和人が親なんて信じられるかっ!! もう出るわ! 和人の子供がオワタ(5号) 4269 和人は創りました(5号) pid=4274 割り込み ... ... cuomo@karky7 ~ $ ./no_reuse <----- 2回目の実行 和人は創りました(1号) pid=4277 ああぁー、やっちまった...: Address already in use <---- まだ使えない 和人の子供がオワタ(1号) 4276 和人は創りました(1号) pid=4278 和人の子 pid=4278 は暇人なので 3秒だけ寝るw.. 和人の子 pid=4277 は暇人なので 3秒だけ寝るw.. 和人は創りました(2号) pid=4280 和人の子 pid=4280 は暇人なので 3秒だけ寝るw.. 割り込み ... ... cuomo@karky7 ~ $ netstat -tpn (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 192.168.254.21:58603 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:9000 192.168.254.21:58605 TIME_WAIT - tcp 0 0 192.168.254.21:58602 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:9000 192.168.254.21:58604 TIME_WAIT - tcp 0 0 192.168.254.21:58599 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58600 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58601 192.168.254.21:9000 TIME_WAIT - cuomo@karky7 ~ $
SO_REUSEADDRオプションをつけないと2回め以降のbind()が「Address already in use」で失敗し、サーバープロセスが起動できません。よって和人の子は孤児のまま彷徨う事になります。
ようするに
「192.168.254.21:9000へbind()したいのですが、まだそのコネクションがTIME_WAIT状態で残っているのでダメですよ」
ってこと、じゃSO_REUSEADDRをつけるとどうなるのと言えば...
* SO_REUSEADDRを有効にした場合
「和人! Socketをリユーズするんだ!」でソケットにオプションを追加しています
~ $ ./reuse 和人は創りました(1号) pid=5966 和人! Socketをリユーズするんだ! <----- SO_REUSEADDRをいれる 和人の子 pid=5966 は暇人なので 6秒だけ寝るw.. 和人は創りました(2号) pid=5967 和人の子 pid=5967 は暇人なので 3秒だけ寝るw.. 和人は創りました(3号) pid=5969 和人の子 pid=5969 は暇人なので 7秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58689 和人の子供がオワタ(3号) 5967 和人は創りました(3号) pid=5970 ... ... 和人の子供がオワタ(4号) 5969 和人は創りました(4号) pid=5977 和人の子 pid=5977 は暇人なので 8秒だけ寝るw.. 和人は創りました(5号) pid=5994 和人の子 pid=5994 は暇人なので 8秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58694 和人の子供がオワタ(5号) 5973 和人は創りました(5号) pid=5996 和人の子 pid=5996 は暇人なので 4秒だけ寝るw.. 割り込み ~ $ ./reuse 和人は創りました(1号) pid=5999 和人! Socketをリユーズするんだ! 和人の子 pid=5999 は暇人なので 5秒だけ寝るw.. 和人は創りました(2号) pid=6001 和人の子 pid=6001 は暇人なので 8秒だけ寝るw.. 和人は創りました(3号) pid=6002 和人の子 pid=6002 は暇人なので 8秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58695 和人の子供がオワタ(3号) 5999 和人は創りました(3号) pid=6003 ... ... 和人の子 pid=6028 は暇人なので 3秒だけ寝るw.. 和人が親なんて信じられるかっ!! もう出るわ! 接続してきた和人の子 ... 192.168.254.21, 和人ポート 58702 和人の子供がオワタ(4号) 6026 和人は創りました(4号) pid=6029 和人の子 pid=6029 は暇人なので 3秒だけ寝るw.. ~ $ ./reuse 和人! Socketをリユーズするんだ! 和人は創りました(1号) pid=6033 和人の子 pid=6033 は暇人なので 9秒だけ寝るw.. 和人は創りました(2号) pid=6034 和人の子 pid=6034 は暇人なので 7秒だけ寝るw.. 和人は創りました(3号) pid=6036 和人は創りました(4号) pid=6037 ... ... ~ $ netstat -tpn (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 192.168.254.21:58698 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58700 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58704 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58699 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58703 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:9000 192.168.254.21:58702 TIME_WAIT - tcp 0 0 192.168.254.21:58701 192.168.254.21:9000 TIME_WAIT - tcp 0 0 192.168.254.21:58702 192.168.254.21:9000 TIME_WAIT -TIME_WAITが残ってても、必ずbind()が成功する。
サーバーを再起動とかかけたときにSO_REUSEADDRをしてないと、コネクションが綺麗に始末されるまでサーバーを起動できないって事になりかねませんね、これは
危ない!
ということで、サーバーを書くときはこのオプションをつけましょうというお話、
まぁ和人はどうでもいいが...
もう、和人のSocketネタはつまらないからやめる...
最後にコード
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/* Solaris: cc -g -O0 so_reuseaddr.c -lsocket -lnsl -o no_reuse */
/* cc -g -O0 so_reuseaddr.c -lsocket -lnsl -DKAZUTO -o reuse */
/* Linux: gcc -g -O0 so_reuseaddr.c -o no_reuse */
/* gcc -g -O0 so_reuseaddr.c -DKAZUTO -o reuse */
int process = 0;
void sig_child(int);
void child_func(void);
void server_func(void);
void set_sockaddrin(struct sockaddr_in *, int);
int main(void)
{
pid_t pid;
struct sigaction sa, osa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = &sig_child;
sa.sa_flags |= SA_RESTART;
sigaction(SIGCHLD, &sa, &osa);
pid = fork();
if(pid == 0) {
server_func();
} else if(pid < 0) {
perror("しょっぱなから無理でしょ");
return -1;
}
while(1) {
if(process < 5) {
pid = fork();
if(pid == 0) {
child_func();
} else if(pid < 0) {
perror("和人は小作りに失敗しました");
} else {
process++;
printf("和人は創りました(%d号) pid=%d\n", process, pid);
}
}
sleep(2);
}
return 1;
}
void server_func(void)
{
int sock, result, con;
socklen_t len;
struct sockaddr_in serv, cli;
char buff[1024];
const char *p;
#ifdef KAZUTO
/* SO_REUSEADDRを有効にする場合は1、無効にする場合は0をセット */
int reuseaddr = 1;
#endif
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1) {
perror("ソケット...どうしてくれんだ");
exit(EXIT_FAILURE);
}
set_sockaddrin(&serv, sizeof(serv));
#ifdef KAZUTO
puts("和人! Socketをリユーズするんだ!");
result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(const void *) &reuseaddr, sizeof(int));
if(result == -1) {
perror("和人はできまい、、がセットできないよぉー\n");
exit(EXIT_FAILURE);
}
#endif
if (bind(sock, (struct sockaddr*)&serv, sizeof(serv)) == -1) {
perror("ああぁー、やっちまった...");
exit(EXIT_FAILURE);
}
if(listen(sock, 15) < 0) {
perror("ああぁー、リッスンゴレライ...");
exit(EXIT_FAILURE);
}
while(1) {
len = sizeof(cli);
con = accept(sock, (struct sockaddr*)&cli, &len);
if(con < 0) {
perror("なにが起こった和人くん!");
} else {
p = inet_ntop(AF_INET, &cli.sin_addr, buff, sizeof(buff));
printf("接続してきた和人の子 ... %s, 和人ポート %d\n",
p,
ntohs(cli.sin_port));
close(con);
}
}
}
void child_func(void)
{
int sock;
int r, result;
time_t t;
pid_t pid;
struct sockaddr_in serv;
pid = getpid();
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1) {
perror("ソケット...そそそ");
exit(EXIT_FAILURE);
}
set_sockaddrin(&serv, sizeof(serv));
r = 0;
while(r < 3) {
t = time(NULL);
srand(t);
r = rand() % 10;
}
printf("和人の子 pid=%d は暇人なので %d秒だけ寝るw..\n", pid, r);
sleep(r);
if(connect(sock, (struct sockaddr*)&serv, sizeof(serv)) < 0) {
perror("和人め裏切ったか...");
exit(1);
}
puts("和人が親なんて信じられるかっ!! もう出るわ!");
close(sock);
exit(1);
}
void sig_child(int no)
{
pid_t pid;
int stat;
while((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
printf("和人の子供がオワタ(%d号) %d\n", process, pid);
if(process > 0) {
process--;
}
}
return;
}
void set_sockaddrin(struct sockaddr_in *serv, int size)
{
bzero(serv, size);
serv->sin_family = AF_INET;
inet_pton(AF_INET, "192.168.254.21", &serv->sin_addr.s_addr);
serv->sin_port = htons(9000);
}
Socket遊びは止めるんだ... 和人...


0 件のコメント:
コメントを投稿