手动实现一个RPC框架(五):Socket网络通信

手动实现RPC系列文章

本系列文章,功能实现来自于 Github 作者 Java Guide的开源作品,我个人是选择边实现边学习的方式,本系列的文章是对Guide哥的作品地实现进行讲解和学习。( 作为我实现作品的笔记)

下面是Guide作品的连接,推荐大家可以直接进去下载并且学习。

Snailclimb/guide-rpc-framework: A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程。) (github.com)

以下是我本系列文章地专栏地址,也欢迎大家去阅读,因为记录的是本人的学习过程,所以可能有不严谨和出错的地方,望海涵。如果有能帮助到您的地方,我将万分荣幸。

手动实现RPC框架系列文章_种一棵橙子树的博客-CSDN博客


前言

前面已经提到过了,我们的RPC框架是通过远程调用来实现不同服务之间的调用的。那么肯定离不开网络传输。我们可以选用的网络传输的方式主要有两种,一种是通过基础的Socket进行网络编程,另一种则是通过Netty。

Socket是阻塞IO,性能较低,而且功能单一。Netty封装了NIO(异步非阻塞IO传输)。

所以接下来,我们会用两种方式来进行网络传输,先感受一下二者的区别,可以让我们更好的理解,为什么我们做的RPC框架要用Netty来进行网络传输。

一、什么是Socket?

Socket(套接字),学习过Java网络编程的人肯定对这个概念有所了解。简单地说,Socket就是一个包含了IP地址和Port端口号的一个概念,通过Socket,我们能找到正确的地址进行传输。

通信是双方的,所以我们要用互联网进行通信,至少需要一对套接字

1.运行于服务端的Server socket

2.运行于客户端的Client socket

在Java开发中,Socket主要有两个类,在Java.net包中

1.Socket 一般用于客户端 客端去请求服务端

2.Server Socket 一般用于服务端,服务端给予客户端响应。

二、Socket实现网络通信的过程

首先看一下这张图

 主要的过程可以总结为以下几步

1.建立并且启动服务端监听客户端的请求。

2.客户端对服务端请求,通过三次握手建立连接。

3.成功连接后,客户端与服务端之间进行数据传输

4.传输完毕,通过四次挥手断开连接,关闭资源。

 而在整个具体的流程中,客户端和服务端分别产生了哪些操作呢,我们往下看。

服务端

1.创建 ServerSocket 对象,绑定服务器自身的地址和端口号 server.bind(new InetSocketAddress(host,port))

2.通过accept()方法读取客户端的请求

3.建立连接后,通过输入流来读取客户端要发送的请求信息。

4.通过输出流向客户端发送响应信息。

5.连接完毕,关闭相关资源

客户端

1.创建Socket对象并且连接指定得服务器地址 ip 和 port 端口号:socket.connect(inetSocketAddress);

2.建立连接后,通过输出流向服务器发送请求。

3.通过输入流获取服务器响应的信息。

4.连接完毕 关闭资源

Socket 网络通信实现

服务端实现

public class HelloServer {
    private static final Logger logger = LoggerFactory.getLogger(HelloServer.class);

    public void start(int port) {
        //1.创建 ServerSocket 对象并且绑定一个端口
        try (ServerSocket server = new ServerSocket(port);) {
            Socket socket;
            //2.通过 accept()方法监听客户端请求
            while ((socket = server.accept()) != null) {
                logger.info("client connected");
                try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                     ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
                   //3.通过输入流读取客户端发送的请求信息
                    Message message = (Message) objectInputStream.readObject();
                    logger.info("server receive message:" + message.getContent());
                    message.setContent("new content");
                    //4.通过输出流向客户端发送响应信息
                    objectOutputStream.writeObject(message);
                    objectOutputStream.flush();
                } catch (IOException | ClassNotFoundException e) {
                    logger.error("occur exception:", e);
                }
            }
        } catch (IOException e) {
            logger.error("occur IOException:", e);
        }
    }

    public static void main(String[] args) {
        HelloServer helloServer = new HelloServer();
        helloServer.start(6666);
    }
}

前面我们提到,服务端通过 ServerSocket  来绑定自己的端口,然后通过 accept() 方法来监听客户端的请求。accept() 方法是阻塞方法,也就是说在执行到accept() 方法的时候当前线程会阻塞,直到接收到了客户端请求才会继续向下执行。后面的过程就是通过输入接受客户端的信息,然后再通过输出流把信息发送给客户端。

在上面的流程中,我们可以发现一个问题,那就是这样的代码一个线程只能处理一个客户端的连接。如果要处理多个客户端连接,那我们就要多创建线程。

 我们知道线程是宝贵的资源,如果我们为每一个socket都创建一个线程去执行,那样会造成资源的极大浪费。可能会有人想到用线程池来创建线程。诚然,用线程池我们可以对它预先分配资源,也不需要频繁的创建线程和销毁线程,还可以控制线程的数量。

但是这种方式始终是治标不治本,Socket的底层仍然是同步阻塞的IO模型,无法从根本上解决问题。

所以在JDK 1.4中引入了一种同步非阻塞的IO模型,NIO,NIO的使用相对复杂,然而我们可以使用基于NIO的网络编程框架Netty。这也是我们后面RPC框架使用的。后面会进行讲解。

客户端实现

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 16:56:00
 */
public class HelloClient {

    private static final Logger logger = LoggerFactory.getLogger(HelloClient.class);

    public Object send(Message message, String host, int port) {
        //1. 创建Socket对象并且指定服务器的地址和端口号
        try (Socket socket = new Socket(host, port)) {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            //2.通过输出流向服务器端发送请求信息
            objectOutputStream.writeObject(message);
            //3.通过输入流获取服务器响应的信息
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            return objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            logger.error("occur exception:", e);
        }
        return null;
    }

    public static void main(String[] args) {
        HelloClient helloClient = new HelloClient();
        helloClient.send(new Message("content from client"), "127.0.0.1", 6666);
        System.out.println("client receive message:" + message.getContent());
    }
}

客户端这里,需要创建Socket对象,并且在使用的时候要指定服务端的 IP地址和端口号。然后也是通过输入输出流来传递数据的。

发送消息的实体类

 

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 17:02:00
 */
@Data
@AllArgsConstructor
public class Message implements Serializable {

    private String content;
}

先运行服务端,然后运行客户端,进行测试 因为 服务端需要先启动,然后进行监听。

服务端输出

[main] INFO github.javaguide.socket.HelloServer - client connected
[main] INFO github.javaguide.socket.HelloServer - server receive message:content from client

客户端输出 

 client receive message:new content

完成了! 


 

总结

本篇文章,我们对Socket进行网络传输的方式进行了学习,同时我们也知道了Socket的缺点,所以下一篇文章,我们进行Netty网络框架的学习和使用。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值