Javaでソケット通信

ソケット通信するサンプル作ってみた。

サーバ


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class WordManageServer {

private static final int PORT = 8888;

public static void main(String[] args) {

ServerSocket serverSocket = null;
Socket socket = null;

try {

// サーバーソケットの生成
serverSocket = new ServerSocket(PORT);

for (;;) {

System.out.println("wait request.");
socket = serverSocket.accept();
System.out.println("accept [" + socket.getInetAddress() + "]");

// スレッド起動
new WordManagerThread(socket);
}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

serverSocket.close();

} catch (IOException e) {

e.printStackTrace();
}
}
}
}

class WordManagerThread extends Thread {

private Socket socket = null;

public WordManagerThread(Socket socket) {

this.socket = socket;
this.start();
}

public void run() {

PrintWriter out = null;
BufferedReader in = null;

try {

// 出力ストリームを取得
out = new PrintWriter(socket.getOutputStream(), true);

InputStream inputStream = socket.getInputStream();
byte[] func = new byte[4];
byte[] dt = new byte[4];

inputStream.read(func);
int val1 = ((func[3] & 0xff) << 24) | ((func[2] & 0xff) << 16)
| ((func[1] & 0xff) << 8) | (func[0] & 0xff);

inputStream.read(dt);
int val2 = ((dt[3] & 0xff) << 24) | ((dt[2] & 0xff) << 16)
| ((dt[1] & 0xff) << 8) | (dt[0] & 0xff);

System.out.println(val1);
System.out.println(val2);

} catch (IOException e) {

e.printStackTrace();

} finally {

close(socket, out, in);
}
}

/**
* @param socket
* @param out
* @param in
*/
private static void close(Socket socket, PrintWriter out, BufferedReader in) {

if (out != null) {

out.close();
}

if (in != null) {

try {

in.close();

} catch (IOException e) {

e.printStackTrace();
}
}

if (socket != null) {

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();
}
}
}
}


クライアント


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class TestClient {

public static void main(String[] args) {

try {

Socket socket = new Socket("localhost", 8888);
BufferedReader in = new BufferedReader(new InputStreamReader(socket
.getInputStream()));

OutputStream os = socket.getOutputStream();
os.write((int)2);
os.write((int)3);
os.flush();

System.out.println(in.readLine());

// 入出力ストリームを閉じる
os.close();
in.close();
socket.close();

} catch (IOException e) {

e.printStackTrace();
}
}
}

javaのプロセスのメモリ状態とかを見る

jconsole(jdk1.5以上)が標準でインストールされるとのこと。


java -Dcom.sun.management.jmxremote 実行クラス

<jdk install>/bin/jconsole


とすると、jconsoleの接続リストの中に自分が実行したプロセスがいる。
それを選択すると、ヒープの状態とか、その他もろもろみれる。
何かメモリリークしちゃってたので、どこでメモリ使いまくってるか検出できた。
普段あんまり使わないと思うけど、いざという時に便利です。

JavaBeansのプロパティの値を全て表示する

エンティティとかDTOのプロパティを全部ログ出力したい場合等に有効かと。


public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE);
}



↑のtoStringを実行すると
jp.co.hoge.entity.hogeDto@f01a1e[prop1=<値1>,prop2=<値2>]


のように出力してくれます。
出力形式はToStringStyleが用意してくれてるので、好みの出力形式に変更可能!

Teedaのforeach

S2JSFあがりの私にはとても違和感があったのと、ちゃんとドキュメント読まないとはまる気がするのでメモ。

Pageクラスに

public List<HogeDto> hogeDtoItems;

があり、Dtoが


public class HogeDto {

private String aaa;
private String bbb;

// getter、setterは省略
}


のようになっていた場合、PageクラスにDtoと同じプロパティ名

public String aaa;
public String bbb;

を持ってないとforeach使ってレンダリングされません。
ダセー、と思ってteeda修正しようと思いましたが、TForEachクラスだけでは修正がとどまらない感じだったので断念・・・
htmlとpageが1対1ってのは分かりますが、PageクラスにDtoと同じプロパティを更新可能な状態(public にするか getter を用意する)でもってなきゃレンダリングされないってのはイケてないと思う。

例外時のページ

http://d.hatena.ne.jp/shot6/20070425

ん~、ためになります。
ISIDの方ってほんとにスバラシイ。

どうなんだろう・・・

http://d.hatena.ne.jp/himazublog/20070408/1175994799

結果がついてきてるからすごいなぁ、と素直に思うけど、かなり綱渡りな気がする。
まぁ、それなりのリスクはしょうがないのかな。
勉強になります。m(_ _)m

Teedaでセッション情報削除

なんと、


public class LogoutPage {

private LoginDto loginDto;

@RemoveSession(name={ "loginDto" })
public Class prerender() {

loginDto.logout();
return null;
}

public void setLoginDto(LoginDto loginDto) {

this.loginDto = loginDto;
}
}


ってやれば、@RemoveSession で指定したloginDtoがセッションから消えるらしい。
ん~、すばらしい。

ついでに、セッションに持たせるには、

@Component(instance = InstanceType.SESSION)

public class Hoge implements Serializable {

って感じで、@Componentアノテーションで、インスタンスはセッションだよ。
ってやるだけでいいみたい。
フレームワークってすごいなぁ。

Teedaで独自のインターセプター

Teedaで独自のインターセプターを作る!

■インターセプターを作る!

<root package>.intercepterにAbstractInterceptorを継承したクラスを作成

■作ったインターセプターをPageクラスに適用する!

<component name="pageCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
<initMethod name="addAspectCustomizer">
<arg>"hogeLogManagerInterceptor"</arg>
<arg>"do.*, initialize, prerender"</arg>
</initMethod>
</component>

こんな感じでいけるらしい。
仕事が終わったら試してみよ。

試してみた



package jp.interceptor;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;

public class TestInterceptor extends AbstractInterceptor {

@Override
public Object invoke(MethodInvocation arg0) throws Throwable {

System.out.println("aaaaa");
arg0.proceed();
System.out.println("bbbbb");
return null;
}

}


こんな感じでちゃんと動いた!

TeedaでRequest、SessionをDI

ソッコーでみつかりました。

http://d.hatena.ne.jp/shot6/20070202

さすがDIコンテナー!便利すぎる。

Teedaのパッケージ構成

teedaってどんな感じがいじってみました。
ざっくりいじって、そう言えばどんなパッケージ構成がいいんだろう?
って思って調べてみたら、ひがさんのブログに書いてあった。

http://d.hatena.ne.jp/higayasuo/20070130/1170151552

ん~、さすがひがさんです。

でも、Doltengで~Serviceって作成できるけど、↑の説明に出てこない。
ん~、サービスクラスって何なんだろう?
あと、ActioinとPageを分離できるけど、どんな時に分離するんだろう・・

えーい!メーリングリストに質問しちゃえ!

その後

メーリングリストに質問したら、小林様が情報提供してくれた。
その結果、各クラスの役割は以下のような感じにしてteedaを使ってみます。

■Action → do~、initialize 等のメソッドを作る。プレゼンテーション層のロジック
■Page → html id属性をプロパティとする JavaBeans
■Dto → ~Items とかで使用するプレゼンテーション層のDto
■Dxo → プレゼンテーション層とドメイン層のコンバータ
■Logic → ビジネスロジック、ドメインロジック
■Dao → テーブルと1対1になるデータアクセスオブジェクト
■Entity → テーブルと1対1になるエンティティ

これさえ決まれば、パッケージ構成も自然と決まるはず!

InnoDBが無効でもENGINE=InnoDBでテーブル作成できちゃう

InnoDBオプションが無効でもENGINE=InnoDBって指定している
CREATE TABLE スクリプトが成功してしまいます。(汗

参考にさせて頂いたURLは↓。

http://webdba.blogspot.com/2007/10/innodbgatypeinnodb_30.html


MySQLのバージョンが異なるせいか、設定ファイルの書き方が微妙に異なる
ので注意して下さい。

mysql> create table hoge ( col1 varchar(4) ) type=innodb ;
Query OK, 0 rows affected, 2 warnings (0.01 sec)

ただし、よくみると警告が表示されている。
Innodb を有効にして
 alter table test type=innodb ;
することでトランザクションが使えるようになる。

my.conf を変更して有効にする(mysqlの再起動が必要)


#skip-innodb
# Uncomment the following if you are using InnoDB tables
innodb_data_home_dir = C:/xampp/mysql/data/
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = C:/xampp/mysql/data/
innodb_log_arch_dir = C:/xampp/mysql/data/
# You can set .._buffer_pool_size up to 50 - 80 %
# of RAM but beware of setting memory usage too high
innodb_buffer_pool_size=16M
innodb_additional_mem_pool_size=2M
# Set .._log_file_size to 25 % of buffer pool size
innodb_log_file_size=5M
innodb_log_buffer_size=8M
innodb_flush_log_at_trx_commit=1
innodb_lock_wait_timeout=50


その後の調査で・・・

ソースからインストールしている場合、
mysql停止→configureのオプションに--with-innodb→make→make install→mysql起動
でinnodbエンジンが有効になりました。
なんと、my.cnfの修正はいりません。しかもconfigure --helpで--with-innodb
オプション出てこないし。。なんで?

ArrayListを配列に変換する

いつも忘れちゃうからメモ。

Integer[] integerArray = (Integer[])arrayList.toArray(new Integer[]{});

なんで、toArrayっていい感じに配列に変換してくれないんだろ。
toArrayの内部でnewしてくれりゃーいいのにな。

tiddlywiki

ToDoとかちょっとしたメモ書きってテキストで書いてましたが、
そんな勢いで使えそうなツール発見。
http://www.tiddlywiki.org/wiki/TiddlyWiki

プラグインのインストールにちょっと手こずったのでメモ。


  • http://www.tiddlytools.com/を開く。
  • 画面左にある「DownLoad」リンクをクリック。
  • ポップアップみたいのが表示されるので、その中の一番下のリンクをローカルにコピー
    (名前を付けてリンク先を保存)
  • tiddlywikiのempty.htmlを開く。
  • 一番上のimportをクリック。
  • 「参照」ボタンをクリックしてtiddlytoolsからダウンロードしたindex.htmlを選択する
  • 好きなプラグインにチェックしてimportする。
  • リロードする。

シェルだけで排他する

なんちゃって排他ですが、

LOCK_FILE=~/lockfile
if [ -f "$LOCK_FILE" ];
then

echo "exist lock file!"
exit

fi

touch $LOCK_FILE

# シェルの処理

rm $LOCK_FILE

バシバシ実行されるシェルだと排他できてないって話になりますが、1分に1回cronで実行されるシェル。なんかの場合上記のようなスクリプトで十分だと思います。

mysqldumpで一行ずつのINSERT

mysqldump -c --order-by-primary --skip-extended-insert -u<username> -p<password> <dbname> <tablename> > dump.sql

こんな感じで、ダンプしたsqlが一行ずつのINSERTとして出力されます。
BULK INSERTのが早いのは分りますが、1テーブル 1行ってダンプするのがデフォルトってどうなんでしょう?
どっちかっていうと、BULK INSERTがオプションで、1row 1行でダンプするのがデフォルトのがいいと思います。。

InnoDBとMyISAMの検索性能検証

同じテーブル、同じデータでInnoDBとMyISAMの性能を比較してみました。

id : INTEGER
sdate : DATE
edate : DATE
yaverage_gap : TINYINT(3)
tagerage_gap : TINYINT(3)
sort_num : TINYINT(3)
message : TEXT
upd_timestamp : TIMESTAMP
gender : TINYINT(3)

ってテーブルに対して、それぞれのストレージエンジンでSELECT * FROM hoge を100万回実行して時間を計測。
(データ件数は80件)

【InnoDB】
182265msec
183344msec

【MyISAM】
164672msec
169609msec

予測通りMyISAMの方が早いですね。
マスター系のテーブルはMyISAMの方がいいのかなぁ。

MySQLのTIMESTAMP

Cannot convert value '0000-00-00 00:00:00' from column 8 to TIMESTAMP
こんなエラーが発生。

JDBCアダプターの動作がバージョンによって異なるらしく、接続文字列に

&zeroDateTimeBehavior=convertToNul

ってパラメータを追加して解決!

log4jの重複ログ出力

パッケージ毎の出力と<root>の出力で、ログが倍出力されてしまった・・・(汗
でもグーグル先生に聞いたらあっさり解決。


<category name="org.apache" additivity="false">
<priority value="DEBUG" />
<appender-ref ref="CONSOLE" />
</category>

<root>
<level value="INFO" />
<appender-ref ref="CONSOLE" />
</root>



って感じで、additivity="false" とすると、上位のアペンダーを継承しないので、ログが重複して出力されなくなるそうです。
でも、これってデフォルトでいい気がするなぁ。なんでデフォルトfalseじゃないんだろ。アペンダーだから?まぁいいや。

MySQLのチューニングについて調べる

MySQLのチューニングポイントについて調べてみた。
検証してないけど、以下がポイントっぽい。


  • key_bufferのサイズ調整。インデックスをちゃんと使ってるなら有効。
  • InnoDBの場合、MyISAMにしてみる。(可能であれば)
  • 更新系処理でオートコミットにしている場合、オートコミットを無効にする
    更新処理が少ないと意味ないけど。
  • my.cnfの見直し。
  • BULK INSERT(呼び方違ってたらすいません)ができるなら使用する。


他にもいっぱいあるだろうけど、とりあえずこんだけメモっとこ。

MySQLのインストール

※以下の操作はMySQL実行ユーザで行って下さい。



  • mysql-5.1.25-rc-linux-x86_64-glibc23.tar.gz をダウンロードして、任意のディレクトリに配置して展開。

  • 展開されたディレクトリmysql-5.1.25-rc-linux-x86_64-glibc23を好きなディレクトリ名称に変更する。

  • mysqlインストールディレクトリに移動して以下のコマンドを実行。
    ./scripts/mysql_install_db --datadir=<data dir path>
    cp <mysql install dir>/support-files/my-medium.cnf <mysqlinstalldir>/data/my.cnf
    ※my.cnfの元となるファイルはhuge、large等もあるので適宜修正して下さい。

  • mysql ディレクティブの修正
    vi <mysql install dir>/data/my.cnf
    [mysqld]
    user = mysql
    basedir = <mysql install dir>
    datadir = <mysql install dir>/data
    port = 3306
    default-character-set=utf8

    [mysql.server]
    default-character-set=utf8

    [safe_mysqld]
    default-character-set=utf8

    [client]
    default-character-set=utf8

  • 設定が完了したので、MySQLを以下のコマンドで起動してみます。
    ./bin/mysqld_safe --defaults-file=<mysql install dir>/data/my.cnf >> <mysql install dir>/data/mysqld_safe.log 2>&1 &

  • 起動ができたら、ユーザの作成。
    <mysql install dir>/bin/mysql --user=root

    mysql> TRUNCATE TABLE mysql.user;
    mysql> FLUSH PRIVILEGES;
    mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY 'パスワード' WITH GRANT OPTION;
    mysql> CREATE DATABASE hoge DEFAULT CHARACTER SET utf8;
    mysql> GRANT ALL PRIVILEGES ON hoge.* TO hogeuser IDENTIFIED BY 'パスワード';

  • 最後にMySQLの停止です。
    /usr/mysql/bin/mysqladmin shutdown --user=root --password=パスワード

シェルでプロパティファイルの値を取得する

シェルとjavaの両方でDBに接続する必要が出てきた。
場合(環境?)によってはシェルとjavaでの接続定義は別ファイルで設定してもいいと思う。
でも、今回はシェルでjavaで使ってるプロパティファイルの値を取得してみた。


getProp()
{
PROP_FILE_PATH=$1
KEY=$2

# キーとプロパティファイルは必須
if [ -z "$KEY" -o -z "$PROP_FILE_PATH" ];
then

exit -1

fi

# 指定されたキーに対応するデータを取得
# 最後の改行は削除
KEY_VALUE=`grep $PROP_FILE_PATH $KEY | awk 'BEGIN { FS="="} {print $2}' | tr -d '\015\032'`

echo $KEY_VALUE
exit 0
}


'\015\032' ってのは改行コードの削除。
環境に合わせて適宜修正する必要がある。
こんなんでいいのかなぁ。

シェルでクラスパスのjarファイルを収集

シェルであるjavaを実行する際、classpathにjarをずらずら書かなければいけません。
いちいち書くのは面倒なので、以下のような感じでclasspathに指定するjarファイル
の文字列を生成。


getJarList()
{
BASE_DIR_PATH=$1

if [ -z "$BASE_DIR_PATH" ];
then

exit -1

fi

JARLIST=""
for f in `find $BASE_DIR_PATH -name "*.jar" -print`
do
JARLIST="$JARLIST$f:";
done

echo $JARLIST
exit 0
}


ちなみ、外部ファイルの関数を呼び出すようにするには

. 外部ファイルのパス

とすれば、getJarList が呼び出せます。

シェル関数名ってアンスコとかで区切る方が一般的なんですかね?
まぁいいや。

サーブレットで画像をいじる場合の注意点

サーブレットで画像を生成したりする場合、

export CATALINA_OPTS="-server -Xmx256M -Xms128M -Xss256k -Djava.awt.headless=true"

ってな感じで「-Djava.awt.headless=true」が必要です。
(-server とか -Xmx とかは必要ないです。環境に合わせて適宜修正するか削除して下さい)