网络初始&网络编程


前言:

一、计算机网络的定义

随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同工作来完成
业务,就有了网络互连;也就是说 由计算机互连而成的通信网络。

计算机网络是计算机技术与通信技术发展相结合的产物。

网络互连就是为了多台计算机连接在一起,实现数据共享。

最本质的功能就是,资源数据共享,是最本质的一个功能。

网络通信:数据共享本质是网络数据传输,即计算机之间通过网络来传输数据 。

根据网络互连的规模不同,就可以划分为局域网and广域网(还有个城域网,相对于前两个而言重要性要逊色一点)。

二、局域网 and 广域网

局域网,Local Area Network,简称LAN ,一般覆盖范围在10km之内之内的计算机网络。个人,家庭,公司,校园…

广域网,Wide Area Network ,简称WAN,一般覆盖范围在100km之内之内的计算机网络。城与城之间,国家,全球…

网络初始&网络编程-LMLPHP

通过路由器,将多个局域网连接起来,在物理上组成很大范围的网络,就形成了广域网。广域网内部的
局域网都属于其子网。

lan口方向的是局域网,wan口是连接广域网的,wan是ip唯一的地址。ip地址分类,怎么划分,点击这个Ip地址 & 子网掩码 链接查看详细解说。

三、协议

协议就是网络中做核心的概念;协议是进行网络通信的必要环节。

协议简单来说就是通信的双方必须共同遵守的一组规范或约定。

网络上的传输本质就是传输一些电信号或者光信号,我们传输的数据是以二进制形式传输出去的,就可以使用高电平/低电平/高频率/低频率…来分别代表0/1,但是 还要明确约定组合起来0 1是啥意思。

上面所说的约定就是协议了,传输的双方约定好数据传输的格式,发送方按照格式构造数据,接收方按照格式来解析数据。我们协议是进行有效通信的必要条件。

3.1、协议的分层

在计算机网络的体系结构,就是指计算机网络的分层协议模型。最终体现为在网络上传输的数据包的格式 。

分层,就是把一个太复杂的大问题分解为相对简单的N个小问题看,依次解决每个小问题,合起来就形成了大问题的解,这种方法也可叫理解为“分而治之”,

在协议中就是把一个协议拆分成多个协议,让多个协议相互配合,根据功能和定位,来对协议进行拆分,起到协议分层的效果。

  • 协议分层两大好处

1、上层协议不必关注下层协议的细节;

接电话为例:在电话里说的人,不必知道电话机的工作原理,就能进行打电话操作。

2、任意一层的协议都可以灵活的替换;

在语言方面,我和另一个人打电话,可以使用 English;

在设备方面,我和另一个人打电话,可以使用座机也可以使用手机;

四、常见的协议模型 ☆

4.1、OSI/RM

OSI/RM,(Open System Interconnection/Reference Model) 开放系统互连/参考模型

这是国际标准化组织 (OSI )提出 的一个参考模型,但并没有在Internet上实装。

  • OSI 分为 7层协议模型:

网络初始&网络编程-LMLPHP

网络初始&网络编程-LMLPHP

OSI 这个东西本身就很抽象,我用一个现实生活中寄快递的栗子,帮助大家的理解。

4.2、TCP/IP ☆

Internet事实上使用的工业标准。

它分为四层协议模型,和五层协议模型

网络初始&网络编程-LMLPHP

TCP/IP的最底层,网络接口层是没有实际的内容的,但是它支持OSI/RM中的物理层和数据链路层全部协议。

采取折中的方法,综合了OSI/RM和TCP/IP的优点,就采用了五层协议。

因为网络接口层对应的是OSI/RM的第一二层,所以也包含了IEEE802.3; IEEE802.6 ; IEEE802.8 ;IEEE802.11 等标准。

在软件行业中,我们更关注的就是网络层以上的内容,相对于物理层来说纯硬件,硬件就不是程序员改管的范围了。

网络层:点对点,负责的是任一节点之间的数据传输,关注传输过程具体走了啥样的线路。(关注过程)例如在IP协议 中,通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网路层。

传输层:端对端,只关注结果(TCP/UDP)。

应用层:应用程序完成的逻辑,数据拿到了之后要干什么。

五、封装和分用

当协议分层之后,就涉及两个重要的过程就是封装和分用

网络初始&网络编程-LMLPHP

这里面的封装就是在每一层协议中封装好,传输给下一层协议的时候,头部加上该层对应的协议格式,在数据链路层中头部和尾部都要加上,以太网首部为了传输数据把数据交给相邻的设备(小区中的菜鸟驿站),以太网的尾部就是目的地相邻的设备(别人家小区的菜鸟驿站)。

这样子层层封装的过程理解为包装快递,分用就是拆快递。

封装分用不仅仅是存在于发送方和接收方,中间的设备(路由器/交换机)也会针对数据进行封装和分用。

通常情况下,交换机,只是分装分用到数据链路层就完了。A的数据发给交换机,交换机物理层进行处理,交给数据链路层,数据链路层针对这里数据解析并且重新打包(修改里面的MAC地址,也是决定接下来数据要发给那个相邻的设备)。

通常的情况下,路由器,只是封装分用到网络层就完了。A的数据发给路由器,路由器物理层进行处理,交给数据链路层,数据链路层再处理,交给网络层,网络层要根据这里的目的地址,来规划接下来的传输路线,规划好了之后再重新交给数据链路层和物理层进行封装。(在实际情况中,路由器/交换机,是可能解析到传输层甚至应用层的)


有了这些协议就能实现网络之间的通信。当然通信也会涉及到 ip地址 ,mac地址…

六、网络通信

在上面聊了这么多的协议,而这些协议就是为了让我们在网络中可以资源共享以及数据通信,并且提高计算机系统的可靠性和可用性。

网络互连的目的是进行网络通信,也即是网络数据传输,更具体一点,是网络主机中的不同进程间,基于网络传输数据。

​ 那么,在组建的网络中,如何判断到底是从哪台主机,将数据传输到那台主机呢?这就需要使用IP地址来标识。

ip地址描述了网络上的一个主机;

Port描述了主机上的一个程序。(跟URL里面的Port是一样的)

ip地址有详细解说,可以跳转 Ip地址 & 子网掩码 里面看。

七、网络编程

​ 网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。

​ 这里满足进程不同就行了,即便是同一个主机只要是不同的进程,基于网络来传输数据,也属于网络编程。但是这种情况只是在开发或者练习的时候在一个主机上进行来完成的网络编程,我们的主要目的是提供网络上不同主机,基于网络来传输数据来源。

  • 进程A:编程获取网络资源
  • 进程B:编程提供网络资源

​ 这里的获取和提供,也对应着两个概念,也就是客服端和服务器

  • 客户端:获取服务的一方进程
  • 服务器:提供服务的一方进程,可提供对外服务

7.1、Socket套接字

​ Socket是属于网络编程套接字里面的范畴,是操作系统提供给应用程序的一组 的 API(Application Programming Interface), 通过调用这些API就可以实现把数据交给传输层,传输层拿到数据程序员就主要关注的就是应用层,在应用层里面就会用到这些API。

​ Tomcat的这个过程,通过“网络” ,浏览器 和 Tomcat的底层就使用的了 Socket 编程,而我们就把操作系统里面提供的这些支持网络编程的 API 称为 Socket API

​ 在传输层中有很多的网络协议,这里涉及到最重要的两个协议就是 TCP ,UDP , 因此 Socket API 对应的也有两大类。在上面介绍协议模型的时候,就给 TCP 和 UDP 简单的解释了一下,这里我们再来看看 TCP 和 UDP 之间的具体的特点对照:

TCP:

  • 有连接
  • 可靠传输
  • 面向字节流
  • 全双工

UDP:

  • 无连接
  • 不可靠传输
  • 面向数据报
  • 全双工
1、有连接 vs 无连接:
	有连接:类似于打电话,要等到对方接起来才可以说话(发送数据资源,接受数据资源)
	无连接:发qq,不管你在不在线都会给你发送信息。
2、可靠传输 vs 不可靠传输
	可靠传输:淘宝给客服发信息,前面提示未读/已读,这样是能够知道对方是否知道的
	不可靠传输:信息发送出去,对方有没有收到,咱是不知道的
3、面向字节流 vs 面向数据报
	面向字节流:以字节为单位进行读写
	面向数据报:以一个报文为单位进行读写 (有具体的大小,在 Socket API 里面封装好的)
4、全双工 vs 半双工
	全双工:一个Socket对象既可以用来发送,也可以用来接收
	半双工:某个对象,只能用来发送,或者说是只能接收

7.2 UDP数据报套接字编程

UDP 面向数据报

DatagramSocket API ,DatagramSocket 是 UDP Sockt,用于发送和接收UDP数据报。

DatagramSocket 的构造方法:

DatagramSocket 方法:

DatagramSocket 这个类是描述了 Socket 对象,类比FIle对象,本质上是一个文件,类似于“网卡“这个设备的抽象。想要操作网卡,先创建 Socket 对象,通过操作Socket对象,就可以反应到网卡上,此处的 Socket可以视为”网卡的遥控器“。


DatagramPacket API ,DatagramPacket是UDP Socket发送和接收的数据报

DatagramPacket 构造方法:

DatagramPacket 方法:

DatagramPacket 对象描述了一个UDP数据报,是使用UDP传输数据的基本单位。

  • 基于UDP 服务端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {

      // 这里 创建了一个 socket 对象,绑定了本机中随机端口
    private DatagramSocket socket = null;

    // 这里是服务器上面的端口,由于ip没有指定,相当于0.0.0.0 服务器监听地址(绑定到当前主机上所有网卡上)
    public UdpEchoServer(int port) throws SocketException {
        // 切记这里的 端口,不能被别的进程绑定,一个进程对应一个端口,
        // 否则,抛出异常,构造socket 失败
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        // 服务器需要 7*24 开着
        while(true){
            /**
             * 这里面的操作设计三步:
             * 1、读取客户端发来的请求
             * 2、根据请求,计算出响应
             * 3、把响应返回给客服端
             */
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);
            // 这里需要把DatagramPacket 中的数据提取传来,转换成 String ,String 方便之后的代码处理
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            // 2.
            // 因为这个代码是 echo server ,故客服端发什么就响应什么。
            String response = process(request);

            // 3.
            // 数据从哪里来就返回哪里去
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}
  • 基于UDP客服端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    public UdpEchoClient(String ip,int port) throws SocketException {
        this.serverIp = ip;
        this.serverPort = port;
        socket  = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scan = new Scanner(System.in);
        while(true) {
            /**
             * 1、从控制台,读取用户输入的数据
             * 2、把数据构造层 UDP数据报,发送给服务器
             * 3、从服务器读取响应数据
             * 4、把响应数据进行解析,并显示
             */
            System.out.println("-> ");
            String request = scan.next();

            // 2、
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            // 3、
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);

            // 4、
            // 第三个参数 不能写成 requestPacket.getData().length ,这是把数据全读出来,
            // requestPacket.getLength() 是读有效数据
            String response = new String(responsePacket.getData(),0,requestPacket.getLength());
            System.out.printf("req: %s; resp: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }

}

7.3 TCP 流套接字编程

这个就是面向字节流,读写 Socket 的时候,不需要xxxPacket 这样的类,直接以字节为单位进行读写。

ServerSocket API

1、ServerSocket 是创建TUP服务器端Socket API。

  • 构造方法:
  • 方法:

网络初始&网络编程-LMLPHP

Socket API

2、Socket 是既给服务器使用也给客户端使用

  • 构造方法
  • 方法

在TCP的服务器里,需要用到两类 Socket:

1、ServerSocket

2、Socket(clientSocket)

这两个起到的作用也是完全不一样的。

serverSocket,就是负责建立连接的;Socket负责和客服端进行通信的

  • TCP 的服务端
package net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    private ServerSocket  serverSocket = null;

    // 此处是服务器要绑定的端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    // TCP 是需要建立连接才能通信
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true) {
            // 需要建立好连接,才能通信
            Socket clientSocket = serverSocket.accept();
            // 建立好了连接,需要和客户端进行通信
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) {
        // 客服端的ip ,和端口号
        System.out.printf("[%s:%d] 客户端建立连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());

        // 因为是面向字节流,所以要用InputStream和OutputStream
        try(InputStream inputStream = clientSocket.getInputStream()) {
            try(OutputStream outputStream = clientSocket.getOutputStream()) {
                Scanner scan = new Scanner(inputStream);
                while(true) {
                    /**
                     * 1、读取请求并解析
                     * 2、根据请求计算响应
                     * 3、把响应写回到客服端
                     */
                    // 1、

                    if (!scan.hasNext()) {
                        System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scan.next();

                    // 2、因为是回显模式,这节返回request就行
                    String response = process(request);

                    // 3、
                    //    可以使用 PrintWriter 来封装一下 OutputStream
                    PrintWriter writer = new PrintWriter(outputStream);
                    // 这里面的数据只是写到了内核中的接受缓存区中,等全部写完了,一起响应给客服端
                    writer.println(response);
                    // 为了保证写入的数据能够及时返回给客户端, 手动加上一个刷新缓冲区的操作
                    writer.flush();

                    System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close(); // 关闭资源可不能省!!!!!!
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
  • TCP 的客服端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    // 客服端操作系统会自动分配ip地址and端口号
    private Socket socket = null;
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 客服端在 new Socket的时候已经建立好连接了
        // 与服务器建立连接,首先肯定要知道服务器的ip地址和端口号
        socket  = new Socket("127.0.0.1",9090);
    }

    public void start() {
        System.out.println("客服端启动!");
        Scanner scan = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()) {
            try(OutputStream outputStream = socket.getOutputStream()) {
                Scanner respScan  = new Scanner(inputStream);
                while(true) {
                    /**
                     * 1、从控制台读取用户输入的数据
                     * 2、根据用户输入的数据,构造请求,发送给服务端
                     * 3、从服务器读取响应
                     * 4、把响应显示出来
                     */
                    System.out.println("-> ");
                    String request = scan.next();

                    // 2、
                    PrintWriter writer  = new PrintWriter(outputStream);
                    writer.println(request);
                    writer.flush();

                    //3、
                    String response = respScan.next();

                    // 4、
                    System.out.printf("req: %s, resp: %s\n", request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

网络初始&网络编程-LMLPHP

针对面对上诉问题,我们在处理客服端的时候,不影响accept()第二次调用就行了;那么我们可以使用多线程,一个线程进行accept()的处理,另一个线程进行客服端的多次请求处理,这样就需要给每个建立好连接的客服端都发送一个线程

  • 正确代码 加多线程
    // TCP 是需要建立连接才能通信
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true) {
            // 需要建立好连接,才能通信
            Socket clientSocket = serverSocket.accept();
            // 建立好了连接,需要和客户端进行通信
            // 需要改动这里,把每次建立好的连接,创建一个新的线程来处理
            Thread t = new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();
        }
    }

铁汁们,觉得笔者写的不错的可以点个赞哟❤🧡💛💚💙💜🤎🖤🤍💟,收藏关注呗,你们支持就是我写博客最大的动力!!!!

07-14 14:33