MOOC 个人学习笔记

# 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 方法
    ByteBuffer buf = ByteBuffer.allocate(48);
  • 向 Buffer 中写数据
    • 从 Channel 写到 Buffer
    int bytesRead = inChannel.read(buf);
    • 通过 Buffer 的 put () 方法写入 Buffer
    buf.put(127);
  • flip () 方法
    • flip 方法将 Buffer 从写模式切换到读模式
    • 调用 flip () 方法会将 position 设回 0,并将 limit 设置成之前 position 的值
  • 从 Buffer 中读数据
    • 从 Buffer 读取数据到 Channel
    int bytesWritten = inChannel.write(buf);
    • 使用 get () 方法从 Buffer 中读取数据
    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
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
  • 关闭 SocketChannel
    • close()
    socketChannel.close();
  • ** 读取 SocketChannel **
    • read()
    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = socketChannel.read(buf);
  • 写入 SocketChannel
    • write()
    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 () 方法
    Selector selector = Selector.open();
  • 向 Selector 注册通道
    • SelectableChannel.register () 方法
    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()
    Set selectedKeys = selector.selectedKeys();

# NIO 结构

Error: Navigating frame was detached
//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 操作失败后异步调用方法
// 服务器通道建立
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 进行操作
//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: 发送协议类
//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;
}