Java多线程消费者问题

有一个简单的场景:一个Queue,包含很多Task,现在的程序要去Queue中取Task来执行
取的时候是多线程去取的,但是一旦有个线程拿到Queue中的某个Task,其他线程就必须退出

拿到Task的线程根据Task里面的内容,将具体工作(因为一个Task中可能存在多个具体的工作)分发给最终的工作执行线程(ProductionLineA或者ProductionLineB)去执行,工作执行线程执行完一个具体的工作后就把信息更新到Task中去,拿到Task的线程会不断检测当前Task里面的具体工作是否都执行完毕,如果完毕,拿到Task的线程退出,Task也会从队列中销毁

通俗的来讲也就是多线程抢任务,抢到一个任务后,把具体的工作分给多个线程去执行,它还可以回来继续抢其他的任务

注意点
1、抢任务的时候可以有多个线程去抢,但是最终一个任务只能有一个线程拿到,也就是执行任务分发的那段代码只能由某个线程执行一次,否则到后面具体的工作可能被执行多次
所以对已经抢到的任务打标记要只能打一次,不能存在A线程判断的时候标记没有被打上,B线程判断也没有,A打标记,B也打标记,这个到后面肯定就乱了,用double check来实现?
2、工作执行线程需要把执行完的情况更新到Task中去,如果第1点中具体工作分正确了,这里应该能保证不会有多个线程执行同样的具体工作,所以把执行完的情况写回Task的时候不用同步保证也行

目前任务标记用的是Thread,这个是不是不合适?
检测是否执行完毕的代码是单线程的,见WatchDog类,目前还缺超时检测,多线程的检测有没有必要?
锁加的恰不恰当?

多线程的代码确实比较难编写和调试

目前我实现了一个代码,看似没有问题,通过测试,分析日志,发现都还正常

			Lock lock = new ReentrantLock();
			lock.tryLock();
			// critical area start
			if (null != task.getExclusive()
					&& task.getExclusive() != Thread.currentThread()) {
				// 如果有人占了,那么自动退出
				// 这里怎么实现比较好?
				continue;
			} else {
				if (null == task.getExclusive()) {
					task.setExclusive(Thread.currentThread());
				}
			}
			// critical area end
			lock.unlock();

关键代码中,目前这一段因该还有修改的空间

运行文件run.sh
日志分析LogAnalyzer.java,目前只能说是大概的分析有没有明显出错的地方,比如执行的数量对不对

日志:

queue has 60 wks
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
can only be executed 10 times...
shan$evol in task[2] is executing by ProductionLineB...
shan$love in task[8] is executing by ProductionLineA...
shan$love in task[3] is executing by ProductionLineA...
can only be executed 10 times...
shan$love in task[0] is executing by ProductionLineA...
shan$vole in task[3] is executing by ProductionLineB...
shan$elvo in task[8] is executing by ProductionLineB...
shan$ovel in task[8] is executing by ProductionLineB...
shan$vole in task[8] is executing by ProductionLineB...
shan$love in task[5] is executing by ProductionLineA...
shan$love in task[5] is executing by ProductionLineA...
shan$love in task[4] is executing by ProductionLineA...
shan$love in task[7] is executing by ProductionLineA...
shan$leov in task[8] is executing by ProductionLineB...
shan$elvo in task[4] is executing by ProductionLineB...
shan$ovel in task[4] is executing by ProductionLineB...
shan$vole in task[4] is executing by ProductionLineB...
shan$leov in task[4] is executing by ProductionLineB...
shan$ovel in task[5] is executing by ProductionLineB...
shan$evol in task[5] is executing by ProductionLineB...
shan$elvo in task[5] is executing by ProductionLineB...
shan$vole in task[1] is executing by ProductionLineB...
shan$ovel in task[1] is executing by ProductionLineB...
shan$leov in task[1] is executing by ProductionLineB...
shan$evol in task[1] is executing by ProductionLineB...
shan$vole in task[6] is executing by ProductionLineB...
shan$evol in task[3] is executing by ProductionLineB...
shan$elvo in task[7] is executing by ProductionLineB...
shan$ovel in task[7] is executing by ProductionLineB...
shan$leov in task[7] is executing by ProductionLineB...
shan$love in task[9] is executing by ProductionLineA...
shan$evol in task[7] is executing by ProductionLineB...
shan$ovel in task[6] is executing by ProductionLineB...
task0 is done...
shan$evol in task[6] is executing by ProductionLineB...
shan$vole in task[9] is executing by ProductionLineB...
shan$leov in task[6] is executing by ProductionLineB...
shan$evol in task[9] is executing by ProductionLineB...
shan$ovel in task[5] is executing by ProductionLineB...
shan$leov in task[4] is executing by ProductionLineB...
shan$leov in task[8] is executing by ProductionLineB...
shan$leov in task[1] is executing by ProductionLineB...
task2 is done...
shan$elvo in task[7] is executing by ProductionLineB...
shan$evol in task[5] is executing by ProductionLineB...
shan$ovel in task[7] is executing by ProductionLineB...
shan$elvo in task[5] is executing by ProductionLineB...
shan$evol in task[3] is executing by ProductionLineB...
shan$evol in task[6] is executing by ProductionLineB...
shan$vole in task[1] is executing by ProductionLineB...
shan$ovel in task[1] is executing by ProductionLineB...
shan$vole in task[9] is executing by ProductionLineB...
shan$evol in task[7] is executing by ProductionLineB...
shan$vole in task[6] is executing by ProductionLineB...
shan$evol in task[9] is executing by ProductionLineB...
shan$leov in task[8] is executing by ProductionLineB...
task4 is done...
shan$evol in task[5] is executing by ProductionLineB...
shan$vole in task[1] is executing by ProductionLineB...
shan$ovel in task[1] is executing by ProductionLineB...
task3 is done...
shan$evol in task[7] is executing by ProductionLineB...
task6 is done...
shan$leov in task[8] is executing by ProductionLineB...
task9 is done...
task5 is done...
shan$vole in task[1] is executing by ProductionLineB...
shan$evol in task[7] is executing by ProductionLineB...
task8 is done...
task1 is done...
task7 is done...

完整源码:
multiple-consumers-problem

JVM标记贴

    JVM参数

  • JMX参数
  • -Dcom.sun.management.jmxremote.port=1090
    -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false

  • 扩展参数
  • -XX:+PrintGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps 和PrintGC以及PrintGCDetails一起使用
    -Xloggc:gc.log

    -XX:+DoEscapeAnalysis
    -XX:+HeapDumpOnOutOfMemoryError

原码,反码,补码备忘

又是一篇炒现饭的帖子,主要是经常忘记,记在这里

首先理解位bit(Binary digit http://en.wikipedia.org/wiki/Bit)

以下都以二进制为例

比如一个8位的存储单元,那么它就可以保存256种不同状态,如果是无符号数的话,那么就是0 ~ 2^8-1,即00000000 ~ 11111111

那么对于有符号的数就是-2^7 ~ 2^7-1,最高位拿来存储符号了,0为正,1为负,即10000001 ~ 01111111

十进制 原码 反码 补码
127 01111111 01111111 01111111
-127 11111111 10000000 10000001
128 010000000 010000000 010000000
-128 10000000
45 00101101 00101101 00101101
-45 10101101 11010010 11010011
1 00000001 00000001 00000001
-1 10000001 11111110 11111111

上表中红色的数是表示溢出的,8位的存储空间无法表示这样的有符号数

计算机中的有符号数都是以补码的形式存放的,可以理解为只有有符号数才有原码,反码,补码
正数的原码,反码,补码表示都一样
负数的原码为其绝对值的原码,但符号位为1;反码就是保持原码的符号位不变,其他位按位取反;补码就是反码加1

引用Wikipedia
“一个数字的二补数就是将该数字作位反相运算(即一补数),再将结果加 1,即为该数字的二补数。”

为什么要引入原码,反码,补码这些概念
参见:http://dev.csdn.net/htmls/17/17680.html

我们最需要关注的是补码,引用作者的原话是

补码的设计目的是:
⑴使符号位能与有效值部分一起参加运算,从而简化运算规则
⑵使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计

引用自http://zh.wikipedia.org/wiki/二補數
在二补数系统中,一个负数就是用其对应正数的二补数来表示。
00000100 4
11111011 按位取反
11111100 二补数(-4)

减法可以用一个数加上另一个数的二补数来表示
5
-4
———-
1

00000101
+11111100
———-
000000001

另外字节(Byte),字(Word),双字,四字,字长这些概念都需要理解

阮一峰有个帖子讲的也很清楚明了

参考

http://zh.wikipedia.org/wiki/原码
http://zh.wikipedia.org/wiki/反码
http://zh.wikipedia.org/wiki/二補數
http://en.wikipedia.org/wiki/One’s_complement
http://en.wikipedia.org/wiki/Two’s_complement

自己动手编译OpenJDK

无聊的时候决定自己动手编译个JDK,曾经想在X86上编译一个Dalvik出来玩玩,但基于那玩意是高手才玩的起的,所以自己那个想法就不了了知了
看了一圈后发现OpenJDK还是比较容易编译的,所以就来炒份现饭
现代JDK这么容易自己编译还是归功于开源和社区

开场白就说这么多,下面是过程记录
下载源码,解压,阅读README和README-builds.html,然后就可以开工了

我是在Linux 2.6.35-28-generic #50-Ubuntu i686 GNU/Linux上编译的,我编译的是openjdk-7-ea-src-b141-05_may_2011这个版本

主要需要以下条件
Basic Linux Check List
1. Install the Bootstrap JDK, set ALT_BOOTDIR.
2. Optional Import JDK, set ALT_JDK_IMPORT_PATH.(可以不要,如果你是完全编译的话)
3. Install or upgrade the FreeType development package.(这个基本有了)
4. Install Ant 1.7.1 or newer, make sure it is in your PATH.

然后在环境变量下做些设置
ALT_BOOTDIR=$SUN_JDK1.6.0_25_HOME(我是用的SUN的JDK6来作为Bootstrap JDK的)
LANG=C(需要设置成这样,否则编译会报错,Linux一般是zh_CN.utf8改下就可以)
ALT_CUPS_HEADERS_PATH=$CUPS_HOME/include(何为CUPS见此)

另外环境变量中不要出现JAVA_HOME,CLASSPATH,有的话就把它们注释掉

记得使修改过的环境变量生效

然后就cd到$openjdk_src下开始执行make sanity检测
成功的话就执行make all ALLOW_DOWNLOADS=true WARNINGS_ARE_ERRORS=开始较为耗时的编译之旅

这两个参数的意思还是比较明白
ALLOW_DOWNLOADS=true就是编译的时候发现缺少某些预知的源码或依赖时,它会自动去下载,前提是必须联网
WARNINGS_ARE_ERRORS=就是禁止编译器把一些警告当错误,如果不加这个就可能会因为类似于这样的cc1plus: warnings being treated as errors提示而终止编译,这不是我们所希望的

但是不要高兴的太早,多多少少可能会遇到点问题,不过找找Google应该都能解决

下面贴出我在编译的时候遇到的问题
1、
/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/hotspot/src/os/linux/vm/os_linux.cpp
cc1plus: warnings being treated as errors
/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/hotspot/src/os/linux/vm/os_linux.cpp: In static member function ‘static bool os::Linux::hugetlbfs_sanity_check(bool, size_t)’:
/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/hotspot/src/os/linux/vm/os_linux.cpp:2853: error: use of assignment suppression and length modifier together in gnu_scanf format
make[6]: *** [os_linux.o] Error 1
make[6]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/build/linux-i586/hotspot/outputdir/linux_i486_compiler2/product’

WARNINGS_ARE_ERRORS=

2、
../../../src/solaris/native/sun/awt/awt.h:38: fatal error: X11/Intrinsic.h: No such file or directory
compilation terminated.
make[5]: *** [/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/build/linux-i586/tmp/sun/sun.awt/awt/obj/BufImgSurfaceData.o] Error 1
make[5]: *** Waiting for unfinished jobs….
make[5]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/jdk/make/sun/awt’
make[4]: *** [library_parallel_compile] Error 2
make[4]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/jdk/make/sun/awt’
make[3]: *** [all] Error 1
make[3]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/jdk/make/sun’
make[2]: *** [all] Error 1
make[2]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/jdk/make’
make[1]: *** [jdk-build] Error 2
make[1]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011′
make: *** [build_product_image] Error 2

sudo apt-get install libxt-dev

3、
../../../src/solaris/native/sun/xawt/XToolkit.c:48: fatal error: X11/extensions/XTest.h: No such file or directory
compilation terminated.
make[5]: *** [/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/build/linux-i586/tmp/sun/sun.awt.X11/xawt/obj/XToolkit.o] Error 1
make[5]: *** Waiting for unfinished jobs….
make[5]: Leaving directory `/home/guohai/dev/src/java/openjdk-7-ea-src-b141-05_may_2011/jdk/make/sun/xawt’
make[4]: *** [library_parallel_compile] Error 2

sudo apt-get install libxtst-dev

从2011-05-12 20:39开始编译,遇到问题,前前后后经历了近一个小时
另外如果编译出错了,并且你解决了错误,貌似你只需要继续执行make all ALLOW_DOWNLOADS=true WARNINGS_ARE_ERRORS=
就可以了,它会接着编译,而不需要从头开始编译
当你看到下面这种提示时
— Build times ———-
Target all_product_build
Start 2011-05-12 21:28:48
End 2011-05-12 21:37:48
00:00:04 corba
00:00:12 hotspot
00:00:03 jaxp
00:00:05 jaxws
00:08:32 jdk
00:00:04 langtools
00:09:00 TOTAL
————————-
应该就可以完工了,cd到$openjdk_src/build/platform/下,看看j2sdk-image和j2re-image中的内容是不是很熟悉?
cd$openjdk_src/build/platform/bin/下执行如下命令就应该可以看到结果,当然版本,硬件架构可能不一样
总之你就自己编译出了一个带自己名字的JDK了

$openjdk_src/build/linux-i586/bin$ ./java -version
openjdk version “1.7.0-internal”
OpenJDK Runtime Environment (build 1.7.0-internal-guohai_2011_05_12_20_39-b00)
OpenJDK Server VM (build 21.0-b11, mixed mode)

$openjdk_src/build/linux-i586/bin$ ./javac -version
javac 1.7.0-internal

有图
compile-my-openjdk

编译成功之后再把那些需要改回来的东西改回来


sudo apt-get install libfreetype6-dev
sudo apt-get install libasound2-dev
sudo apt-get install libcups2-dev

MySQL存储过程备忘

突然要弄MySQL存储过程
好久没有怎么接触过数据库存储过程了,都忘了,记在这里

create table email (id int auto_increment primary key, name text, content text, int size);
create table email_trash (id int auto_increment primary key, name text, content text, int size);

create table attachment (id int auto_increment primary key, email_id int, url text);
create table attachment_trash (id int auto_increment primary key, email_id int, url text);
delimiter //
drop procedure if exists fix_data_trash_magic //
CREATE PROCEDURE `fix_data_trash_magic`()
BEGIN

    DECLARE done INT DEFAULT 0;

    DECLARE _amount INT DEFAULT 0;
    DECLARE _size INT;
    DECLARE _id INT;
    DECLARE _newid LONG;

    DECLARE email_loop CURSOR FOR select id, size from email_trash where name = 'jemmy';

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    OPEN email_loop;
        REPEAT
        FETCH email_loop INTO _id, _size;
            IF NOT done THEN
                insert into email(name, content, size) select name,content,size from email_trash where id = _id;

                select last_insert_id() into _newid;

                insert into attachment(email_id, url) select _newid,url from attachment_trash where email_id = _id;
                
                SET _amount = _size + _amount;
            END IF;
        UNTIL done END REPEAT;
    CLOSE email_loop;
END
call fix_data_trash_magic()//

另外还有常用的几个命令

mysql> show procedure status;
mysql> show create procedure procedure_name; // SHOW THE CREATING SQL OF THE SP

包含异常处理,保存点的存储过程

DELIMITER ;;
CREATE PROCEDURE ps_with_exception_etc(in_domain varchar(50), in_account varchar(32), in_passwd varchar(32), OUT out_no int, OUT out_msg varchar(50))
BEGIN
    DECLARE s_fullid varchar(100);

    DECLARE EXIT HANDLER FOR SQLSTATE '23000'
    BEGIN
        SET out_no = -1;
        SET @prc_work_error = 'Repeated key';
        ROLLBACK TO sp_01;
    END;

    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        SET out_no = -1;
        SET @prc_work_error = 'Unknown error';
        ROLLBACK TO sp_01;
    END;

    -- 声明之类的都应该放在前面

    SET AUTOCOMMIT = 0;
        SELECT concat(in_account, '@', in_domain) INTO s_fullid;
        START TRANSACTION;
        SAVEPOINT sp_01;
        -- DO OO HERE
        -- DO XX HERE
        COMMIT;
    SET out_no = 1;
END
;;
DELIMITER ;

存储过程中的动态SQL
这里只是演示动态SQL的写法,要是真这么写太浪费了,直接可以写出来的SQL就不需要动态SQL

DELIMITER ;;
CREATE PROCEDURE ps_with_dynamic_sql(in_domain varchar(50), in_account varchar(32), OUT out_no int, OUT out_msg varchar(50))
BEGIN
    DECLARE fullid varchar(100);

    DECLARE DROP_USER_TABLEK varchar(500);
    DECLARE DROP_USER_MAILS varchar(500);

    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        SET out_no = -1;
        SET @prc_work_error = 'Unknown error';
        ROLLBACK TO sp_01;
    END;

    SET AUTOCOMMIT = 0;
    START TRANSACTION;
    SAVEPOINT sp_01;

        SET @MyQuery = concat('DELETE FROM tablek WHERE uuid = ', in_account);
        PREPARE DROP_USER_TABLEK from @MyQuery;

        SET @MyQuery = concat('DELETE FROM mails WHERE uuid = ', in_account);
        PREPARE DROP_USER_MAILS from @MyQuery;

        EXECUTE DROP_USER_TABLEK;
        EXECUTE DROP_USER_MAILS;
    COMMIT;
    SET out_no = 1;
END
;;
DELIMITER ;

还有个简单的

CREATE PROCEDURE `insert_data`(in item integer)
BEGIN
    DECLARE counter INT;
    SET counter = item;
    WHILE counter >= 1 DO
        // DO YOUR BIZ HERE
        SET counter = counter - 1;
    END WHILE;
END

另外在存储过程中声明变量,游标,处理器是有一定的顺序的,否则会报语法错误

触发器

create trigger insert_after_addr
after insert on addr
for each row
begin
set @aid=new.id;
insert into data(addr_id, type, value) values (@aid, 5, 'hello trigger');
end
show triggers//
drop trigger insert_after_addr//
alter table data modify id int(22) not null auto_increment
CREATE trigger insert_before_tablek
    before
        insert on tablek
    for each row
        begin
            set new.status = 25;
        end

Post Office Protocol – Version 3 阅读杂记

Post Office Protocol – Version 3 http://www.rfc-editor.org/rfc/rfc1939.txt

A POP3 session progresses through a number of states during its
lifetime. Once the TCP connection has been opened and the POP3
server has sent the greeting, the session enters the AUTHORIZATION
state. In this state, the client must identify itself to the POP3
server. Once the client has successfully done this, the server
acquires resources associated with the client’s maildrop, and the
session enters the TRANSACTION state. In this state, the client
requests actions on the part of the POP3 server. When the client has
issued the QUIT command, the session enters the UPDATE state. In
this state, the POP3 server releases any resources acquired during
the TRANSACTION state and says goodbye. The TCP connection is then
closed.

Minimal POP3 Commands:

USER name valid in the AUTHORIZATION state
PASS string
QUIT

STAT valid in the TRANSACTION state
LIST [msg]
RETR msg
DELE msg
NOOP
RSET
QUIT

Optional POP3 Commands:

APOP name digest valid in the AUTHORIZATION state

TOP msg n valid in the TRANSACTION state
UIDL [msg]

POP3 Replies:

+OK
-ERR

Example POP3 Session

S:
C:
S: +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
C: APOP mrose c4c9334bac560ecc979e58001b3e22fb
S: +OK mrose’s maildrop has 2 messages (320 octets)
C: STAT
S: +OK 2 320
C: LIST
S: +OK 2 messages (320 octets)
S: 1 120
S: 2 200
S: .
C: RETR 1
S: +OK 120 octets
S: S: .
C: DELE 1
S: +OK message 1 deleted
C: RETR 2
S: +OK 200 octets
S: S: .
C: DELE 2
S: +OK message 2 deleted
C: QUIT
S: +OK dewey POP3 server signing off (maildrop empty)
C:
S:

Responses to certain commands are multi-line. In these cases, which
are clearly indicated below, after sending the first line of the
response and a CRLF, any additional lines are sent, each terminated
by a CRLF pair. When all lines of the response have been sent, a
final line is sent, consisting of a termination octet (decimal code
046, “.”) and a CRLF pair. If any line of the multi-line response
begins with the termination octet, the line is “byte-stuffed” by
pre-pending the termination octet to that line of the response.
Hence a multi-line response is terminated with the five octets
“CRLF.CRLF”. When examining a multi-line response, the client checks
to see if the line begins with the termination octet. If so and if
octets other than CRLF follow, the first octet of the line (the
termination octet) is stripped away. If so and if CRLF immediately
follows the termination character, then the response from the POP
server is ended and the line containing “.CRLF” is not considered
part of the multi-line response.

POP3命令实例
telnet mail-host 110

USER hello@example.com

PASS 111111

STAT
+OK 13 332023

LIST
+OK POP3 clients that break here, they violate STD53.
1 202
2 73113
3 402
4 402
5 80267
6 13380
7 407
8 755
9 115473
10 16932
11 30343
12 169
13 178
.

LIST 2
+OK 2 73113

UIDL 3
+OK 3 uuxvo-13134583174281799

RETR 1 #应该返回第1封邮件的所有内容
+OK
Date: Mon, 15 Aug 2011 14:37:42 +0800 (CST)
From:
To: hello@example.com
Message-ID: <6879346.11.1313450262952.JavaMail.root@knight>
Subject: =?UTF-8?Q?=E6=B5=8B=E8=AF=952011=E5=B9=B48=E6=9C=881?=
=?UTF-8?Q?5=E6=97=A5=E6=98=9F=E6=9C=9F=E4=B8=8014:37?=
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=”—-=_Part_10_343799.1313390262951″

——=_Part_10_343799.1313390262951
Content-Type: text/html;charset=gbk
Content-Transfer-Encoding: base64

PGh0bWw+PGhlYWQ+PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0
L2h0bWw7IGNoYXJzZXQ9Z2JrIj48dGl0bGU+PC90aXRsZT48L2hlYWQ+PGJvZHk+suLK1DIwMTHE
6jjUwjE1yNXQx8ba0rsxNDozNzxkaXY+suLK1DIwMTHE6jjUwjE1yNXQx8ba0rsxNDozNzwvZGl2
PjwvYm9keT48L2h0bWw+
——=_Part_10_343799.1313390262951–
.

DELE 1
+OK

RSET
+OK

TOP 2 3 #应该返回第2封邮件的邮件头+前3行
+OK
Date: Tue, 16 Aug 2011 09:31:27 +0800 (CST)
From: noreply@example.com
To: hello@example.com
Message-ID: <27682895.1.1313458287431.JavaMail.root@SERVER2>
Subject: =?gbk?B?PLLiytTNy9DFMjAxMcTqONTCMTbI1dDHxtq2/jA5OjI3Ps22td3Kp7Dc?=
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary=”—-=_Part_0_31571602.1313458287431″

——=_Part_0_31571602.1313458287431
Content-Type: text/html;charset=gbk
Content-Transfer-Encoding: base64
.

TOP 1 #应该返回第1封邮件的邮件头

QUIT #断开连接