少女祈祷中...

1.NIO编程

  • Non-Blocking I/O
  • 提供非阻塞通讯等方式
  • 避免同步I/O通讯效率过低
  • 一个线程可以管理多个连接
  • 减少线程多的压力

主要类

  • Buffer 缓存区
  • Channel 通道
  • Selector多路选择器

Buffer

  • Buffer 缓冲区,一个可以读写的内存区域
    • ByteBuffer, CharBuffer, DoubleBuffer, IntBuffer, LongBuffer, ShortBuffer (StringBuffer 不是Buffer缓冲区)
  • 四个主要属性
    • capacity 容量
    • position 读写位置
    • limit 界限
    • mark 标记,用于重复一个读/写操作
  • 使用Buffer读写数据一般遵循以下四个步骤
    • 写入数据到Buffer
    • 调用flip()方法
    • 从Buffer中读取数据
    • 调用clear()方法或者compact()方法
  • Buffer的分配
    • allocate方法
    1
    ByteBuffer buf = ByteBuffer.allocate(48);
  • 向Buffer中写数据
    • 从Channel写到Buffer
    1
    int bytesRead = inChannel.read(buf);
    • 通过Buffer的put()方法写入Buffer
    1
    buf.put(127);
  • flip()方法
    • flip方法将Buffer从写模式切换到读模式
    • 调用flip()方法会将position设回0,并将limit设置成之前position的值
  • 从Buffer中读数据
    • 从Buffer读取数据到Channel
    1
    int bytesWritten = inChannel.write(buf);
    • 使用get()方法从Buffer中读取数据
    1
    byte aByte = buf.get();
  • clear()与compact()方法
    • clear()
      • position将被设回0,limit被设置成 capacity的值
      • Buffer 被清空了,但Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据
    • compact()
      • 将所有未读的数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面
      • limit属性依然像clear()方法一样,设置成capacity
      • 不会覆盖未读的数据

Channel

  • 全双工的,支持读/写(而Stream流是单向的)
  • 支持异步读写
  • 和Buffer配合,提高效率
  • ServerSocketChannel 服务器TCP Socket 接入通道,接收客户端
  • SocketChannel TCP Socket通道,可支持阻塞/非阻塞通讯
  • DatagramChannel UDP 通道
  • FileChannel 文件通道

SocketChannel

  • 打开SocketChannel
1
2
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
  • 关闭SocketChannel
    • close()
    1
    socketChannel.close();
  • **读取SocketChannel **
    • read()
    1
    2
    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = socketChannel.read(buf);
  • 写入SocketChannel
    • write()
    1
    2
    3
    4
    5
    6
    7
    8
    String newData = "New String to write to file...";
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    while(buf.hasRemaining()) {
    channel.write(buf);
    }

Selector

  • 每隔一段时间,不断轮询注册在其上的Channel
  • 如果有一个Channel有接入、读、写操作,就会被轮询出来
  • 根据SelectionKey可以获取相应的Channel,进行后续IO操作
  • 避免过多的线程
  • SelectionKey四种类型
    • OP_CONNECT (连接就绪 某个Channel成功连接到另一个服务器)
    • OP_ACCEPT (接收就绪 一个ServerSocketChannel准备好接收新进入的连接)
    • OP_READ (读就绪 有数据可读的通道)
    • OP_WRITE (写就绪 等待写数据的通道)
  • Selector的创建
    • Selector.open()方法
    1
    Selector selector = Selector.open();
  • 向Selector注册通道
    • SelectableChannel.register()方法
    1
    2
    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
  • 通过Selector选择通道
  • 一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道
    • select() 阻塞到至少有一个通道在你注册的事件上就绪了
    • select(long timeout) 最长会阻塞timeout毫秒(参数)
    • selectNow() 不会阻塞,不管什么通道就绪都立刻返回
  • 访问已选择键集
  • 一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)
    • selectedKeys()
    1
    2
    Set selectedKeys = selector.selectedKeys();

NIO结构

graph LR
p[Server]--c---s[Server
Socket
Channel] s--c---sr[Selector] sr--c---b01[Buffer] sr--c---b02[Buffer] sr--c---b03[Buffer] b01--c---s01[Selector] b02--c---s02[Selector] b03--c---s03[Selector] s01--c---c1[Client1] s02--c---c2[Client2] s03--c---c3[Client3]
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//Server
//Selector构建
Selector selector = Selector.open();
//Channel构建
ServerSocketChannel servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false); //设置非阻塞模式
servChannel.socket().bind(new InetSocketAddress(8001), 1024);//设定8001端口
servChannel.register(selector, SelectionKey.OP_ACCEPT); //selector绑定

//selector一次遍历所有channel
selector.select(1000); //最多等待堵塞1000ms
Set<SelectionKey> selectedKeys = selector.selectedKeys(); //获得所有有数据响应的key
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
try {
//Handle
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}

//Handle
if(key.isValid()) {
if(key.isAcceptable()) {
// Accept the new connection
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// Add the new connection to the selector
sc.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()) {
//Read the data
}
}

//Client
//Selector构建
Selector selector = Selector.open();
//Channel构建
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); //设置非阻塞模式

// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress("127.0.0.1", 8001))) {
socketChannel.register(selector, SelectionKey.OP_READ);
//doWrite
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}

//Handle
if(key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if(key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
}
}
if(key.isReadable()) {
//Read the data
}
}

//doWrite
byte[] str = //TODO
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length); //Buffer分配内存
writeBuffer.put(str); //str写入Buffer
writeBuffer.flip(); //读写模式切换
sc.write(writeBuffer); //Buffer数取数据到Channel

2.AIO编程

  • Asynchronous I/O, 异步I/O
  • JDK 1.7引入,主要在java.nio包中
  • 异步I/O,采用回调方法进行处理读写操作

主要类

  • AsynchronousServerSocketChannel 服务器接受请求通道
    • bind 绑定在某一个端口 accept 接受客户端请求
  • AsynchronousSocketChannel Socket通讯通道
    • read 读数据 write 写数据
  • CompletionHandler 异步处理类
    • completed 操作完成后异步调用方法 failed 操作失败后异步调用方法
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
31
//服务器通道建立
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 8001));

//服务器接受客户端请求,成功接收后自动回调
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel channel, Object attachment) {
//TODO
}

@Override
public void failed(Throwable exc, Object attachment) {
//TODO
}
});

//客户端通道建立
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();

//连接成功后自动回调
channel.connect(new InetSocketAddress("localhost", 8001), null, new CompletionHandler<Void, Void>() {
@Override
public void completed(Integer result, Object attachment) {
//TODO
}
@Override
public void failed(Throwable exc, Object attachment) {
//TODO
}
});

三种I/O的区别

BIONIOAIO
阻塞方式阻塞非阻塞非阻塞
同步方式同步同步异步
编程难度简单较难困难
客户机/服务器线程对比1:1N:1N:1
性能

3.Netty编程

关键技术

  • 通道 Channel
    • ServerSocketChannel/NioServerSocketChannel/…
    • SocketChannel/NioSocketChannel
  • 事件 EventLoop
    • 为每个通道定义一个EventLoop,处理所有的I/O事件
    • EventLoop注册事件
    • EventLoop将事件派发给ChannelHandler
    • EventLoop安排进一步操作
  • 事件
    • 事件按照数据流向进行分类
    • 入站事件:连接激活/数据读取/……
    • 出站事件:打开到远程连接/写数据/……
  • 事件处理 ChannelHandler
    • Channel通道发生数据或状态改变
    • EventLoop会将事件分类,并调用ChannelHandler的回调函数
    • 程序员需要实现ChannelHandler内的回调函数
    • ChannelInboundHandler/ChannelOutboundHandler
  • ChannelHandler工作模式:责任链
    • 责任链模式
      • 将请求的接收者连成一条链
      • 在链上传递请求,直到有一个接收者处理该请求
      • 避免请求者和接收者的耦合
    • ChannelHandler可以有多个,依次进行调用
    • ChannelPipeline作为容器,承载多个ChannelHandler
  • ByteBuf
    • 强大的字节容器,提供丰富API进行操作
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//from BiliBili
//Server
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();

ServerBootstrap bootstrap = new ServerBootstrap();
//链式编程,设置服务器
bootstrap.group(bossGroup,workGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器通道的实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列得到连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true) //设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() { //给workGroup的EventLoop对应的管道设置处理器
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler()); //添加处理类
}
});

//绑定一个端口并同步,生成了一个ChannelFuture对象
ChannelFuture cf =bootstrap.bind(6668).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
//ServerHandler extends ChannelInboundHandlerAdapter
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
ByteBuf in = (ByteBuf) msg;
String content = in.toString(CharsetUtil.UTF_8);
System.out.println("Server received: " + content);
System.out.println("Client address: "+ctx.channel().remoteAddress());

ByteBuf out = ctx.alloc().buffer(1024);
out.writeBytes((content + " 666").getBytes());
ctx.write(out);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
//Client
EventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors) //设置两个线程组
.channel(NioSocketChannel.class) //使用NioSocketChannel作为服务器通道的实现
.handler(new ChannelInitializer<SocketChannel>() { //给eventExecutors的EventLoop对应的管道设置处理器
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler()); //添加处理类
}
});
System.out.println("...客户端 is ready...");

//启动客户端连接服务端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
//优雅关闭
eventExecutors.shutdownGracefully();
}
//ClientHandler extends ChannelInboundHandlerAdapter
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
System.out.println("Client received: " + buf.toString(CharsetUtil.UTF_8));
System.out.println("Server address: "+ctx.channel().remoteAddress());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}

4.邮件基础知识

主要协议(发送端口25, 接收端口110)

  • 发送, SMTP, Simple Mail Transfer Protocol
  • 接收, Pop3, Post Office Protocol 3, (POP)
  • 接收, IMAP, Internet Message Access Protocol, IMAP4
  • 摘要浏览
    • 选择下载附件
    • 多文件夹
    • 网络硬盘

5.Mail编程

邮件服务器支持

  • 需要在邮件服务内设置,可以查看相关邮件帮助
  • 需要知道pop服务器和smtp服务器信息

javax.mail 包和javax.mail.internet 包

关键类

  • Session: 邮件会话 和HttpSession不同
  • Store: 邮件存储空间
  • Folder: 邮件文件夹
  • Message: 电子邮件
  • Address: 邮件地址
  • Transport: 发送协议类
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//Receive
public class MailClientRecv {
private Session session;
private Store store;
private String username = "chenliangyu1980@126.com";
private String password = "1234567899";
private String popServer = "pop.126.com";

public void init()throws Exception
{
//设置属性
Properties props = new Properties();
props.put("mail.store.protocol", "pop3");
props.put("mail.imap.class", "com.sun.mail.imap.IMAPStore");
props.put("mail.pop3.class", "com.sun.mail.pop3.POP3Store");

// 创建Session对象
session = Session.getInstance(props,null);
session.setDebug(false); //输出跟踪日志

// 创建Store对象
store = session.getStore("pop3");

//连接到收邮件服务器
store.connect(popServer,username,password);
}

public void receiveMessage()throws Exception
{
String folderName = "inbox";
Folder folder=store.getFolder(folderName);
if(folder==null)
{
throw new Exception(folderName+"邮件夹不存在");
}
//打开信箱
folder.open(Folder.READ_ONLY);
System.out.println("您的收件箱有"+folder.getMessageCount()+"封邮件.");
System.out.println("您的收件箱有"+folder.getUnreadMessageCount()+"封未读的邮件.");

//读邮件
Message[] messages=folder.getMessages();
//for(int i=1;i<=messages.length;i++)
for(int i=1;i<=3;i++)
{
System.out.println("------第"+i+"封邮件-------");
//打印邮件信息
Message message = messages[i];
//folder.getMessage(i).writeTo(System.out);
System.out.println((message.getFrom())[0]);
System.out.println(message.getSubject());
System.out.println();
}
folder.close(false); //关闭邮件夹
}

public void close()throws Exception
{
store.close();
}

public static void main(String[] args)throws Exception {
MailClientRecv client=new MailClientRecv();
//初始化
client.init();
//接收邮件
client.receiveMessage();
//关闭连接
client.close();
}
}
//Send
public class MailClientSend {
private Session session;
private Transport transport;
private String username = "lychen@sei.ecnu.edu.cn";
private String password = "1234567899";
private String smtpServer = "webmail.ecnu.edu.cn";

public void init()throws Exception
{
//设置属性
Properties props = new Properties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.class", "com.sun.mail.smtp.SMTPTransport");
props.put("mail.smtp.host", smtpServer); //设置发送邮件服务器
props.put("mail.smtp.port", "25");
props.put("mail.smtp.auth", "true"); //SMTP服务器需要身份验证

// 创建Session对象
session = Session.getInstance(props,new Authenticator(){ //验账账户
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
session.setDebug(true); //输出跟踪日志

// 创建Transport对象
transport = session.getTransport();
}

public void sendMessage()throws Exception{
//创建一个邮件
//Message msg = TextMessage.generate();
//Message msg = HtmlMessage.generate();
Message msg = AttachmentMessage.generate();
//发送邮件
transport.connect();
transport.sendMessage(msg, msg.getAllRecipients());
//打印结果
System.out.println("邮件已经成功发送");
}

public void close()throws Exception
{
transport.close();
}

public static void main(String[] args)throws Exception {

MailClientSend client=new MailClientSend();
//初始化
client.init();
//发送邮件
client.sendMessage();
//关闭连接
client.close();
}
}

//Text
String from = "lychen@sei.ecnu.edu.cn "; // 发件人地址
String to = "chenliangyu1980@126.com"; // 收件人地址
String subject = "test";
String body = "您好,这是来自一封chenliangyu的测试邮件";

// 创建Session实例对象
Session session = Session.getDefaultInstance(new Properties());
// 创建MimeMessage实例对象
MimeMessage message = new MimeMessage(session);
// 设置发件人
message.setFrom(new InternetAddress(from));
// 设置收件人
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
// 设置发送日期
message.setSentDate(new Date());
// 设置邮件主题
message.setSubject(subject);
// 设置纯文本内容的邮件正文
message.setText(body);
// 保存并生成最终的邮件内容
message.saveChanges();
return message;

//Html
String from = "lychen@sei.ecnu.edu.cn "; // 发件人地址
String to = "chenliangyu1980@126.com"; // 收件人地址
String subject = "HTML邮件";
String body = "<a href=http://www.ecnu.edu.cn>"
+ "<h4>欢迎大家访问我们的网站</h4></a></br>"
+ "<img src=\"https://news.ecnu.edu.cn/_upload/article/images/2e/e2/6b554d034c9192101208c732195e/16a6ec66-6729-4469-a5f4-0435e0f2e66a.jpg\">";

// 创建Session实例对象
Session session = Session.getDefaultInstance(new Properties());
// 创建MimeMessage实例对象
MimeMessage message = new MimeMessage(session);
// 设置发件人
message.setFrom(new InternetAddress(from));
// 设置收件人
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
// 设置发送日期
message.setSentDate(new Date());
// 设置邮件主题
message.setSubject(subject);
// 设置HTML格式的邮件正文
message.setContent(body, "text/html;charset=gb2312");
// 保存并生成最终的邮件内容
message.saveChanges();
return message;

//Attachment
String from = "lychen@sei.ecnu.edu.cn "; // 发件人地址
String to = "chenliangyu1980@126.com"; // 收件人地址
String subject = "多附件邮件"; //邮件主题
String body = "<a href=http://www.ecnu.edu.cn>" +
"欢迎大家访问我们的网站</a></br>";

// 创建Session实例对象
Session session = Session.getDefaultInstance(new Properties());
// 创建MimeMessage实例对象
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to));
message.setSubject(subject);

//创建代表邮件正文和附件的各个MimeBodyPart对象
MimeBodyPart contentPart = createContent(body);
MimeBodyPart attachPart1 = createAttachment("c:/temp/ecnu4.jpg");
MimeBodyPart attachPart2 = createAttachment("c:/temp/ecnu5.jpg");

//创建用于组合邮件正文和附件的MimeMultipart对象
MimeMultipart allMultipart = new MimeMultipart("mixed");
allMultipart.addBodyPart(contentPart);
allMultipart.addBodyPart(attachPart1);
allMultipart.addBodyPart(attachPart2);

//设置整个邮件内容为最终组合出的MimeMultipart对象
message.setContent(allMultipart);
message.saveChanges();
return message;

public static MimeBodyPart createContent(String body) throws Exception {
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setContent(body,"text/html;charset=gb2312");
return htmlBodyPart;
}

public static MimeBodyPart createAttachment(String filename) throws Exception {
//创建保存附件的MimeBodyPart对象,并加入附件内容和相应信息
MimeBodyPart attachPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource(filename);
attachPart.setDataHandler(new DataHandler(fds));
attachPart.setFileName(fds.getName());
return attachPart;
}