Armadilloフォーラム

popen()でNULLが返る件

yoshizu

2016年2月24日 10時00分

yoshizuです
いつも、お世話になっています。

atmadillo-840 atde5-i386-20140131
の環境で、armadillo側サーバーsocketが、 ネットワーク経由で他のPCからLinux上で
実行するコマンドを受け取り、armadillo側でpopen()により、そのlinuxコマンドを実行し、
実行結果をsocket経由で、他のPCへ返すというスレッドプログラムを作成しました。

しかしながら、このスレッドを1000回実行すると popen()関数からNULLが返ってきてしまい
本スレッドが続行できなくなってしまします。

(目的は  "iwconfig wlan1"を実行し その結果の出力メッセージを読み取る という繰り返しを行うため。)

ネット上の情報からulimit -n 65536としましたが、変化ありませんでした。

回避策がなく、困っています。

アドバイス頂ければと思い投稿しました。

以下がソースで★が該当部分です。

void* ClientThread_51020(void* vp)
{

COMMON* common=(COMMON*)vp;
int ss;
char cmd_buf[256];
char param0[80];
char param1[80];
int i;
int j;
int counter;
char sprintf_work[4096];
time_t timer;
unsigned char *p;
int xfr_size;

pthread_t id;
struct sockaddr_in addr, caddr;
socklen_t caddr_len;
int optval, optlen;
unsigned short listen_port;
int csock;

FILE *in_pipe;

/* 読み込み用バッファ */
char buffer[BUFSIZE + 1];
int readed_num;

/* ソケットのオープン */

ss = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if ( ss < 0 ){
perror("socket");
}

optval = 1;
optlen = sizeof(optval);

/* ソケットオプション */

setsockopt(ss,SOL_SOCKET,SO_REUSEADDR,
&optval,optlen);

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;

signal(SIGPIPE,SIG_IGN);

/* 待ちうけport */
listen_port =PORT_51020;

addr.sin_port = htons(listen_port);

/* BIND失敗なら次のポートへ */
while(1){
addr.sin_port = htons(listen_port);

if ( bind(ss,(struct sockaddr*)&addr,sizeof(addr)) < 0 ){
perror("bind3");
listen_port++;
continue;
}else{
break;
}

}
/* リッスン */

if ( listen(ss,SOMAXCONN) < 0 ){
perror("listen");
}

/* クライントからLinuxの指令を受け取りpopenで実行 /

while(1){

caddr_len = sizeof(caddr);

  /* アクセプトによりクライアントからの接続まで先へは進まない */

 csock = accept(ss,(struct sockaddr*)&caddr,&caddr_len);
 if ( csock < 0 ){
printf("accept error\n");
perror("accept"); continue;
 }

while(1){
memset(cmd_buf,0,sizeof(cmd_buf));

//PCからの受信

if(recv(csock,cmd_buf,sizeof(cmd_buf),0)==0)
{
//PC GUIが終了されたらここ

puts("Close from PC");

break;
}
/* バッファを初期化 */
memset(buffer,'\0',sizeof(buffer));

/* パイプを読み込みモードでコマンドを実行 */

in_pipe = popen(cmd_buf,"r");

if(in_pipe==NULL){
puts("in_pipe==NULL");//★これが発生
exit (0);
}
if(in_pipe < 0){
puts("in_pipe < 0");
exit (0);
}

i=0;

memset(line_buf,0,sizeof(line_buf));

//コマンド実行結果をline_bufferに入れる

if(in_pipe != NULL){
while(fread(buffer,1,1,in_pipe) > 0){
output(buffer);
line_buf[i]=*buffer;
i++;
if(i>(sizeof(line_buf)-10))break;//line_bufを破壊防止
}
}
pclose(in_pipe);
memset(sprintf_work,0,sizeof(sprintf_work));

sprintf(sprintf_work,"%s\n", line_buf);  

//PCに実行結果を送信
send(csock,sprintf_work,sizeof(sprintf_work),0);
sleep (1);

}

}
close(ss);
}

以上よろしくお願い致します。

コメント

齊藤と申します。
とりあえず、コマンドが正常に受信できているかとどういうエラーで失敗しているかを調べてはどうでしょう。
こんな感じ。

in_pipe = popen(cmd_buf,"r");

if(in_pipe==NULL){
perror("popen");
puts(cmd_buf);
exit (0);
}

斉藤様
yoshizuです
お世話になっています

puts(cmd_buf);を毎回出力しました。
in_pipe==NULLの時の1000回目も
正常な999回目と同様cmd_bufには正しく
入っていました。

51020 socket close from pc
wait loop
accept ok
iwconfig wlan1(★ 999回目)
12:22 in_pipe=103416
Close from PC
51020 socket close from pc
wait loop
accept ok
iwconfig wlan1 (★ 1000回目)
in_pipe==NULL

以上よろしくお願い致します。

> 齊藤と申します。
> とりあえず、コマンドが正常に受信できているかとどういうエラーで失敗しているかを調べてはどうでしょう。
> こんな感じ。
>
> in_pipe = popen(cmd_buf,"r");
>
> if(in_pipe==NULL){
> perror("popen");
> puts(cmd_buf);
> exit (0);
> }

中村様
yoshizuです
お世話になっています。

ご指摘のとおりcsockのcloseがありませんでした。
処理を追加することで、NULLとならなくなりました。

csock = accept(ss,(struct sockaddr*)&caddr,&caddr_len);
のclose(csock)とclose(ss)の違いを理解しておらず
勉強します。

対応ありがとうございました。

> 中村です。
>
> accept()の戻り値csockをclose()してますか?
>
> --
> なかむら
>

伊澤です。

本題と外れたところが気になりました。

> while(1){
> memset(cmd_buf,0,sizeof(cmd_buf));
>
> //PCからの受信
>
> if(recv(csock,cmd_buf,sizeof(cmd_buf),0)==0)
折角のrecv()の戻り値捨てちゃって大丈夫ですか?
socketは読み込み途中での復帰も有り得たと思いましたが。
# まぁ、実際にはこの件では問題出てないようですが……

> {
> //PC GUIが終了されたらここ
>
> puts("Close from PC");
>
> break;
> }
> /* バッファを初期化 */
> memset(buffer,'\0',sizeof(buffer));
>
> /* パイプを読み込みモードでコマンドを実行 */
>
> in_pipe = popen(cmd_buf,"r");
>
> if(in_pipe==NULL){
> puts("in_pipe==NULL");//★これが発生
> exit (0);
> }
> if(in_pipe < 0){
ポインタ値のマイナスチェックって?

> puts("in_pipe < 0");
> exit (0);
> }
>
> i=0;
>
> memset(line_buf,0,sizeof(line_buf));
>
> //コマンド実行結果をline_bufferに入れる
>
> if(in_pipe != NULL){
> while(fread(buffer,1,1,in_pipe) > 0){
この辺さくっとfgets()では済みませんか?
それと、1バイトしか使わないのにbuffer配列を使ったら勿体無いような。

> output(buffer);
> line_buf[i]=*buffer;
> i++;
> if(i>(sizeof(line_buf)-10))break;//line_bufを破壊防止
> }
> }
> pclose(in_pipe);
in_pipeがNULLのときも呼んでますが、
pclose()はNULLを適切に処理できましたっけ?

> memset(sprintf_work,0,sizeof(sprintf_work));
>
> sprintf(sprintf_work,"%s\n", line_buf);  
これも、単にstrcpy()でいいような。
と言うかそもそもsprintf_workは要ります?

>
>
> //PCに実行結果を送信
> send(csock,sprintf_work,sizeof(sprintf_work),0);
これは明らかに余計なナル文字を送っちゃいますね。

> sleep (1);
>
>
> }
line_bufはグローバルなんでしょうか。まさかとは思いますが、
他の非同期で走る処理とバッティングしてませんよね。

スタックにKB単位で確保してその度にクリアするなんて
「配列を取り敢えずクリアしてからそこに放り込めば大丈夫」
って戦略は幾らメモリにも余裕のあるアルマジロとはいえ
資源の乏しい組み込み向けの戦略じゃない気がします。
実際、文字列として扱うのかバイト列として扱うのか
切り分けができていないからこそのsend()の問題ですし。

企業文化もあるのであれこれ言いたくはありませんが……

伊澤様
yoshizuです

いろいろ指摘ありがとうございます

ソースを見直してみます。

> 伊澤です。
>
> 本題と外れたところが気になりました。
>
> > while(1){
> > memset(cmd_buf,0,sizeof(cmd_buf));
> >
> > //PCからの受信
> >
> > if(recv(csock,cmd_buf,sizeof(cmd_buf),0)==0)
> 折角のrecv()の戻り値捨てちゃって大丈夫ですか?
> socketは読み込み途中での復帰も有り得たと思いましたが。
> # まぁ、実際にはこの件では問題出てないようですが……
>
> > {
> > //PC GUIが終了されたらここ
> >
> > puts("Close from PC");
> >
> > break;
> > }
> > /* バッファを初期化 */
> > memset(buffer,'\0',sizeof(buffer));
> >
> > /* パイプを読み込みモードでコマンドを実行 */
> >
> > in_pipe = popen(cmd_buf,"r");
> >
> > if(in_pipe==NULL){
> > puts("in_pipe==NULL");//★これが発生
> > exit (0);
> > }
> > if(in_pipe < 0){
> ポインタ値のマイナスチェックって?
>
> > puts("in_pipe < 0");
> > exit (0);
> > }
> >
> > i=0;
> >
> > memset(line_buf,0,sizeof(line_buf));
> >
> > //コマンド実行結果をline_bufferに入れる
> >
> > if(in_pipe != NULL){
> > while(fread(buffer,1,1,in_pipe) > 0){
> この辺さくっとfgets()では済みませんか?
> それと、1バイトしか使わないのにbuffer配列を使ったら勿体無いような。
>
> > output(buffer);
> > line_buf[i]=*buffer;
> > i++;
> > if(i>(sizeof(line_buf)-10))break;//line_bufを破壊防止
> > }
> > }
> > pclose(in_pipe);
> in_pipeがNULLのときも呼んでますが、
> pclose()はNULLを適切に処理できましたっけ?
>
> > memset(sprintf_work,0,sizeof(sprintf_work));
> >
> > sprintf(sprintf_work,"%s\n", line_buf);  
> これも、単にstrcpy()でいいような。
> と言うかそもそもsprintf_workは要ります?
>
> >
> >
> > //PCに実行結果を送信
> > send(csock,sprintf_work,sizeof(sprintf_work),0);
> これは明らかに余計なナル文字を送っちゃいますね。
>
> > sleep (1);
> >
> >
> > }
> line_bufはグローバルなんでしょうか。まさかとは思いますが、
> 他の非同期で走る処理とバッティングしてませんよね。
>
> スタックにKB単位で確保してその度にクリアするなんて
> 「配列を取り敢えずクリアしてからそこに放り込めば大丈夫」
> って戦略は幾らメモリにも余裕のあるアルマジロとはいえ
> 資源の乏しい組み込み向けの戦略じゃない気がします。
> 実際、文字列として扱うのかバイト列として扱うのか
> 切り分けができていないからこそのsend()の問題ですし。
>
> 企業文化もあるのであれこれ言いたくはありませんが……