SAK 図書館

SAK Streets メインストリート 図書館・カフェ 一覧 SAK Streets 検索
| VB | SQL | Perl | PHP | ASP | JSP | C | PC | HP | FAQ | フリーソフト |

CGI-Perl 応用実地編 - 簡易伝言板作成8 (ファイル排他制御)

■伝言投稿ファイル排他制御

  ・この伝言板は、ひとつのファイルに複数ユーザが書き込むので、更新が重な
    ると、ファイルが壊れる可能性があります。
    実のところ、こういう危険な仕様のプログラムは作ったことがありません。
    私が公開しているプログラムでは、排他制御がなくても壊れない仕様になっ
    ています。

  ・ファイルを壊さないためには、更新中は他のユーザの投稿を待たせるか、
    エラーで落とす機能が必要となります。
    次の排他関数 filelock() と unfilelock() を最後に追加します。
    尚、filelock() 中の「#// 強制アンロック」は、# で注釈にした方が良い
    かもしれません。なぜ、ファイルが壊れる危険をおかして、強制アンロック
    しているのかと言うと、サーバ異常などでプログラムがロックしたまま異常
    終了しても、一切の投稿ができなくなるのを防ぐためです。
    この関数では、1 秒待って 5 回のリトライをしても、他ユーザの更新が終
    わらなければ、強制的にアンロックしています。

#//=====================================================================
#//  ファイルロック
#//=====================================================================
sub filelock
{
  my($ct);

#// ファイルロック
  for ($ct = 1; $ct <= 5; $ct++) {
    if (-e "filelock.dat") {
      sleep 1;
    }
    else {
      if (open(FILE, ">filelock.dat")) {
        close(FILE);
        return 1;
      }
    }
  }

#// 強制アンロック
  unfilelock();

#// 戻り値セット
  return 0;
}

#//=====================================================================
#//  ファイルアンロック
#//=====================================================================
sub unfilelock
{
  my($ct);

#// ファイルアンロック
  for ($ct = 1; $ct <= 5; $ct++) {
    if (unlink "filelock.dat") {
      return 1;
    }
    sleep 1;
  }

#// 戻り値セット
  return 0;
}

  ・「#// 伝言ファイル書き込み」の前に、次のように追加します。
    ロックした以降は、プログラムがエラーになっても必ず、unfilelock() が
    呼び出されるようにしなくてはなりません。

  #// 排他開始
    if (!filelock()) {
      DspMsg("他のユーザと投稿が重なりました。<BR>しばらくしてから投稿して下さい。");
      exit(0);
    }

  ・「#// 伝言ファイル書き込み」のファイルオープンでエラーになると、
    exit(0) しているので、このままではロックしたまま終了してしまいます。
    そこで、DspMsg() の前に「unfilelock();」を追加しています。

      unfilelock();
      DspMsg("ファイル書き込みオープンエラー ($msdnm$msfnm)");

  ・「#// 投稿処理終了」の前に、次のように追加します。
    更新が終われば、ファイルをアンロックすると言う意味です。

  #// 排他解除
    unfilelock();

  ・以上を test8.cgi として修正すると、次のようになります。

#!/usr/local/bin/perl
#//=====================================================================
#//  test8.cgi 簡易伝言板
#//=====================================================================

#// 準備
  $title   = '簡易伝言板';
  $callcgi = 'test8.cgi';

#// 伝言ファイル
  $msdnm   = './';
  $msfnm   = 'dengon.txt';
  $msmax   = 10;

#// クッキー
  $cknm    = 'dengon';

#// パラメタ取得
  *data    = GetPara();
  $md      = $data{'md'};
  $hd      = $data{'hd'};
  $ms      = $data{'ms'};

#// セキュリティ情報取得
  $ip      = $ENV{'REMOTE_ADDR'};
  $blws    = $ENV{'HTTP_USER_AGENT'};

#// クッキー取得
  if ($ENV{'HTTP_COOKIE'}) {
    *data = GetCookie($ENV{'HTTP_COOKIE'});
    $data{$cknm} =~ s/%20/ /g;
    @w = split(/\,/, $data{$cknm});
    $hdc = @w[0];
  }

#// 伝言ファイル読み込み
  $err = 0;
  open(FIL, "$msdnm$msfnm") or $err = 1;
  if ($err) {
    DspMsg("ファイル読み込みオープンエラー ($msdnm$msfnm)");
    exit(0);
  }
  @rec = <FIL>;
  close(FIL);

#// 投稿処理開始
  if ($md eq '1') {

  #// データチェック
    $hd =~ s/ / /g;
    $hd =~ s/</&lt;/g;
    $hd =~ s/>/&gt;/g;
    $hd =~ s/^\s+//;
    $hd =~ s/\s+$//;
    $ms =~ s/ / /g;
    $ms =~ s/</&lt;/g;
    $ms =~ s/>/&gt;/g;
    $ms =~ s/^\s+//;
    $ms =~ s/\s+$//;
    if ($hd eq '') {
      DspMsg("ハンドルが空白です。");
      exit(0);
    }
    if ($ms eq '') {
      DspMsg("メッセージが空白です。");
      exit(0);
    }

  #// クッキーデータセット
    $hdc = $hd;

  #// 伝言編集
    ($ss, $mn, $hh, $dd, $mm, $yy, $yb) = localtime(time);
    $yy += 1900;
    $mm++;
    $dt = sprintf("%04d.%02d.%02d %02d:%02d:%02d", $yy, $mm, $dd, $hh, $mn, $ss);
    $rec = $dt . '_~|~_' . $hd . '_~|~_' . $ms . '_~|~_' . $ip . '_~|~_' . $blws . "\n";

  #// 排他開始
    if (!filelock()) {
      DspMsg("他のユーザと投稿が重なりました。<BR>しばらくしてから投稿して下さい。");
      exit(0);
    }

  #// 伝言ファイル書き込み
    $err = 0;
    open(FIL, ">$msdnm$msfnm") or $err = 1;
    if ($err) {
      unfilelock();
      DspMsg("ファイル書き込みオープンエラー ($msdnm$msfnm)");
      exit(0);
    }
    unshift @rec, $rec;
    for ($i = $#rec; $i >= $msmax; $i--) {
      pop @rec;
    }
    print FIL @rec;
    close(FIL);

  #// 排他解除
    unfilelock();

#// 投稿処理終了
  }

#// クッキーセット
  $hdc =~ s/\,/%2C/g;
  $cooks = "$hdc\,";
  $cooks =~ s/ /%20/g;
  $cooks =~ s/;//g;
  ($secg, $ming, $hourg, $mdayg, $mong, $yearg, $wdayg) = gmtime(time + 5184000);
  @mons = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  @week = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
  $dt= sprintf("%s\, %02d-%s-%04d %02d:%02d:%02d GMT", $week[$wdayg], $mdayg, $mons[$mong], $yearg+1900, $hourg, $ming, $secg);
  print "Content-type: text/html\n";
  print "Set-Cookie: $cknm=$cooks; expires=$dt;\n\n";

#// HTML ヘッド表示
print << "END_OF_HTML";
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>$title</TITLE>
</HEAD>
<BODY>
<CENTER>
<P><B>◆ 簡易伝言板 ◆</B></P>
<FORM action="$callcgi" id=FORM1 method=post name=FORM1>
<INPUT TYPE="hidden" NAME="md" VALUE="1">
<TABLE bgcolor=#b0b0b0 border=0 cellPadding=3 cellSpacing=2>
  <TBODY>
    <TR bgcolor=#f0f0f0>
        <TD nowrap><FONT size=-1>ハンドル</FONT></TD>
        <TD nowrap><INPUT id=hd name=hd size="22" maxlength="40" value="$hdc"></TD>
    </TR>
    <TR bgcolor=#f0f0f0>
        <TD nowrap><FONT size=-1>メッセージ</FONT></TD>
        <TD nowrap><INPUT id=ms name=ms size="62" maxlength="200"></TD>
    </TR>
  </TBODY>
</TABLE>
<BR>
<TABLE bgcolor=#b0b0b0 border=0 cellPadding=3 cellSpacing=2>
  <TBODY>
    <TR bgcolor=#f0f0f0>
        <TD nowrap><INPUT id=submit1 name=submit1 type=submit value=" 投稿 "></TD>
        <TD nowrap><INPUT id=reset1 name=reset1 type=reset value="リセット"></TD>
    </TR>
  </TBODY>
</TABLE>
</FORM>
END_OF_HTML

#// 伝言ファイルヘッド表示
print << "END_OF_HTML";
<TABLE bgcolor=#b0b0b0 border=0 cellPadding=3 cellSpacing=2>
  <TBODY>
    <TR bgcolor=#d0d0d0>
        <TD nowrap><FONT size=-1>日付</FONT</TD>
        <TD nowrap><FONT size=-1>ハンドル</FONT</TD>
        <TD><FONT size=-1>メッセージ</FONT</TD>
    </TR>
END_OF_HTML

#// 伝言ファイルボディ表示
  foreach (@rec) {
    @w = split(/\_\~\|\~\_/, $_);
    $dt = substr(@w[0], 0, 16);
    $hd = @w[1];
    $ms = @w[2];
    $ip = @w[3];
    $blws = @w[4];
    print '<TR bgcolor=#f0f0f0>';
    print '<TD nowrap><FONT size=-1>', $dt, '</FONT</TD>';
    print '<TD nowrap><FONT size=-1>', $hd, '</FONT</TD>';
    #print '<TD><FONT size=-1>', $ms, '</FONT</TD>';
    print '<TD><FONT size=-1>', $ms, "<BR>$ip $blws", '</FONT</TD>';
    print '</TR>';
  }

#// 伝言ファイルテイル表示
print << "END_OF_HTML";
  </TBODY>
</TABLE>
END_OF_HTML

#// HTML テイル表示
print << "END_OF_HTML";
</CENTER>
</BODY>
</HTML>
END_OF_HTML

#// 終了
  exit(0);

#//=====================================================================
#//  メッセージ表示
#//=====================================================================
sub DspMsg
{
  my($msg) = @_[0];

#// メッセージ表示
print << "END_OF_HTML";
Content-type: text/html

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>$title</TITLE>
</HEAD>
<BODY>
<P>$msg</P>
</BODY>
</HTML>
END_OF_HTML

#// リターン
  return;
}

#//=====================================================================
#//  パラメタ取得
#//=====================================================================
sub GetPara
{
  my($encode) = @_;
  my($method) = $ENV{'REQUEST_METHOD'};
  local($qu, @in, $key, $val);

#// 日本語コード変換 jcode.pl 読み込み
  require 'jcode.pl' if $encode;

#// パラメタ取得
  if ($method eq 'GET') {
    $qu = $ENV{'QUERY_STRING'};
  }
  elsif ($method eq 'POST') {
    read(STDIN, $qu, $ENV{'CONTENT_LENGTH'});
  }
  local(@qu) = split(/&/, $qu);
  foreach (@qu) {
    tr/+/ /;
    ($key, $val) = split(/=/);
    $key =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c', hex($1))/ge;
    $val =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c', hex($1))/ge;
    $val =~ s/\r\n/\n/g;
    jcode'convert(*key, $encode) if ($encode);
    jcode'convert(*val, $encode) if ($encode);
    $in{$key} = $val;
  }

#// 戻り値セット
  return *in;
}

#//=====================================================================
#//  クッキー取得
#//=====================================================================
sub GetCookie
{
  local($cookie) = $ENV{'HTTP_COOKIE'};
  local(*data, @cookie, $key, $val);

#// クッキー取得
  @cookie = split(/ /, $cookie);
  foreach (@cookie) {
    ($key, $val) = split(/=/);
    $data{$key} = $val;
  }

#// 戻り値セット
  return *data;
}

#//=====================================================================
#//  ファイルロック
#//=====================================================================
sub filelock
{
  my($ct);

#// ファイルロック
  for ($ct = 1; $ct <= 5; $ct++) {
    if (-e "filelock.dat") {
      sleep 1;
    }
    else {
      if (open(FILE, ">filelock.dat")) {
        close(FILE);
        return 1;
      }
    }
  }

#// 強制アンロック
  unfilelock();

#// 戻り値セット
  return 0;
}

#//=====================================================================
#//  ファイルアンロック
#//=====================================================================
sub unfilelock
{
  my($ct);

#// ファイルアンロック
  for ($ct = 1; $ct <= 5; $ct++) {
    if (unlink "filelock.dat") {
      return 1;
    }
    sleep 1;
  }

#// 戻り値セット
  return 0;
}

■CGI-Perl 応用実地編資料
■CGI-Perl 基礎編資料
■CGI-Perl 基礎実地編資料
■PHP 基礎編資料
■PHP + MySQL 編資料
| VB | SQL | Perl | PHP | ASP | JSP | C | PC | HP | FAQ | フリーソフト |

SAK Streets メインストリート 図書館・カフェ 一覧 SAK Streets 検索



Copyright (C) 1999-2017 Y.SAK