忍者ブログ
え?全力で生きているかって? ――答えはYesだ。当たり前だろそれが人生なんだよ!!
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

正直ちょっと飽きてきた。。。最近やってねぇ。



とりあえず誰も必要としてないソース公開。メールサーバーの記事書いてる時にはできてたけど、ちょっと動作に難ありで放置してた。だが修正するのも面倒になったからもうとりあえずこのまま公開。



…まぁ、チャットとかそういうのってそもそもMineCraftのシステム自体に用意されてるから、いちいちマシンを介してやる必要もないと思うが、それを言っちゃぁすべておしまいだからね。



動作だけど、もう簡単。打った文字をrednet.broadcast()で配信しまくってrednet.receive()で受け取りまくるだけ。

ここで、rednet.broadcast()の説明をちょっと。

これは、receive()状態にある周囲のマシン全部にメッセージを送信するメソッド。receive()状態じゃないともちろん受信はできない。

ここでちょっと疑問が湧くよね。receive()状態の時ってなんもできなくなるんちゃうん?って。いつメッセージ送れるんだよって話になる。

普通にやるとそうなるわけだが、今回はメッセージ送るメソッドとメッセージ受信するメソッドを並列に動作させてやることで、メッセージのやりとりがskypeみたいにできるようになっている。ワイルドだろぉ〜?



まぁとりあえずソースを晒そうか。

今回の名前はminechatでいく。以下ソース



MY_ID=os.getComputerID()
--os.getComputerID()で、使用しているマシンのID取得が可能
 
print("------------------------------")
print("  MineChat  created by kim")
print("------------------------------\n")
 
print("exit : type exit()\n")
 
print("press enter...")
str=read()
 
for i=1, 30 do
    print("\n")
end
--ここのfor文はあとで解説①
 
--cursor set
--これも後で説明②
cx,cy=term.getCursorPos()
 
--login send
--minechat起動してる人全員に「俺ログインしたぜ」って言うだけ
rednet.broadcast("#"..MY_ID.."is Online!")

 
--send message
--メッセージ送信メソッド
sendmsg=function()
    while true do
        --メッセージ入力待ち
        write("me> ")
        msg=read()
 
        if msg=="exit()" then
           --exit()って入力された時はminechat終了
           rednet.broadcast("#"..MY_ID.."is Offline...")
           return 
        else
           --それ以外だったらメッセージ送信
           rednet.broadcast(msg)
        end
    end
end
 
--get message
--メッセージ受信メソッド
getmsg=function()
    while true do
        --waiting message
        local event,id,msg=os.pullEvent("rednet_message")
 
        --message show
        term.setCursorPos(cx,cy)
        --カーソルの位置指定。後で説明③
        write(id.."> "..msg.."\nme> ")
        --受信したメッセージ表示
    end
end
 
parallel.waitForAny(sendmsg,getmsg)
--これで並列実行できる。後で説明④



ソース以上



後で説明することが多くなってしまっためんどくさい

まず①と②の部分。謎の改行。

このターミナルでは、表示する時のカーソル(以下表示カーソル)と入力する時のカーソル(以下入力カーソル)の位置がイコールじゃないらしい。

どういうことかを具体的に説明するのが難しいのだが、例えば


1 me> |
2
3
4

の状態で入力待機してたとする。左の数字は行数で | がターミナルに表示されてるカーソル。

今の状態で入力カーソルが | と同じ位置。で、文字入力してenter押したとする。

1 me> hello!
2 me> |
3
4

するとこんな感じ。この状態で24番からメッセージ受信したときに、

1 me> hello!
2 24> hi
3 me> |
4

ってなってほしいけど、実際は、

1 24> hillo!
2 me> |
3
4

みたいに、自分が打った文字の上に上書きされちゃう。これは、表示カーソルが1行目1文字目の位置にあるから、そこから相手のメッセージを表示しちゃうみたい。

その逆に、受信したメッセージの上に自分が上書きしちゃう時もある。



こういうのを回避するために、一回画面下まで全部改行してやって、画面左下に表示カーソルと入力カーソルを持って行ってやった。それが①の部分。

この状態でカーソルの位置取得。②の部分がそれ。



改行してからやるとこうなる。

仮にターミナルが20行のものだったとして、

16
17
18
19
20 me> |

で、文字入力してenterすると、

16
17
18
19 me> hello!
20 me> |

ってなる。メッセージ受信すると、カーソルの位置を設定してからメッセージを書き出す。さっき②で取得したカーソルの位置を③の書き方で指定。この例でいくと20行目1文字目から文字を表示していく。つまり20行目の m の部分に表示カーソルが行く。 

この状態で、相手ID> 相手メッセージ を書いてやると、20行目のme> の部分も上書きされて

16
17
18
19 me> hello!
20 24> hi

で、さらに \nme> を書かせてから入力待ち状態を作ると、

16
17
18 me> hello!
19 24> hi
20 me> |

ってなる。これなら問題ないよね。



で、今回一番重要なのが④の存在。これで、受信メソッドと送信メソッドの並列実行を行なっている。

ちなみに、parallel.waitForAny()とparallel.waitForAll()の2種類がある。

Any()のほうは、どれか一つでもメソッドがreturnしたら動作終了

All()のほうは、すべてのメソッドがreturnしたら動作終了

という感じ。

今回は、入力メソッド側がexit()って打った場合のみ終了したいわけだから、Any()のほうを使ってる。

動作ごとにメソッド用意してこれ書くだけで簡単に並列実行できるのは素晴らしいですね。





で、ここで一応今回の問題点を言っておく。

冒頭にも言ったとおり、今回はbroadcast()を使ってるから、周囲でreceive()状態になってるマシンすべてにメッセージが行く。

それはreceive()してるマシンは例外なく届くわけで、前回作ったメールサーバーにもメッセージが行くわけだ。

つまり、このプログラム使うときははメールサーバーがない環境かもしくはメールサーバーとは別の場所じゃないとまずい。全メッセージをメールサーバーが受信しちゃって超めんどくさい。逆に会話ログは取れるけどねw



だから、メールサーバーに干渉しないものを作ろうとすると、やっぱりsend()を使ったやり方じゃないと無理っぽいかな。でもsend()は送る相手一人だけだから、サーバーを一つ用意して、ログイン管理とかした上でメッセージをそれぞれ良い感じにみんなに配布していくようなプログラムを書くしか無いかな。



ってなことを考えてめんどくさくなってやめたわけ。



まぁそこまで大したプログラムにはならないと思うので、気が向いたらまたやります。



以上。
 


PR
1/31 11:58 ソース修正(getモード時の動作修正)



メールクライアント(メール送受信ソフト)をメインマシンで作るよ。

サーバー設置編読んでなければ先に読んだほうがいいです→サーバー設置編



ソース書く前に、サーバーの動作を再確認。


メールをずっと待ってるサーバーたん
  ↓
メールきた!
  ↓
メールの内容で動作が変わるよ

・メッセージの内容が「get()」やったら
 →ログ(未読メッセージ)をメインマシンに送る(レッドストーントーチ ジュワ...消)
・メッセージの内容が「getall()」やったら
 →全ログ(メッセージ一覧)をメインマシンに送る
・それ以外(hi とか hello とか)やったら
 →ログに書き溜める(レッドストーントーチ  ピッカーン 光)

  ↓
またメールを待つサーバーたん



っていう動作ね。

この中の、メッセージ作って送ったり、get()とかgetall()とか送る部分を今回作るのね。


つまり流れで言うと、

クライアント起動
  ↓
モードが3つ

・メール送信
・get()って送って未読メール受信&表示
・getall()って送ってメール一覧受信&表示

  ↓
終了



じゃぁソースいくで。

今回の環境

・メインマシン→アドバンスドPC。ID=27
・メール受信サーバー→普通のPC。ID=28

再確認だが、コメントは -- (ハイフン2つ)な。

今回のプログラム名はmailでいく。edit mailでエディタ起動。以下ソース




SERVER_ID=28  --サーバーID。28
SERVER_ID=tonumber(SERVER_ID)  --説明省略
 
print("mode?")
write("\tsend,get,getall >")
--モード一覧。send,get,getall
--サーバー設置編でやったアレの通り
--あと、printとwriteの違いだが、
--自動的に改行してくれるかしてくれないかの違い。
--writeは改行してくれない
 
mode=read()
--どのモードかキーボードから読み込み
 
if mode=="send" then
  --send mode
  --メール送信モード
  --正直言うとswitch文で書きたかったけど
  --書式調べんのめんどくさかったからif文で失礼
  
  write("send for? >")
  --受信相手のidを入力
  local id=read()
  --read()ってやることでキーボードから入力できる
  local id=tonumber(id)
  --local ってやつは気にしなくていい。
  --知りたかったら「lua ローカル変数」とかでググって
  
  write("message...>")  
  --メッセージ入力
  local msg=read()
 
  --send message
  --ここで送信
  rednet.send(id,msg)
 
elseif mode=="get" then
  --get mode
  --logファイルを取得する=未読メッセージ受信
  rednet.send(SERVER_ID,"get()")
  --get()ってメッセージをサーバーに送ってやることで、
  --サーバーのlogファイルを取得できるようになる

  --get message(timeout 30sec)
  --タイムアウトの設定を追記
  --30秒たったら受信やめる
  print("message getting...")
  local id,msg=rednet.receive(30)
  
  --show message
  print("\n"..msg)
  --受信したlogファイルを表示
 
  rednet.send(SERVER_ID,"stop()")
  --サーバーはずっとログファイルを送信し続けるから、
  --stop()ってメッセージ送って送信をやめてもらう
 
elseif mode=="getall" then
  --getall mode
  --alllogファイル取得=メッセージ一覧取得
  
  rednet.send(SERVER_ID,"getall()")  --上と同じ
  local event,id,msg=os.pullEvent("rednet_message")
 
  --show message
  print("\n"..msg)
 
  rednet.send(SERVER_ID,"stop()")
 
else
  --print usage
  --使い方説明というか説明になってないけど
  print("mode:")
  print("\tsend:send mail")
  print("\tget:get mail")
  print("\tgetall:get all mail")
  return
end
  
 

ソースコード以上


こっちもソース間違ってるかもしれんからその時はコメントお願いします。



動作イメージはこんな感じ。

まずはsendモード。俺宛のメールは28固定。


2013-01-31_09.18.48.png







次getモード。ちなみに24はメインでもサーバーでもない第3のマシンのID


2013-01-31_09.19.05.png







次getallモード。過去のテストがずらり。


2013-01-31_09.38.33.png






とりあえず以上。

ちなみに、sendで受信サーバー以外にも送れるけど、向こうが受信するかどうかは向こうの状態次第ってやつですね。例えば、第3のマシン(24)からメインマシン(27)にsendで送ろうとすると、メインがrednet.receive()状態じゃないとだめって例のアレですね。



んで、これ書いてる時に、メール無いときにgetモード発動させた時の動作が無いことに気づいたからそのうち修正かけます。でも今の状態でも動作はします。 ←修正済



次、これの応用みたいな感じでチャットツールも作ったからそのうちソース晒します。




謝辞

サーバー用プログラム、クライアントプログラム開発の際、errorcode氏の助言を参考に致しました。ありがとうございました。



以上。


1/31 11:47 ソース修正(if msg=="get()" then 付近からファイルチェック追記)



ということで、メール受信サーバー作ったからソース晒す。

一応このへんとかこのへんとか見ればAPIとか分かりますんで。

基本lua言語ですよ。大丈夫や俺だってこれが初めてなんやから。



ということでソース。の前にいくつかお話。



1つ目。モデムの設定から。


まぁ普通にモデム作るじゃん?shift押しながらマシンに貼っつけるじゃん?でもこれだけじゃぁだめなんだな。律儀にモデムをONにしてやらなきゃならんのだよ。なかなかこだわっとるわ。



ということで、ルートディレクトリにstartupってのを作って、マシン起動時にモデムONになるように設定してやる。startupってのを作ることで、マシン起動したときにそのファイルに書かれたことを実行するようになる。

romディレクトリの中にもあるけどそれとちゃうで!一緒かもしれんけどそれはいじらんほうがいい。ちゃんとルートに作れよ!

cd /
edit startup

って打つとエディタ起動


起動したら

rednet.open("モデムつけた場所")

って書く。モデムつけた場所は、パソコン正面から見て

右:right
左:left
上:top
背面:back

て書けばおk。俺は背面につけたから、rednet.open("back") って書いた。
これもwikiに書いてあるからちゃんと読みましょう。

書いたら、ctrl押してsave、ctrl押してexitでエディタ終了させる。

これで、パソコン起動したらモデムがONになるよ。

試しに、reboot って打ってみ。起動してからモデム見たら赤く光っとるで。完璧やん。



ちなみにrednet.open()だが、プログラム書くときに一番上に書いといてもおk。というか、ソース共有するときだったら書いたほうがいいのかもしれない。が、俺はもうモデムONがデフォという考え方でいく。


これを、サーバー機とメインマシンとどっちもやっとく。


そしてもう一つ。idって打ってどっちもマシンのIDを調べとくこと。メッセージ送信するときに必要。



2つ目。rednetのお話をちょっと。

今回ネックになってるのが、rednet.send()とrednet.receive()の部分。なぜかというと、相手がreceive()で待ち構えている時じゃないとsend()したメッセージが届かないから。

しかしずっとreceive()で待機なんかするわけないやん。作業でけへんもんな。どうしよか。じゃぁ代わりにサーバーたんに受信頼もうよってのが今回の趣旨。

つまり、メッセージを一回サーバーで受信してもらって、それを転送してもらうっていう動作をする。

…面倒ですよね。でもこれでメインマシンで何してても常にメール受信できるんです!何通でもどうぞwwwって感じ。



3つ目。今回のマシン周りの設置

こんな感じ。2つPCあるけど受信サーバーは左側のPC。右側のPCはまだ関係ない。


2013-01-31_08.19.58.png






メール受信したらレッドストーントーチが光るようにしたった


2013-01-31_08.20.49.png







プログラム起動したらこんなかんじでメール受信待機


2013-01-31_08.21.20.png







メッセージ受信したら一応メッセージ表示される。
裏でlogにメッセージ書き貯めていって、また待ち始める。


2013-01-31_08.21.06.png







メインマシンから要求がきたら、今までサーバーが受信したメールを全部転送し始める。



今回作ったプログラムの動作イメージを書くとこんな感じ。



メールをずっと待ってるサーバーたん
  ↓
メールきた!
  ↓
メールの内容で動作が変わるよ

・メッセージの内容が「get()」やったら
 →ログ(未読メッセージ)をメインマシンに送る(レッドストーントーチ ジュワ...消)
・メッセージの内容が「getall()」やったら
 →全ログ(メッセージ一覧)をメインマシンに送る
・それ以外(hi とか hello とか)やったら
 →ログに書き溜める(レッドストーントーチ ピッカーン 光)

  ↓
またメールを待つサーバーたん



という感じ。



じゃぁソース晒そうか。面倒くさがりな俺だがたまにはコメントつけて丁寧に解説しよう

今回の環境を書いとくと、

・メインマシン→アドバンスドPC。ID=27
・メール受信サーバー→普通のPC。ID=28

あと、

・コメントは -- (ハイフン2つ)の部分。luaの仕様。
・日本語のコメントは書けない。書けても書くな。
・英語のコメントは実際に俺も書いた。書かなくてもいいがこれぐらい書いといたほうが後で多少捗るかもね
・「受信ランプ」ってコメントがあるが、受信した時にレッドストーントーチを光らせるように設定したかったらそれ書いて。いらなければ消すかコメントアウトでよろしく。細かい説明は後でする。


今回のプログラム名はpop_serverでいく。ここは好きに変えてもらっておk。

じゃぁ始める。edit pop_server って打ってエディタ起動。以下ソースコード


print("pop server #28\n")
--print()で文字の出力ができる。
--#28ってのはサーバーIDが28だったから
--\nは改行。
 
SEND_ID=27  --メインマシンのID。今回は27
SEND_ID=tonumber(SEND_ID)  --説明省略
 
---------------
--file writer
---------------
--ファイル書き込みする関数を作る
filewriter=function(filename,str)
  --file open
  file=io.open(filename,"a")  --ファイルを追記モードでオープン
 
  --writing
  if file then
    file:write(str)  --ファイルに書き込み
  else
    print("Cannot writing!")  --エラー処理は忘れずに☆
  end
 
  --file close
  file:close()  --ファイルクローズは基本ですよ
 
end
 
--sign off
rs.setOutput("left",true)  --受信ランプ off
 
 
----------
--main
----------
while true do  --無限ループ
  --message get wait
  print("waiting...")  --メッセージ受信待機のとき表示
 
  event,id,msg=os.pullEvent("rednet_message")
  --os.pullEvent("rednet_message")
  --rednet.receive()と見かけの動作は同じ
  --なんでreceive()じゃないかって?大人の事情だよ
  
  print("--------------------")
  print("id:"..id)
  print("msg:"..msg)
  print("--------------------")
  --上の4行は書かなくてもいい。
  --受信したメッセージが見れるだけ
  
  b=false  --(ry
 
  if msg=="get()" then
    --log forwarding
    --今回は未読のメッセージをlogとして保存していく
    --未読メッセージの送信を行う部分

    if fs.exists("/log") then
      --未読メッセージがある場合
    
      --log message load
      logfile=io.open("/log","r")  --/logを読み込みモードでオープン
      logmsg=logfile:read("*a")  --*aでファイル上から下まで読み込み
 
      --message send
      while b==false do  --bがfalseの状態なら無限ループ
        print("sending...")  --送信中表示
        rednet.send(SEND_ID,logmsg)
        --rednet.send(相手ID,メッセージ)でメッセージ送信
        --ここではメインマシンに未読メッセージ送信
      
        id,msg=rednet.receive(2)
        --2秒間だけメッセージ受信
        --ここで「stop()」ってメッセージが来たら送信をやめる
        if msg=="stop()" then
          b=true  --bがtrueになったからループ終了
        end
      end
    else
      --未読メッセージがない場合
      while b==false do
        print("sending...")
        rednet.send(SEND_ID,"Mail is empty.")

        id,msg=rednet.receive(2)

        if msg=="stop()" then
          b=true
        end
      end
    end
 
    --sign off
    rs.setOutput("let",true)  --受信ランプ off
 
    --logfile delete
    fs.delete("/log")
    --ログファイル削除
    --ここで削除することで未読メッセージだけ貯められる
  
  elseif msg=="getall()" then
    --alllog forwarding
    --今度は今まで受信したすべてのメッセージを返す
    --動作自体はさっきの部分と同じ
 
    --alllog message load
    alllogfile=io.open("/alllog","r") 
    alllogmsg=alllogfile:read("*a")
 
    --message send
    while b==false do
      print("sending...")
      rednet.send(SEND_ID,alllogmsg)
      id,msg=rednet.receive(2)
 
      if msg=="stop()" then
b=true
      end
    end
 
    --今回はランプの動作はしないんだよ
    --ログも消す必要ナッシング
 
  else
    --write message to log
    --ログファイルに受信したメッセージを書き込む
    
    --sign on
    rs.setOutput("left",false)  --受信ランプ on
 
    --get minecraft day&time
    --マイクラ内時間の取得。それっぽくね
    time=os.time()  --時間取得
    time=textutils.formatTime(time,flase)  --書式設定
    day=os.day()  --日にち取得。過ごした日数
 
    --write message to file
    --ここで書き込む書式を決めてやってから書き込む
    str_date="date:"..day..","..time.."\n"
    str_msg="id:"..id.."\nmessage:"..msg.."\n"
    str_line="----------------------------\n"
 
    --writing!
    --最初に作った関数を使って書き込む
    --まずはlogのほうから
    filewriter("/log",str_date)
    filewriter("/log",str_msg)
    filewriter("/log",str_line)
 
    --alllogにも書き込み
    filewriter("/alllog",str_date)
    filewriter("/alllog",str_msg)
    filewriter("/alllog",str_line)
 
  end
end
 


ソースコード以上。

もし間違いがあったら遠慮なくコメント欄へ「ここ間違ってるわハゲ!」ってコメントください。
もしそっちの間違いだったらこっちから「間違ってへんわハゲ!」って返すけど。



後回しになったレッドストーントーチのお話な。

rs.setOutput("出力方向",true or false)でレッドストーン回路の出力ができる。

今回、レッドストーン回路がサーバー機の左側にあるから、出力方向はleft。

で、trueとfalseだが、単純にonとoff。このへんはレッドストーントーチの特性みたいなもんを使ったアレなわけだが、スイッチとか使って一応説明しとく。


rs.setOutput("left",true)の状態がこれ


2013-01-31_08.29.54.png






で、rs.setOutput("left",false)の状態がこれ


2013-01-31_08.29.46.png







スイッチがパソコンに変わっただけ。ということ。



で、最後。起動したときにこのプログラムを立ち上げるように設定したかったら設定するといい。

最初に編集したstartupに以下を追加

shell.run("pop_server")

これでマシンが起動したら自動的にプログラムが動作してメール受信待ち状態になる。



動作確認の方法だが、メインマシンで lua て打つとluaが立ち上がる。
ここで、コマンドをいくつか打つと確認ができる。

今回はサーバーIDが28だからそれで行くけどそのへん臨機応変に。

・メッセージ送信
  rednet.send(28,"test")

・未読メッセージ受信
  rednet.send(28,"get()")
  rednet.receive()
  rednet.send(28,"stop()")

・全メッセージ受信
  rednet.send(28,"get()")
  rednet.receive()
  rednet.send(28,"stop()")



ということでメール受信サーバー設置編は以上。

このままではまだ不便ということで、次はメインマシンで使うメールクライアントを作るよ。
 
メール受信サーバー作ったった(クライアント作成編)へ続く


カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
カウンター
プロフィール
HN:
Mugen
性別:
男性
職業:
社会人だよバカヤローー!!
趣味:
ピアノ、アニメ・音楽鑑賞、ネットサーフィンとかとか
自己紹介:
ただの車好きな変態です。

twitterもやってますんで、よければリンクからドゾー
Twitter




最新CM
[01/14 FreddieSpemo]
[01/13 FreddieSpemo]
[01/13 FreddieSpemo]
[12/21 réplique cartier bracelet]
[12/16 van cleef and arpels alhambra diamond necklace knock off]
バーコード
最新TB
ブログ内検索
アクセス解析
忍者ブログ [PR]