网络编程 -- 从 Socket 编程 到 OkHttp 框架

1,013 阅读8分钟

前言

最近在做一个项目的时候,因为项目要求跨域连接。所以,使用了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)地址。

//获取本机的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. 基础使用
  //创建一个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();//查询字符串,即参数
  1. 读取网页内容
  //使用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)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. getInetAddress():获得远程服务器的IP地址。

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

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

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

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

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

3.Socket 状态

  1. 关闭状态
1. close()

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

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

4.Socket 使用实例

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

  1. 服务器端
/**
 * 基于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();
  1. 客户端
//客户端
//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();
  1. 结果
我是服务器,客户端说:用户名:3dot141;密码:hahah
我是客户端,服务器说:欢迎您! 
  1. 多线程中的运用

    • 服务器端创建ServerSocket,使用while(true)循环调用accept()等待客户端连接
    • 客户端创建一个socket并请求和服务器端连接
    • 服务器端接受请求,创建socket与该客户建立专线连接
    • 建立连接的两个socket在一个单独的线程上对话
    • 服务器端继续等待新的连接
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 端到端的通信类.

//本机地址
// 随机
DatagramSocket()
// 指定
DatagramSocket(int port, InetAddress)


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

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

// 构造方法
// 接收时
DatagramPacket(byte[] buf, int length);
// 发送时
DatagramPacket(byte[] buf, int length, InetAddress iAdrr, int Port);

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

2. 基本使用

  1. 服务器端
//服务器端,实现基于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();
  1. 客户端
//客户端
//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 进行了简单的封装,基本满足我在项目中的需要。
下面贴上我的 工具类

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
谢谢。