目录

[Java核心技术] Java网络编程

Java 核心技术读书笔记——Java网络编程

1 Java UDP编程

笔记

TCP:Transmission Control Protocol

  • 传输控制协议,面向连接的协议
  • 两台机器的可靠无差错的数据传输
  • 双向字节流传递

UDP:User Datagram Protocol

  • 用户数据报协议,面向无连接协议
  • 不保证可靠的数据传输
  • 速度快,也可以在较差网络下使用
  • 计算机通讯:数据从一个IP的 port 出发(发送方),运输到 另外一个IP的 port(接收方)
  • UDP:无连接无状态的通讯协议,
    • 发送方发送消息,如果接收方刚好在目的地,则可以接受。如果 不在,那这个消息就丢失了
    • 发送方也无法得知是否发送成功
    • UDP 的好处就是简单,节省,经济

1.1 DatagramSocket 通讯的数据管道

  • sendreceive 方法
  • (可选,多网卡)绑定一个 IP 和 Port

1.2 DatagramPacket

  • 集装箱:封装数据
  • 地址标签:目的地 IP+Port

1.3 实例

  • 接收消息
 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
import java.net.*;
public class UdpRecv
{
  public static void main(String[] args) throws Exception
  {
    DatagramSocket  ds=new DatagramSocket(3000);
    byte [] buf=new byte[1024];
    DatagramPacket dp=new DatagramPacket(buf,1024);
    
    System.out.println("UdpRecv: 我在等待信息");
    ds.receive(dp);
    System.out.println("UdpRecv: 我接收到信息");
    String strRecv=new String(dp.getData(),0,dp.getLength()) +
    " from " + dp.getAddress().getHostAddress()+":"+dp.getPort(); 
    System.out.println(strRecv);
    
    Thread.sleep(1000);
    System.out.println("UdpRecv: 我要发送信息");
    String str="hello world 222";
    DatagramPacket dp2=new DatagramPacket(str.getBytes(),str.length(), 
        InetAddress.getByName("127.0.0.1"),dp.getPort());
    ds.send(dp2);
    System.out.println("UdpRecv: 我发送信息结束");
    ds.close();
  }
}

/**
 * 
 * UdpRecv: 我在等待信息
 * UdpRecv: 我接收到信息
 * hello world from 127.0.0.1:61933
 * UdpRecv: 我要发送信息
 * UdpRecv: 我发送信息结束
 * 
 */
  • 发送消息
 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
import java.net.*;
public class UdpSend
{
  public static void main(String [] args) throws Exception
  {
    DatagramSocket ds=new DatagramSocket();
    String str="hello world";
    DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),
        InetAddress.getByName("127.0.0.1"),3000);
    
    System.out.println("UdpSend: 我要发送信息");
    ds.send(dp);
    System.out.println("UdpSend: 我发送信息结束");
    
    Thread.sleep(1000);
    byte [] buf=new byte[1024];
    DatagramPacket dp2=new DatagramPacket(buf,1024);
    System.out.println("UdpSend: 我在等待信息");
    ds.receive(dp2);
    System.out.println("UdpSend: 我接收到信息");
    String str2=new String(dp2.getData(),0,dp2.getLength()) +
        " from " + dp2.getAddress().getHostAddress()+":"+dp2.getPort(); 
    System.out.println(str2);
        
    ds.close();
  }
}
/**
 * 
 * 
 * UdpSend: 我要发送信息
 * UdpSend: 我发送信息结束
 * UdpSend: 我在等待信息
 * UdpSend: 我接收到信息
 * hello world 222 from 127.0.0.1:3000
 * 
 */

2 TCP 编程

TCP协议:有链接、保证可靠的无误差通讯

  • ① 服务器:创建一个ServerSocket,等待连接
  • ② 客户机:创建一个Socket,连接到服务器
  • ③ 服务器:ServerSocket接收到连接,创建一个Socket和客户的Socket建立专线连接,后续服务器和客户机的对话(这一对Socket)会在一个单独的线程(服务器端)上运行
  • ④ 服务器的ServerSocket继续等待连接,返回 ①

/java12/01.png
TCP协议

2.1 ServerSocket: 服务器码头

  • 需要绑定port
  • 如果有多块网卡,需要绑定一个IP地址

2.2 Socket: 运输通道

  • 客户端需要绑定服务器的地址和Port
  • 客户端往Socket输入流写入数据,送到服务端
  • 客户端从Socket输出流取服务器端过来的数据
  • 服务端反之亦然

/java12/02.png
Socket

2.3 相关规则

  • 服务端等待响应时,处于阻塞状态
  • 服务端可以同时响应多个客户端
  • 服务端每接受一个客户端,就启动一个独立的线程与之对 应
  • 客户端或者服务端都可以选择关闭这对Socket的通道
  • 实例
    • 服务端先启动,且一直保留
     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
    
    import java.net.*;
    import java.io.*;
    public class TcpServer
    {
      public static void main(String [] args) 
      {
        try
        {
          ServerSocket ss=new ServerSocket(8001); //驻守在8001端口
          Socket s=ss.accept();                   //阻塞,等到有客户端连接上来
          System.out.println("welcome to the java world");
          InputStream ips=s.getInputStream();     //有人连上来,打开输入流
          OutputStream ops=s.getOutputStream();   //打开输出流
          //同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
    
          ops.write("Hello, Client!".getBytes());  //输出一句话给客户端
    
    
          BufferedReader br = new BufferedReader(new InputStreamReader(ips));
          //从客户端读取一句话     
          System.out.println("Client said: " + br.readLine());
    
    
          ips.close();          
          ops.close();
          s.close();
          ss.close();
        }
        catch(Exception e)
        {
          e.printStackTrace();
        }
      }
    }
    /**
     * 
     * 
     * 输入:Hello Server
     * I want to send: Hello Server
     * Server said: Hello, Client!
     * 
     * 
     */
    
    • 客户端后启动,可以先退出
     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
    
    import java.net.*;
    import java.io.*;
    
    public class TcpClient {
      public static void main(String[] args) {
        try {
          Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001); //需要服务端先开启
    
          //同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
          InputStream ips = s.getInputStream();    //开启通道的输入流
          BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
    
          OutputStream ops = s.getOutputStream();  //开启通道的输出流
          DataOutputStream dos = new DataOutputStream(ops);     
    
          BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
          while (true) 
          {
            String strWord = brKey.readLine();
            if (strWord.equalsIgnoreCase("quit"))
            {
              break;
            }
            else
            {
              System.out.println("I want to send: " + strWord);
              dos.writeBytes(strWord + System.getProperty("line.separator"));
    
              System.out.println("Server said: " + brNet.readLine());
            }
    
          }
    
          dos.close();
          brNet.close();
          brKey.close();
          s.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    /**
     * 
     * welcome to the java world
     * Client said: Hello Server
     * 
     * 
     */
    

3 Java HTTP编程

HTTP

HTTP

  • 超文本传输协议(HyperText Transfer Protocol)
  • 用于从WWW(World Wide Web)服务器传输超文本到本地浏览 器的传输协议
  • 1989年蒂姆•伯纳斯•李(Tim Berners Lee)提出了一种能让远隔两 地的研究者们共享知识的设想
  • 借助多文档之间相互关联形成的超文本 (HyperText),连成可 相互参阅的 WWW
  • 1990年问世,1997年发布版本1.1,2015年发布版本2.0
  • 资源文件采用HTML编写,以URL形式对外提供

3.1 HTTP访问方式

  • GET:从服务器获取资源到客户端
  • POST:从客户端向服务器发送数据
  • PUT:上传文件
  • DELETE:删除文件
  • HEAD:报文头部
  • OPTIONS:询问支持的方法
  • TRACE:追踪路径
  • CONNECT:用隧道协议连接代理

Java HTTP编程(java.net包)

  • 支持模拟成浏览器的方式去访问网页

  • URL , Uniform Resource Locator,代表一个资源

  • URLConnection

    • 获取资源的连接器
    • 根据URL的openConnection()方法获得URLConnection
    • connect方法,建立和资源的联系通道
    • getInputStream方法,获取资源的内容
  • GET实例

     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
    
    
    import java.io.*;
    import java.net.*;
    import java.util.*;
    
    
    public class URLConnectionGetTest
    {
       public static void main(String[] args)
       {
          try
          {
             String urlName = "http://www.baidu.com";
    
             URL url = new URL(urlName);
             URLConnection connection = url.openConnection(); 
             // 建立连接通道
             connection.connect();
    
             // 打印http的头部信息
    
             Map<String, List<String>> headers = connection.getHeaderFields();
             for (Map.Entry<String, List<String>> entry : headers.entrySet())
             {
                String key = entry.getKey();
                for (String value : entry.getValue())
                   System.out.println(key + ": " + value);
             }
    
             // 输出将要收到的内容属性信息
    
             System.out.println("----------");
             System.out.println("getContentType: " + connection.getContentType());
             System.out.println("getContentLength: " + connection.getContentLength());
             System.out.println("getContentEncoding: " + connection.getContentEncoding());
             System.out.println("getDate: " + connection.getDate());
             System.out.println("getExpiration: " + connection.getExpiration());
             System.out.println("getLastModifed: " + connection.getLastModified());
             System.out.println("----------");
    
             BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
    
             // 输出收到的内容
             String line = "";
             while((line=br.readLine()) != null)
             {
               System.out.println(line);
             }
             br.close();
          }
          catch (IOException e)
          {
             e.printStackTrace();
          }
       }
    }
    /**
     * 
     * null: HTTP/1.1 200 OK
     * Server: bfe
     * Content-Length: 2381
     * Date: Tue, 01 Mar 2022 16:27:49 GMT
     * Content-Type: text/html
     * ----------
     * getContentType: text/html
     * getContentLength: 2381
     * getContentEncoding: null
     * getDate: 1646152069000
     * getExpiration: 0
     * getLastModifed: 0
     * ----------
     * <!DOCTYPE html>
     * ......
     * 
     * 
     * 
     */
    
  • POST实例

      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
    
    
    import java.io.*;
    import java.net.*;
    import java.nio.file.*;
    import java.util.*;
    
    public class URLConnectionPostTest
    {
       public static void main(String[] args) throws IOException
       {
          String urlString = "https://tools.usps.com/go/ZipLookupAction.action";
          Object userAgent = "HTTPie/0.9.2";
          Object redirects = "1";
          CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
    
          Map<String, String> params = new HashMap<String, String>();
          params.put("tAddress", "1 Market Street");  
          params.put("tCity", "San Francisco");
          params.put("sState", "CA");
          String result = doPost(new URL(urlString), params, 
             userAgent == null ? null : userAgent.toString(), 
             redirects == null ? -1 : Integer.parseInt(redirects.toString()));
          System.out.println(result);
       }   
    
       public static String doPost(URL url, Map<String, String> nameValuePairs, String userAgent, int redirects)
             throws IOException
       {        
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();
          if (userAgent != null)
             connection.setRequestProperty("User-Agent", userAgent);
    
          if (redirects >= 0)
             connection.setInstanceFollowRedirects(false);
    
          connection.setDoOutput(true);
    
          //输出请求的参数
          try (PrintWriter out = new PrintWriter(connection.getOutputStream()))
          {
             boolean first = true;
             for (Map.Entry<String, String> pair : nameValuePairs.entrySet())
             {
              //参数必须这样拼接 a=1&b=2&c=3
                if (first) 
                {
                  first = false;
                }
                else
                {
                  out.print('&');
                }
                String name = pair.getKey();
                String value = pair.getValue();
                out.print(name);
                out.print('=');
                out.print(URLEncoder.encode(value, "UTF-8"));
             }
          }      
          String encoding = connection.getContentEncoding();
          if (encoding == null) 
          {
            encoding = "UTF-8";
          }
    
          if (redirects > 0)
          {
             int responseCode = connection.getResponseCode();
             System.out.println("responseCode: " + responseCode);
             if (responseCode == HttpURLConnection.HTTP_MOVED_PERM 
                   || responseCode == HttpURLConnection.HTTP_MOVED_TEMP
                   || responseCode == HttpURLConnection.HTTP_SEE_OTHER) 
             {
                String location = connection.getHeaderField("Location");
                if (location != null)
                {
                   URL base = connection.getURL();
                   connection.disconnect();
                   return doPost(new URL(base, location), nameValuePairs, userAgent, redirects - 1);
                }
    
             }
          }
          else if (redirects == 0)
          {
             throw new IOException("Too many redirects");
          }
    
          //接下来获取html 内容
          StringBuilder response = new StringBuilder();
          try (Scanner in = new Scanner(connection.getInputStream(), encoding))
          {
             while (in.hasNextLine())
             {
                response.append(in.nextLine());
                response.append("\n");
             }         
          }
          catch (IOException e)
          {
             InputStream err = connection.getErrorStream();
             if (err == null) throw e;
             try (Scanner in = new Scanner(err))
             {
                response.append(in.nextLine());
                response.append("\n");
             }
          }
    
          return response.toString();
       }
    }
    
    /**
     * 
     * 
     * responseCode: 200
     * <html lang="en">
     * ......
     *
     */
    

4 HttpClient

HttpClient

HttpClient 包含一下两个包:

  • JDK HTTP Client (JDK自带,从9开始)
  • Apache HttpComponents的HttpClient (Apache出品)

4.1 JDK HttpClient

  • JDK 9 新增,JDK10更新,JDK11正式发布
  • 收录在java.net.http
  • 取代URLConnection
  • 支持HTTP/1.1和HTTP/2
  • 实现大部分HTTP方法

主要类

  • HttpClient
  • HttpRequest
  • HttpResponse

JDK HttpClient GET 实例

 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
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;

/* 获取百度首页的源码 */
public class JDKHttpClientGetTest {

  public static void main(String[] args) throws IOException, InterruptedException {
    doGet();
  }
  
  public static void doGet() {
    try{
      HttpClient client = HttpClient.newHttpClient();
      HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
      HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
      System.out.println(response.body());
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
}

JDK HttpClient POST 实例

 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
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

/* */
public class JDKHttpClientPostTest {

  public static void main(String[] args) throws IOException, InterruptedException {
    doPost();
  }
  
  public static void doPost() {
    try {
      HttpClient client = HttpClient.newBuilder().build();
      HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create("https://tools.usps.com/go/ZipLookupAction.action"))
          //.header("Content-Type","application/x-www-form-urlencoded")
          .header("User-Agent", "HTTPie/0.9.2")
          .header("Content-Type","application/x-www-form-urlencoded;charset=utf-8")
          //.method("POST", HttpRequest.BodyPublishers.ofString("tAddress=1 Market Street&tCity=San Francisco&sState=CA"))
          //.version(Version.HTTP_1_1)
          .POST(HttpRequest.BodyPublishers.ofString("tAddress=" 
              + URLEncoder.encode("1 Market Street", "UTF-8") 
              + "&tCity=" + URLEncoder.encode("San Francisco", "UTF-8") + "&sState=CA"))
          //.POST(HttpRequest.BodyPublishers.ofString("tAddress=" + URLEncoder.encode("1 Market Street", "UTF-8") + "&tCity=" + URLEncoder.encode("San Francisco", "UTF-8") + "&sState=CA"))
          .build();
      HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
      System.out.println(response.statusCode());
      System.out.println(response.headers());
      System.out.println(response.body().toString());

    }
    catch(Exception e) {
      e.printStackTrace();
    }
  } 
}

4.2 HttpComponents

hc.apache.org, Apache出品

  • HttpClient进化而来
  • 是一个集成的Java HTTP工具包
    • 实现所有HTTP方法:get/post/put/delete
    • 支持自动转向
    • 支持https协议
    • 支持代理服务器等

HttpComponents GET 实例

 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
import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpComponentsGetTest {

    public final static void main(String[] args) throws Exception {
      
      CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)   //设置连接超时时间
                .setConnectionRequestTimeout(5000) // 设置请求超时时间
                .setSocketTimeout(5000)
                .setRedirectsEnabled(true)//默认允许自动重定向
                .build();
        
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        httpGet.setConfig(requestConfig);
        String srtResult = "";
        try {
            HttpResponse httpResponse = httpClient.execute(httpGet);
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                srtResult = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");//获得返回的结果                
                System.out.println(srtResult);
            }else
            {
                //异常处理
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


HttpComponents POST 实例

 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
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class HttpComponentsPostTest {

    public final static void main(String[] args) throws Exception {
      
      //获取可关闭的 httpCilent
        //CloseableHttpClient httpClient = HttpClients.createDefault();
      CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();
      //配置超时时间
        RequestConfig requestConfig = RequestConfig.custom().
                setConnectTimeout(10000).setConnectionRequestTimeout(10000)
                .setSocketTimeout(10000).setRedirectsEnabled(false).build();
         
        HttpPost httpPost = new HttpPost("https://tools.usps.com/go/ZipLookupAction.action");
        //设置超时时间
        httpPost.setConfig(requestConfig);
        
        //装配post请求参数
        List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>(); 
        list.add(new BasicNameValuePair("tAddress", URLEncoder.encode("1 Market Street", "UTF-8")));  //请求参数
        list.add(new BasicNameValuePair("tCity", URLEncoder.encode("San Francisco", "UTF-8"))); //请求参数
        list.add(new BasicNameValuePair("sState", "CA")); //请求参数
        try {
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,"UTF-8"); 
            //设置post求情参数
            httpPost.setEntity(entity);
            httpPost.setHeader("User-Agent", "HTTPie/0.9.2");
            //httpPost.setHeader("Content-Type","application/form-data");
            HttpResponse httpResponse = httpClient.execute(httpPost);
            String strResult = "";
            if(httpResponse != null){ 
                System.out.println(httpResponse.getStatusLine().getStatusCode());
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    strResult = EntityUtils.toString(httpResponse.getEntity());
                }
                else {
                    strResult = "Error Response: " + httpResponse.getStatusLine().toString();
                } 
            }else{
                 
            }
            System.out.println(strResult);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(httpClient != null){
                    httpClient.close(); //释放资源
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5 Java NIO 编程

  • 传统的TCP和UDP通讯属于 Blocking I/O
  • NIONon-Blocking I/O,非阻塞I/O,又名 New I/O)提供非阻塞通讯等方式,避免同步I/O通讯效率过低,一个线程可以管理多个连接,减少线程多的压力,不是真异步。
笔记
  • 并发编程的同步:是指多个线程需要以一种同步的方式来访问某一个数据结构。这里同步的反义词是非同步的,即线程不安全的。
  • 网络通讯的同步:是指客户端和服务端直接的通讯等待方式。这里的同步的反义词是异步,即无需等待另一端操作完成。
  • JDK 1.4引入,1.7升级 NIO 2.0 (包括了AIO)
  • 主要在java.nio包中
  • 主要类
    • Buffer 缓存区
    • Channel 通道
    • Selector 多路选择器

/java12/03.png
NIO 服务端-客户端通讯示意图

5.1 Buffer 缓冲区

Buffer 缓冲区:一个可以读写的内存区域

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  • 注意:StringBuffer 不是 Buffer缓冲区

四个主要属性

  • capacity 容量
  • position 读写位置
  • limit 界限
  • mark 标记,用于重复一个读/写操作

5.3 Channel 通道

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

5.4 Selector多路选择器

  • 每隔一段时间,不断轮询注册在其上的Channel
  • 如果有一个Channel有接入、读、写操作,就会被轮询出来
  • 根据SelectionKey可以获取相应的Channel,进行后续IO操作
  • 避免过多的线程
  • SelectionKey四种类型
    • OP_CONNECT
    • OP_ACCEPT
    • OP_READ
    • OP_WRITE
      /java12/04.png
      Selector多路选择器

5.5 实例

NioServer

  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
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServer {

    public static void main(String[] args) throws IOException {
      int port = 8001;
      Selector selector = null;
      ServerSocketChannel servChannel = null;
      
      try {
      selector = Selector.open();
      servChannel = ServerSocketChannel.open();
      servChannel.configureBlocking(false);
      servChannel.socket().bind(new InetSocketAddress(port), 1024);
      servChannel.register(selector, SelectionKey.OP_ACCEPT);
      System.out.println("服务器在8001端口守候");
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
      
      while(true)
      {
        try {
          selector.select(1000);
          Set<SelectionKey> selectedKeys = selector.selectedKeys();
          Iterator<SelectionKey> it = selectedKeys.iterator();
          SelectionKey key = null;
          while (it.hasNext()) {
            key = it.next();
            it.remove();
            try {
              handleInput(selector,key);
            } catch (Exception e) {
              if (key != null) {
                key.cancel();
                if (key.channel() != null)
                  key.channel().close();
              }
            }
          }
        } 
        catch(Exception ex)
        {
          ex.printStackTrace();         
        }
        
        try
        {
          Thread.sleep(500);
        }
        catch(Exception ex)
        {
          ex.printStackTrace();         
        }
      }
    }
    
    public static void handleInput(Selector selector, SelectionKey key) throws IOException {

    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
        SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        int readBytes = sc.read(readBuffer);
        if (readBytes > 0) {
          readBuffer.flip();
          byte[] bytes = new byte[readBuffer.remaining()];
          readBuffer.get(bytes);
          String request = new String(bytes, "UTF-8"); //接收到的输入
          System.out.println("client said: " + request);
          
          String response = request + " 666";
          doWrite(sc, response);
        } else if (readBytes < 0) {
          // 对端链路关闭
          key.cancel();
          sc.close();
        } else
          ; // 读到0字节,忽略
      }
    }
  }

  public static void doWrite(SocketChannel channel, String response) throws IOException {
    if (response != null && response.trim().length() > 0) {
      byte[] bytes = response.getBytes();
      ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
      writeBuffer.put(bytes);
      writeBuffer.flip();
      channel.write(writeBuffer);
    }
  }
}

/**
 * 
 * 
 * 服务器在8001端口守候
 * client said: f3e638a0-6324-408f-b0d8-3c87ef93133b
 * client said: ce4c70b2-5031-4d1e-a996-a2a99dc8899e
 * client said: 03ea5cde-ec89-4d4b-940a-0de89c885064
 * client said: ea56b109-82b9-4621-a223-7c3ef6103044
 * client said: 3377c7a3-e2cd-462d-80cc-13084d9e9daf
 * 
 * 
 */

NioClient

  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
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;

public class NioClient {

  public static void main(String[] args) {

    String host = "127.0.0.1";
    int port = 8001;

    Selector selector = null;
    SocketChannel socketChannel = null;

    try 
    {
      selector = Selector.open();
      socketChannel = SocketChannel.open();
      socketChannel.configureBlocking(false); // 非阻塞

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

    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    while (true) 
    {
      try 
      {
        selector.select(1000);
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> it = selectedKeys.iterator();
        SelectionKey key = null;
        while (it.hasNext()) 
        {
          key = it.next();
          it.remove();
          try 
          {
            //处理每一个channel
            handleInput(selector, key);
          } 
          catch (Exception e) {
            if (key != null) {
              key.cancel();
              if (key.channel() != null)
                key.channel().close();
            }
          }
        }
      } 
      catch (Exception e) 
      {
        e.printStackTrace();
      }
    }
  

    // 多路复用器关闭后,所有注册在上面的Channel资源都会被自动去注册并关闭
//    if (selector != null)
//      try {
//        selector.close();
//      } catch (IOException e) {
//        e.printStackTrace();
//      }
//
//    }
  }

  public static void doWrite(SocketChannel sc) throws IOException {
    byte[] str = UUID.randomUUID().toString().getBytes();
    ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
    writeBuffer.put(str);
    writeBuffer.flip();
    sc.write(writeBuffer);
  }

  public static void handleInput(Selector selector, SelectionKey key) throws Exception {

    if (key.isValid()) {
      // 判断是否连接成功
      SocketChannel sc = (SocketChannel) key.channel();
      if (key.isConnectable()) {
        if (sc.finishConnect()) {
          sc.register(selector, SelectionKey.OP_READ);          
        }         
      }
      if (key.isReadable()) {
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        int readBytes = sc.read(readBuffer);
        if (readBytes > 0) {
          readBuffer.flip();
          byte[] bytes = new byte[readBuffer.remaining()];
          readBuffer.get(bytes);
          String body = new String(bytes, "UTF-8");
          System.out.println("Server said : " + body);
        } else if (readBytes < 0) {
          // 对端链路关闭
          key.cancel();
          sc.close();
        } else
          ; // 读到0字节,忽略
      }
      Thread.sleep(3000);
      doWrite(sc);
    }
  }
}
/**
 * 
 * 
 * Server said : f3e638a0-6324-408f-b0d8-3c87ef93133b 666
 * Server said : ce4c70b2-5031-4d1e-a996-a2a99dc8899e 666
 * Server said : 03ea5cde-ec89-4d4b-940a-0de89c885064 666
 * Server said : ea56b109-82b9-4621-a223-7c3ef6103044 666
 * Server said : 3377c7a3-e2cd-462d-80cc-13084d9e9daf 666
 * 
 * 
 */

6 Java AIO 编程

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

6.1 主要类

  • AsynchronousServerSocketChannel 服务器接受请求通道
    • bind 绑定在某一个端口
    • accept 接受客户端请求
  • AsynchronousSocketChannel Socket通讯通道
    • read 读数据
    • write 写数据
  • CompletionHandler 异步处理类
    • completed 操作完成后异步调用方法
    • failed 操作失败后异步调用方法

6.2 三种 I/O 的区别

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

6.3 实例

AioServer

 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
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AioServer {

    public static void main(String[] args) throws IOException {  
      AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();   
        server.bind(new InetSocketAddress("localhost", 8001));  
        System.out.println("服务器在8001端口守候");
        
        //开始等待客户端连接,一旦有连接,做completed(AsynchronousSocketChannel channel, Object attachment)任务
        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {  
            @Override  
            public void completed(AsynchronousSocketChannel channel, Object attachment) {  
               server.accept(null, this); //持续接收新的客户端请求
               
                 ByteBuffer buffer = ByteBuffer.allocate(1024); //准备读取空间
                 //开始读取客户端内容,一旦读取结束,做 completed(Integer result_num, ByteBuffer attachment) 任务
                 channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                     @Override
                     public void completed(Integer result_num, ByteBuffer attachment) {
                         attachment.flip(); //反转此Buffer 
                         CharBuffer charBuffer = CharBuffer.allocate(1024);
                         CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
                         decoder.decode(attachment,charBuffer,false);
                         charBuffer.flip();
                         String data = new String(charBuffer.array(),0, charBuffer.limit());
                         System.out.println("client said: " + data);
                         channel.write(ByteBuffer.wrap((data + " 666").getBytes())); //返回结果给客户端
                         try{
                             channel.close();
                         }catch (Exception e){
                           e.printStackTrace();
                         }
                     }
      
                     @Override
                     public void failed(Throwable exc, ByteBuffer attachment) {
                         System.out.println("read error "+exc.getMessage());
                     }
                 });
                 

            }  
  
            @Override  
            public void failed(Throwable exc, Object attachment) {  
                System.out.print("failed: " + exc.getMessage());  
            }  
        });  

        while(true){
          try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
        }
    }  
}

/**
 * 
 * 
 * 
 * 服务器在8001端口守候
 * client said: 178a8702-cd42-4b5a-b95b-2c5647a8356a
 * 
 */

AioClient

 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
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.UUID;


public class AioClient {

  public static void main(String[] a) {
    try
    {
      AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
      
      //channel连接成功后,自动做channel.connect任务
      channel.connect(new InetSocketAddress("localhost", 8001), null, new CompletionHandler<Void, Void>() {

        public void completed(Void result, Void attachment) {
          String str = UUID.randomUUID().toString();
          
          //str向服务器写数据成功后,自动做 new CompletionHandler<Integer, Object>() 任务
          channel.write(ByteBuffer.wrap(str.getBytes()), null,
              new CompletionHandler<Integer, Object>() {

                @Override
                public void completed(Integer result, Object attachment) {
                  try {
                    System.out.println("write " + str + ", and wait response");
                    //等待服务器响应
                    ByteBuffer buffer = ByteBuffer.allocate(1024); //准备读取空间
                             //开始读取服务器反馈内容,一旦读取结束,做completed(Integer result_num, ByteBuffer attachment) 任务
                    channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                 @Override
                                 public void completed(Integer result_num, ByteBuffer attachment) {
                                     attachment.flip(); //反转此Buffer 
                                     CharBuffer charBuffer = CharBuffer.allocate(1024);
                                     CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
                                     decoder.decode(attachment,charBuffer,false);
                                     charBuffer.flip();
                                     String data = new String(charBuffer.array(),0, charBuffer.limit());
                                     System.out.println("server said: " + data);
                                     try{
                                         channel.close();
                                     }catch (Exception e){
                                       e.printStackTrace();
                                     }
                                 }
                  
                                 @Override
                                 public void failed(Throwable exc, ByteBuffer attachment) {
                                     System.out.println("read error "+exc.getMessage());
                                 }
                             });
                             
                    channel.close();
                  } catch (Exception e) {
                    e.printStackTrace();
                  }
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                  System.out.println("write error");
                }

              });
        }

        public void failed(Throwable exc, Void attachment) {
          System.out.println("fail");
        }

      });
      Thread.sleep(10000);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

/**
 * 
 * 
 * write 178a8702-cd42-4b5a-b95b-2c5647a8356a, and wait response
 * server said: 178a8702-cd42-4b5a-b95b-2c5647a8356a 666
 * 
 * 
 */

7 Netty 编程

  • Netty, http://netty.io
  • 最早由韩国 Trustin Lee 设计开发的,后来由 JBoss 接手开发,现在是独立的 Netty Project
  • 一个非阻塞客户端-服务端网络通讯框架
  • 基于异步事件驱动模型
  • 简化Java的 TCP 和 UDP 编程
  • 支持 HTTP/2, SSL 等多种协议
  • 支持多种数据格式,如 JSON 等

7.1 关键技术

7.1.1 通道 Channel

  • ServerSocketChannel/NioServerSocketChannel/…
  • SocketChannel/NioSocketChannel

7.1.2 事件 EventLoop

  • 为每个通道定义一个EventLoop,处理所有的I/O事件
  • EventLoop注册事件
  • EventLoop将事件派发给ChannelHandler
  • EventLoop安排进一步操作

7.1.3 事件

  • 事件按照数据流向进行分类
  • 入站事件:连接激活/数据读取/……
  • 出站事件:打开到远程连接/写数据/……

7.1.4 事件处理 ChannelHandler

  • Channel通道发生数据或状态改变
  • EventLoop会将事件分类,并调用ChannelHandler的回调函数
  • 程序员需要实现ChannelHandler内的回调函数
  • ChannelInboundHandler/ChannelOutboundHandler

7.1.5 ChannelHandler工作模式:责任链

  • 责任链模式:
    • 将请求的接收者连成一条链
    • 在链上传递请求,直到有一个接收者处理该请求
    • 避免请求者和接收者的耦合

/java12/05.png
责任链模式

  • ChannelHandler可以有多个,依次进行调用
  • ChannelPipeline作为容器,承载多个ChannelHandler

7.1.6 ByteBuf

  • 强大的字节容器,提供丰富API进行操作

7.1.7 实例

  • EchoServer
     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
    
    // EchoServer.java
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    import java.net.InetSocketAddress;
    
    public class EchoServer {
        public static void main(String[] args) throws Exception {
            int port = 8001;
            final EchoServerHandler serverHandler = new EchoServerHandler();
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                //ServerBootstrap是netty中的一个服务器引导类
                ServerBootstrap b = new ServerBootstrap();
                b.group(group)
                    .channel(NioServerSocketChannel.class)  //设置通道类型
                    .localAddress(new InetSocketAddress(port))  //设置监听端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { //初始化责任链
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(serverHandler); //添加处理类
                        }
                    });
    
                ChannelFuture f = b.bind().sync();  //开启监听
                if(f.isSuccess()){
                  System.out.println(EchoServer.class.getName() +
                            " started and listening for connections on " + f.channel().localAddress());
                }
    
                f.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully().sync();
            }
        }    
    }
    
    // EchoServerHandler.java
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandler.Sharable;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.CharsetUtil;
    
    public class EchoServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf in = (ByteBuf) msg;
            String content = in.toString(CharsetUtil.UTF_8);
            System.out.println("Server received: " + content);
    
            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();
        }
    }
    
    /**
     * 
     * 
     * MOOC1808.EchoServer started and listening for connections on /0:0:0:0:0:0:0:0:8001
     * Server received: Netty rocks!
     * 
     * 
     */
    
  • EchoClient
     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
    
    // EchoClient.java
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    import java.net.InetSocketAddress;
    
    public class EchoClient {
    
      public static void main(String[] args) throws Exception {
          String host = "localhost";
            int port = 8001;
    
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap b = new Bootstrap();
                b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                            throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
                ChannelFuture f = b.connect().sync();
                f.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully().sync();
            }
        }   
    }
    
    // EchoClientHandler.java
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandler.Sharable;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    public class EchoClientHandler
        extends SimpleChannelInboundHandler<ByteBuf> {
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
                    CharsetUtil.UTF_8));
        }
    
        @Override
        public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
            System.out.println(
                    "Client received: " + in.toString(CharsetUtil.UTF_8));
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx,
            Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    /**
     * 
     * 
     * 
     * Client received: Netty rocks! 666
     * 
     * 
     * 
     */
    

7.2 书籍推荐

  • 《Netty 实战》,Norman Maurer 著,何品 译,人民邮电出版社,2017.
  • 《Netty权威指南》,李林锋,电子工业出版社,2015

8 Java Mail 编程

/java12/06.png
Java Mail

8.1 Java Mail 服务器配置

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

8.2 Java Mail 工具包

  • javax.mail 包和javax.mail.internet 包
    1
    2
    3
    4
    5
    
    <dependency>
      <groupId>com.sun.mail</groupId>
      <artifactId>javax.mail</artifactId>
      <version>1.6.2</version>
    </dependency>
    

8.3 关键类

  • Session: 邮件会话 和HttpSession不同
  • Store: 邮件存储空间
  • Folder: 邮件文件夹
  • Message: 电子邮件
  • Address: 邮件地址
  • Transport: 发送协议类

8.4 实例

接收邮件

 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
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import java.util.*;

public class MailClientRecv {
  private Session session;
  private Store store;
  private String username = "ieblyang@163.com";
  private String password = "1234567899";
  private String popServer = "pop.163.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();
  }
}

发送邮件

  • 文本邮件

     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
    
    // messages/TextMessage.java
    package messages;
    
    import java.util.Date;
    import java.util.Properties;
    import javax.mail.Message;
    import javax.mail.Session;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeMessage;
    import java.io.FileOutputStream;
    
    public class TextMessage {
      public static MimeMessage generate() throws Exception {
        String from = "ieblyang@qq.com "; // 发件人地址
        String to = "ieblyang@163.com"; // 收件人地址
    
        String subject = "test";
        String body = "您好,这是来自一封ieblyang的测试邮件";
    
        // 创建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();
    
        // 把MimeMessage对象中的内容写入到文件中
        //msg.writeTo(new FileOutputStream("e:/test.eml"));
        return message;
      }
    }
    
    
  • 网页邮件

     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
    
    // messages/
    package messages;
    
    import java.util.Date;
    import java.util.Properties;
    import javax.mail.Message;
    import javax.mail.Session;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeMessage;
    import java.io.FileOutputStream;
    
    public class HtmlMessage {
      public static MimeMessage generate() throws Exception 
      {
        String from = "ieblyang@qq.com "; // 发件人地址
        String to = "ieblyang@163.com"; // 收件人地址
    
        String subject = "多附件邮件";        //邮件主题
        String body = "<a href=https://ieblyang.tech>" +
                "欢迎大家访问我的网站</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.setSentDate(new Date());
        // 设置邮件主题
        message.setSubject(subject);
        // 设置HTML格式的邮件正文
        message.setContent(body, "text/html;charset=gb2312");
        // 保存并生成最终的邮件内容
        message.saveChanges();
    
        // 把MimeMessage对象中的内容写入到文件中
        //msg.writeTo(new FileOutputStream("e:/HtmlMessage.eml"));
        return message;
      }
    }
    
    
  • 附件邮件

     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
    
    // messages/
    import java.io.FileOutputStream;
    import java.util.Properties;
    import javax.activation.DataHandler;
    import javax.activation.FileDataSource;
    import javax.mail.Message;
    import javax.mail.Session;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeBodyPart;
    import javax.mail.internet.MimeMessage;
    import javax.mail.internet.MimeMultipart;
    public class AttachmentMessage 
    {
      public static MimeMessage generate() throws Exception
      {
        String from = "ieblyang@qq.com "; // 发件人地址
        String to = "ieblyang@163.com"; // 收件人地址
    
        String subject = "多附件邮件";        //邮件主题
        String body = "<a href=https://ieblyang.tech>" +
                "欢迎大家访问我的网站</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/ieblyang1.jpg");
        MimeBodyPart attachPart2 = createAttachment("c:/temp/ieblyang2.jpg");
    
        //创建用于组合邮件正文和附件的MimeMultipart对象
        MimeMultipart allMultipart = new MimeMultipart("mixed");
        allMultipart.addBodyPart(contentPart);
        allMultipart.addBodyPart(attachPart1);
        allMultipart.addBodyPart(attachPart2);
    
        //设置整个邮件内容为最终组合出的MimeMultipart对象
        message.setContent(allMultipart);
        message.saveChanges();
    
        //message.writeTo(new FileOutputStream("e:/ComplexMessage.eml"));
        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;
      }
    }
    
  • 发送邮件

     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
    
    import javax.mail.*;
    import java.util.*;
    import messages.*;
    
    
    public class MailClientSend {
      private Session session;
      private Transport transport;
      private String username = "ieblyang@qq.com";
      private String password = "1234567899";
      private String smtpServer = "smtp.qq.com";
    
      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();
      }
    }