zoukankan      html  css  js  c++  java
  • 基于 socket 手写一个 TCP 服务端及客户端

      通过 socket 实现一个 TCP 服务端与客户端,实现通过 TCP 协议进行消息收发。

      关键在 socket 的使用的理解上。

      socket 是对操作系统提供的协议栈的封装,底层调用的是操作系统提供的协议栈。

      当我们调用 ServerSocket 的 accept 方法时,线程阻塞。以 TCP 协议为例,直到网卡接收到一个三次握手的连接请求,网卡向 CPU 发送中断信号,CPU 调用中断处理程序唤醒我们阻塞在 accept 方法上的线程,进行连接处理。

      三次握手的过程是由协议栈完成的,我们在应用层编程无法感知。直到三次握手完成,协议栈将客户端信息与服务端信息封装在一个 Socket 对象中返回,我们通过该Socket 对象完成数据的收发。

      值得注意的是,在连接建立完成前,操作系统会为本次连接在内核空间开辟两个数据缓冲区:发送缓冲区与接收缓冲区。

      我们要做的是监听发送缓冲区是否有数据到达,以及将需要发送的数据写入发送缓冲区。

      至于网卡接收到的数据何时由操作系统拆包并写入接收缓冲区,以及我们写入发送缓冲区的数据何时会被封装为 TCP 报文发送给网卡是操作系统 OS 控制的,这对我们来说是透明的。不同的 OS 对此会有不同的实现,我们不需要关注这些细节(或者说想要关注也没办法介入)。

      本次实现是通过传统的 ServerSocket 建立服务端,并没有使用通道技术。也就是说是 BIO 的实现,当并发量比较大时可以采用 NIO 多路复用技术进行优化。这可以帮助我们节约线程数。

      对 TCP 报文进行分包有多种方式,本次实现使用的是最普适的方式,通过报文头添加报文长度字段进行分包,也就是与 HTTP 协议 Header 中的 Content-Length 相同的方式。

      测试时客户端发送的数据是一个序列化的对象,服务端对其进行反序列化并检查结果。 

      由于牵扯到线程的切换,本次实现并没有对代码结构进行提前设计,仅仅是简单的实现了数据收发功能。经过设计优化的代码将在下篇博客发出。

      服务端:

    /**
     * @Author Nxy
     * @Date 2020/3/21 17:16
     * @Description socket 服务端
     */
    public class BasicSeverDemo {
        public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket(80);
                System.out.println("server start!");
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            while (!Thread.currentThread().isInterrupted()) {
                Socket socket;
                BufferedInputStream in;
                BufferedOutputStream out;
                try {
                    //阻塞等待连接请求
                    socket = server.accept();
                    System.out.println("建立连接:" + socket.getInetAddress());
                    in = new BufferedInputStream(socket.getInputStream());
                    out = new BufferedOutputStream(socket.getOutputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("连接建立失败!");
                    continue;
                }
                byte[] result;
                try {
                    //阻塞等待接收请求数据
                    byte[] lengthByte = IOUtil.readBytesFromInputStream(in, 4);
                    //本次请求的长度
                    int length = ByteBuffer.wrap(lengthByte).getInt();
                    System.out.println("from server:" + length);
                    //读取指定长度字节
                    result = IOUtil.readBytesFromInputStream(in, length);
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
                //反序列化对象
                Invocation obj = null;
                try {
                    ByteArrayInputStream bis = new ByteArrayInputStream(result);
                    ObjectInputStream ois = new ObjectInputStream(bis);
                    obj = (Invocation) ois.readObject();
                    ois.close();
                    bis.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                } catch (ClassNotFoundException ex) {
                    ex.printStackTrace();
                }
                System.out.println(obj.getInterfaceName() + ":" + obj.getMethodName());
            }
        }

      客户端:

    /**
     * @Author Nxy
     * @Date 2020/3/21 17:54
     * @Description socket 客户端
     */
    public class BasicClientDemo {
        public static void main(String[] args) {
            Socket socket;
            BufferedOutputStream out;
            BufferedInputStream in;
            try {
                socket = new Socket("127.0.0.1", 80);
                out = new BufferedOutputStream(socket.getOutputStream());
                in = new BufferedInputStream(socket.getInputStream());
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            Object[] params = new Object[2];
            Class[] paramTypes = new Class[2];
            Invocation invocation = new Invocation(BasicClientDemo.class.getName(), "main", paramTypes, params);
            byte[] invocationBytes = toByteArray(invocation);
            int length = invocationBytes.length;
            try {
                System.out.println("from client:" + length);
                out.write(ByteBuffer.allocate(4).putInt(length).array());
                out.flush();
                out.write(invocationBytes);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

      工具类:

    public class IOUtil {
        /**
         * @Author Nxy
         * @Date 2020/3/21 20:21
         * @Param in:输入流,length:读取字节数
         * @Return
         * @Exception
         * @Description 从输入流读取指定长度字节的数据
         */
        public static byte[] readBytesFromInputStream(BufferedInputStream in,
                                                      int length) throws IOException {
            int readSize;
            byte[] bytes = null;
            bytes = new byte[length];
            long length_tmp = length;
            long index = 0;// start from zero
            while ((readSize = in.read(bytes, (int) index, (int) length_tmp)) != -1) {
                length_tmp -= readSize;
                if (length_tmp == 0) {
                    break;
                }
                index = index + readSize;
            }
            return bytes;
        }

        public static byte[] toByteArray(Object obj) {
            byte[] bytes = null;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(obj);
                oos.flush();
                bytes = bos.toByteArray();
                oos.close();
                bos.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return bytes;
        }
    }

      执行效果,服务端正常接收到数据并成功反序列化为对象:

  • 相关阅读:
    将.lib库文件转换成.a库文件的工具
    协议
    协议
    bzoj1066
    bzoj2668
    bzoj2245
    bzoj2324后续思考
    bzoj2324
    jsoi2014前两轮回眸
    bzoj1293
  • 原文地址:https://www.cnblogs.com/niuyourou/p/12542209.html
Copyright © 2011-2022 走看看