本文作者:乐字节-坑王老薛

这个问题,对于我们学习技术的开发者来说,有很多技术,就目前来说可能觉得都很复杂且功能强大,但是其实所有的这些技术都是由前人从最基础的功能慢慢努力日积月累才成就的,我们是站在了很多巨人的肩膀上前行,今天我们就来看看服务器是怎么慢慢的发展过来的吧。

通信的方式

  • 简单通信
  • 不同请求
  • 复杂请求

案例实操

简单通信

回顾 Socket 编程给我们最大的感受,是可以在多台电脑之间进行数据的传输,这就是网络编程的开端和基础,通过了解客户端和服务端之间的通信来更直观地了解 Web 的发展历程。

Client

/**
 * 客户端:向服务器发送请求,并发送简单的消息
 * @author 坑王老薛
 *
 */
public class Client {

    public static void main(String[] args) throws UnknownHostException, IOException {
        // 创建客户端 必须指定服务器+端口
        Socket client = new Socket("localhost", 8888);
        // 发送消息 请求资源
        // 获取输出流
        OutputStream os = client.getOutputStream();
        // 使用缓冲字符输出流
        BufferedWriter br = new BufferedWriter(new OutputStreamWriter(os));
        // 写出消息,发送内容
        String msg = "Hello, I am Client, I need some resources";
        br.write(msg);
        br.close();
    }

}

Server

/**
 * 服务端,接收客户端请求并给出简单的响应
 * @author 坑王老薛
 *
 */
public class Server {

    public static void main(String[] args) throws IOException{
        // 创建服务器,指定端口ServerSocket(int port)
        ServerSocket socket = new ServerSocket(8888);
        // 接收客户端连接
        Socket client = socket.accept();
        System.out.println("******************");
        // 获取数据的输入流
        InputStream is = client.getInputStream();
        // 使用缓冲字符输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String msg = "";
        while ((msg = br.readLine()) != null) {
            System.out.println(msg);
        }
        br.close();
    }

}

服务端控制台:

从上面的例子总结通信条件如下:

  1. 需要有服务器端(server):等待被请求,需要暴露 ip 和 port
  2. 需要有客户端(client):主动发起请求,知晓服务端的 ip 和 port
  3. 通信规则(协议):TCP/IP 协议

ip 用于定位计算机;端口号(定位程序),用于标识进程的逻辑地址,不同进程的标志;有效端口:0~65535,其中 0~1023 由系统使用或者保留端口,开发中建议使用 1024 以上的端口。

不同请求

Client

/**
 * 客户端:向服务器发送请求,发送不同的请求
 * @author 坑王老薛
 *
 */
public class Client {

    public static void main(String[] args) throws IOException {
        // 通过系统默认类型的 SocketImpl 创建未连接套接字
        Socket socket = new Socket();
        // 此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名
        SocketAddress address = new InetSocketAddress("localhost", 8898);
        // 将此套接字连接到服务器,并指定一个超时值。 或者不指定超时时间
        socket.connect(address, 1000);
        OutputStream os = socket.getOutputStream();
        os.write("time".getBytes());
        os.flush();
        socket.close();
    }

}

Server

/**
 * 服务端 
 * public class ServerSocketextends Object:此类实现服务器套接字。
 * 服务器套接字等待请求通过网络传入。
 * 它基于该请求执行某些操作,然后可能向请求者返回结果。
 * 
 * @author 坑王老薛
 *
 */
public class Server {
    public static void main(String[] args) throws IOException {
        // 创建绑定到特定端口的服务器套接字。
        ServerSocket server = new ServerSocket(8898);

        // Socket accept() 侦听并接受到此套接字的连接。
        Socket client = server.accept();
        System.out.println("接收到连接");

        InputStream is = client.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        byte[] req = new byte[1024];
        // 接收客户端请求
        int len = bis.read(req);
        String reqStr = new String(req, 0, len);
        System.out.println(reqStr);
        if (reqStr.equals("money")) {
            System.out.println("here's the money");
        } else if (reqStr.equals("time")) {
            System.out.println("you have so much time");
        }
        client.close();
        server.close();
    }
}

服务端控制台:

复杂请求

Client

/**
 * 客户端
 * 
 * @author 坑王老薛
 *
 */
public class Client {

    public static void main(String[] args) throws IOException {
        // 通过系统默认类型的 SocketImpl 创建未连接套接字
        Socket socket = new Socket();
        // 此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名
        SocketAddress address = new InetSocketAddress("localhost", 8898);
        // 将此套接字连接到服务器,并指定一个超时值。 或者不指定超时时间
        socket.connect(address, 1000);

        OutputStream os = socket.getOutputStream();
        os.write("money".getBytes());
        os.flush();
        // 接收响应, 显示结果
        InputStream is = socket.getInputStream();
        byte[] result = new byte[1024];
        int len = is.read(result);
        String resultStr = new String(result, 0, len);
        System.out.println(resultStr);
        socket.close();
    }

}

Server

/**
 * 服务端
 * @author 坑王老薛
 *
 */
public class Server2 {

    public static void main(String[] args) throws IOException {
        // 创建绑定到特定端口的服务器套接字。
        ServerSocket server = new ServerSocket(8898);

        // Socket accept() 侦听并接受到此套接字的连接。
        Socket client = server.accept();
        System.out.println("接收到连接");
        InputStream is = client.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        byte[] req = new byte[1024];
        // 接收客户端请求
        int len = bis.read(req);
        String reqStr = new String(req, 0, len);
        System.out.println(reqStr);
        // 将接收到的请求封装成对象, 传送给请求的类
        MyRequest request = new MyRequest();
        MyResponse response = new MyResponse();

        OutputStream os = client.getOutputStream();
        if (reqStr.equals("money")) {
            // 根据请求的信息, 构造处理的类
            MyServlet s1 = new ServletMoney();
            s1.service(request, response);
            // 通过client的响应, 将结果响应回客户端
            os.write("here's the money".getBytes());
            os.flush();
        } else if (reqStr.equals("time")) {
            // 根据请求的信息, 构造处理的类
            MyServlet s2 = new ServletTime();
            s2.service(request, response);
            // 通过client的响应, 将结果响应回客户端
            os.write("you have somuch time".getBytes());
            os.flush();
        }
        client.close();
        server.close();
    }

}

/*
 * 我是一个有要求的人,你请求的这个资源必须是满足我要求格式的类,作用:防止混乱,方便调用 这是我的标准
 */
interface MyServlet {
    void service(MyRequest req, MyResponse resp);
}

class ServletMoney implements MyServlet {
    /*
     * @see com.mage.server.MyServlet#service(com.mage.server.MyRequest, com.mage.server.MyResponse)
     */
    @Override
    public void service(MyRequest req, MyResponse resp) {
        // 做出力所能及的处理
    }
}

class ServletTime implements MyServlet {
    /*
     * @see com.mage.server.MyServlet#service(com.mage.server.MyRequest, com.mage.server.MyResponse)
     */
    @Override
    public void service(MyRequest req, MyResponse resp) {
        // 做出力所能及的处理
    }
}

/*
 * 请求信息都按规律封装在该对象
 */
class MyRequest {
}

class MyResponse {
}

服务端控制台: 客户端控制台:

随着客户需求越来越复杂,需要的功能越来越多,我们的服务器端需要处理的请求越来越多,需要区分不同的请求,还需要按照不同请求进行请求数据的提取以及资源的分配和运算还有逻辑的处理,最后还需要响应给客户端,这就使得服务器端代码越来越复杂,实现越来越困难

根据以往的经验,双方进行通信只需要遵循一定的规则就可以很明确地知道各部分数据的含义,于是出现了网络更上层的应用协议(HTTP 协议),规定服务器端和客户端通信的规则。

客户端请求服务器端和服务器端响应客户端,都按照固定的规则,那么接收请求和响应数据这部分操作就可以固定下来,交给特定的一段代码来执行,从而减少服务器端的代码量,于是慢慢演变出了我们现在使用的服务器

扩展~TCP/IP协议簇

概念

TCP/IP 协议簇(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。

TCP/IP 协议不仅仅指的是 TCP 和 IP 两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇,只是因为在 TCP/IP 协议中 TCP 协议和 IP 协议最具代表性,所以被称为TCP/IP协议。

TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。

应用层

应用层是直接为应用进程提供服务的,对于不同种类的应用程序会根据自己的需要来使用应用层的不同协议。比如邮件传输应用使用了 SMTP 协议、万维网应用使用了 HTTP 协议、文件传输应用使用了 FTP 协议、远程登录服务应用使用了有 TELNET 协议等等。

传输层

传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。

在传输层有两个性质不同的协议:

  • TCP(Transmission ControlProtocol,传输控制协议)
  • UDP(User Data Protocol,用户数据报协议)。

网络层

网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方。网络层可以进行网络连接的建立和终止以及IP地址的寻找等功能。

链路层

链路层用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。

传输示意图

根据以上概念以及 TCP/IP 四个层的介绍,我们可以来看看网络传输中 TCP/IP 协议到底是怎样一步一步地将数据进行传输的。