从 Socket 编程 到 OkHttp 框架

前言

最近在做一个项目的时候,因为项目要求跨域连接。所以,使用了Okhttp框架。其内部原理是基于 socket 网络编程的。因为自己在这方面比较薄弱,所以写这一篇文章进行相关的总结。

基础知识(参考 图解TCP/IP 与 深入理解计算机系统)

  1. TCP/IP 参考模型
    网络基础知识
  2. socket 套接字
    每个套接字都是连接的一个端点,有相应的套接字地址。由一个IP地址与16位的整数端口组成.
    一个连接由两端的套接字地址唯一确定。叫套接字对。
    如:(cliaddr:cliport, servaddr:servport)
    端口号分为:
    标准既定的端口号: 0~49151. 其中知名端口号由 0~1023 组成。FTP 一般使用 21号端口号,HTTP 通信一般使用 80 号端口号。 动态分配的端口号**: 49152~65535. 操作系统为之分配不同的端口号。然后应用程序使用时,由操作系统将连接建立。
  3. java 中的网络编程类
    InetAddress:用于标识网络上的硬件资源,主要是IP地址
    URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据
    Sockets:使用TCP协议实现的网络通信Socket相关的类
    Datagram:使用UDP协议,将数据保存在用户数据报中,通过网络进行通信。UDP协议中,使用 数据报 为传输单位。

java 网络编程类介绍

1. InetAddress

InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址。

1
2
3
4
5
6
7
8
9
10
11
12
//获取本机的InetAddress实例
InetAddress address =InetAddress.getLocalHost();
//获取计算机名
address.getHostName();
//获取IP地址
address.getHostAddress();
//获取字节数组形式的IP地址,以点分隔的四部分
byte[] bytes = address.getAddress();

//获取其他主机的InetAddress实例
InetAddress address2 =InetAddress.getByName("其他主机名");
InetAddress address3 =InetAddress.getByName("IP地址");

2. URL

URL(Uniform Resource Locator)统一资源定位符,表示Internet上某一资源的地址,协议名:资源名称

  1. 基础使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //创建一个URL的实例
    URL myBlog =new URL("https://3dot141.cn");
    URL url =new URL(myBlog,"/blogs/33521.html?username=3dot141#test");//?表示参数,#表示锚点
    url.getProtocol();//获取协议
    url.getHost();//获取主机
    url.getPort();//如果没有指定端口号,根据协议不同使用默认端口。此时getPort()方法的返回值为 -1
    url.getPath();//获取文件路径
    url.getFile();//文件名,包括文件路径+参数
    url.getRef();//相对路径,就是锚点,即#号后面的内容
    url.getQuery();//查询字符串,即参数
  2. 读取网页内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //使用URL读取网页内容
    //创建一个URL实例
    URL url =new URL("http://www.baidu.com");
    InputStream is = url.openStream();//通过openStream方法获取资源的字节输入流
    InputStreamReader isr =newInputStreamReader(is,"UTF-8");//将字节输入流转换为字符输入流,如果不指定编码,中文可能会出现乱码
    BufferedReader br =newBufferedReader(isr);//为字符输入流添加缓冲,提高读取效率
    String data = br.readLine();//读取数据
    while(data!=null){
    System.out.println(data);//输出数据
    data = br.readerLine();
    }
    br.close();
    isr.colose();
    is.close();

3. Socket

首先介绍下关于 linux 下的套接字连接原理,帮助理解

下面介绍java 下 Socket的使用

1.Socket 的构造方法

1
2
3
4
5
6
7
8
9
10
11
1)Socket()

2)Socket(InetAddress address, int port)throws UnknownHostException,IOException

// 设定远程服务器地址与客户端地址
3)Socket(InetAddress address, int port, InetAddress localAddr, int localPort)throws IOException

4)Socket(String host, int port) throws UnknownHostException,IOException

// 设定远程服务器地址与客户端地址
5)Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

2.获取Socket信息

1
2
3
4
5
6
7
8
9
10
11
1. getInetAddress():获得远程服务器的IP地址。

2. getPort():获得远程服务器的端口。

3. getLocalAddress():获得客户本地的IP地址。

4. getLocalPort():获得客户本地的端口。

5. getInputStream():获得输入流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。

6. getOutputStream():获得输出流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。

3.Socket 状态

  1. 关闭状态

    1
    2
    3
    4
    5
    6
    1. close()

    // 状态测试方法
    1. isClosed()
    2. IsConnected()
    3. isBound()
  2. 半关闭状态

    1
    2
    3
    4
    5
    6
    1. shutdownInput()
    2. shutdownOutput()

    // 状态测试方法
    1. isInputShutDown()
    2. isOutputShutdown()

4.Socket 使用实例

以上就是 Socket 类的基本方法。 下面让我们进入实战,来看一下,Socket 类如何使用

  1. 服务器端

    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
    /**
    * 基于TCP协议的Socket通信,实现用户登录,服务端
    */
    //1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
    ServerSocket serverSocket =newServerSocket(33521);//1024-65535的某个端口
    //2、调用accept()方法开始监听,等待客户端的连接
    Socket socket = serverSocket.accept();
    //3、获取输入流,并读取客户端信息
    InputStream is = socket.getInputStream();
    InputStreamReader isr =newInputStreamReader(is);
    BufferedReader br =newBufferedReader(isr);
    String info =null;
    while((info=br.readLine())!=null){
    System.out.println("我是服务器,客户端说:"+info);
    }
    socket.shutdownInput();//关闭输入流

    //4、获取输出流,响应客户端的请求
    OutputStream os = socket.getOutputStream();
    PrintWriter pw = new PrintWriter(os);
    pw.write("欢迎您!");
    pw.flush();


    //5、关闭资源
    pw.close();
    os.close();
    br.close();
    isr.close();
    is.close();
    socket.close();
    serverSocket.close();
  2. 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //客户端
    //1、创建客户端Socket,指定服务器地址和端口
    Socket socket =newSocket("localhost",33521);
    //2、获取输出流,向服务器端发送信息
    OutputStream os = socket.getOutputStream();//字节输出流
    PrintWriter pw =newPrintWriter(os);//将输出流包装成打印流
    pw.write("用户名:3dot141;密码:hahah");
    pw.flush();
    socket.shutdownOutput();
    //3、获取输入流,并读取服务器端的响应信息
    InputStream is = socket.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    String info = null;
    while((info=br.readLine())!null){
    System.out.println("我是客户端,服务器说:"+info);
    }

    //4、关闭资源
    br.close();
    is.close();
    pw.close();
    os.close();
    socket.close();
  3. 结果

    1
    2
    我是服务器,客户端说:用户名:3dot141;密码:hahah
    我是客户端,服务器说:欢迎您!
  4. 多线程中的运用

    • 服务器端创建ServerSocket,使用while(true)循环调用accept()等待客户端连接
    • 客户端创建一个socket并请求和服务器端连接
    • 服务器端接受请求,创建socket与该客户建立专线连接
    • 建立连接的两个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
      public class ServerThread implements runnable{
      //服务器线程处理
      //和本线程相关的socket
      Socket socket =null;
      //
      public ServerThread(Socket socket){
      this.socket = socket;
      }

      publicvoid run(){
      //服务器处理代码
      }
      }

      //服务器代码
      ServerSocket serverSocket =newServerSocket(33521);
      Socket socket =null;
      int count =0;//记录客户端的数量
      while(true){
      socket = serverScoket.accept();
      ServerThread serverThread =new ServerThread(socket);
      serverThread.start();
      count++;
      System.out.println("客户端连接的数量:"+count);
      }

4. UDP 编程

1. 简单介绍

UDP 是面向无连接的协议,反应迅速,适用于适时场景,但是丢包后不能发现。
用于 直播等网速要求较高的应用

DatagramSocket 端到端的通信类.

1
2
3
4
5
6
7
8
9
10
//本机地址
// 随机
DatagramSocket()
// 指定
DatagramSocket(int port, InetAddress)


// 发送与接收
send(DatagramPacket)
receive(DatagramPacket)

DatagramPacket 数据报, 为 IP 和 UDP 等网络层以上的包的单位 。虽然这些都是包,但不同的层拥有不同的称呼。数据链路层中 叫 , TCP 则表示 为 .
方法

1
2
3
4
5
6
7
8
9
10
11
// 构造方法
// 接收时
DatagramPacket(byte[] buf, int length);
// 发送时
DatagramPacket(byte[] buf, int length, InetAddress iAdrr, int Port);

// 使用方法
// 用于服务器获得 客户端地址
getAddress()
// 用于服务器获得 客户端接口
getPort()

2. 基本使用

  1. 服务器端

    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
    //服务器端,实现基于UDP的用户登录
    //1、创建服务器端DatagramSocket,指定端口
    DatagramSocket socket =new datagramSocket(33521);
    //2、创建数据报,用于接受客户端发送的数据
    byte[] data =newbyte[1024];//
    DatagramPacket packet =newDatagramPacket(data,data.length);
    //3、接受客户端发送的数据
    socket.receive(packet);//此方法在接受数据报之前会一致阻塞
    //4、读取数据
    String info =newString(data,o,data.length);
    System.out.println("我是服务器,客户端告诉我"+info);


    //=========================================================
    //向客户端响应数据
    //1、定义客户端的地址、端口号、数据
    // 这里也可以自己设置
    InetAddress address = packet.getAddress();
    int port = packet.getPort();
    byte[] data2 = "欢迎您!".geyBytes();
    //2、创建数据报,包含响应的数据信息
    DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
    //3、响应客户端
    socket.send(packet2);
    //4、关闭资源
    socket.close();
  2. 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //客户端
    //1、定义服务器的地址、端口号、数据
    InetAddress address =InetAddress.getByName("localhost");
    int port =33521;
    byte[] data ="用户名:3dot141;密码:hahah".getBytes();
    //2、创建数据报,包含发送的数据信息
    DatagramPacket packet = newDatagramPacket(data,data,length,address,port);
    //3、创建DatagramSocket对象
    DatagramSocket socket =newDatagramSocket();
    //4、向服务器发送数据
    socket.send(packet);


    //接受服务器端响应数据
    //======================================
    //1、创建数据报,用于接受服务器端响应数据
    byte[] data2 = new byte[1024];
    DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
    //2、接受服务器响应的数据
    socket.receive(packet2);
    String raply = new String(data2,0,packet2.getLenth());
    System.out.println("我是客户端,服务器说:"+reply);
    //4、关闭资源
    socket.close();

OkHttp 框架

在项目中,我对 OkHttp 进行了简单的封装,基本满足我在项目中的需要。
下面贴上我的 工具类

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
public class OkhttpUtil {
public static final MediaType JSON = MediaType.parse("application/json;charset=UTF-8");

public static String doGet(String url) throws IOException {
OkHttpClient client = new OkHttpClient();
Request get = new Request.Builder().url(url).build();
Response response = client.newCall(get).execute();
return response.body().string();
}

public static String doGet(String url, Map<String, String> map) throws IOException {
OkHttpClient client = new OkHttpClient();
String newUrl = url;
if (map != null) {
int loop = 0;
for (String key : map.keySet()) {
if (loop == 0) {
newUrl = newUrl + "?" + key + "=" + map.get(key);
} else {
newUrl = newUrl + "&" + key + "=" + map.get(key);
}
loop = 1;
}
}
Request get = new Request.Builder().url(newUrl).build();
Response response = client.newCall(get).execute();

return response.body().string();
}

public static String doPost(String url, String requestBody) throws IOException {
OkHttpClient client = new OkHttpClient();
Request post = new Request.Builder().url(url).post(RequestBody.create(JSON, requestBody)).build();
Response response = client.newCall(post).execute();
if (!response.isSuccessful()) {
throw new IOException("没能得到数据" + response);
}
return response.body().string();

}
}

如果有对 okhttp 框架感兴趣的,可以参阅下面的网址。我就不献丑了。
okhttp 源码解析
okhttp 使用教程

结语

路漫漫其修远兮,吾将上下而求索。
在程序员的道路上,我还只是一个刚上路的小学生,怀着对代码世界的向往,砥砺前行。

stay hungry, stay foolish
与诸君共勉。
您的每一次点赞,关注都是对我的一种激励。
我的个人博客 – killCode
谢谢。

0%