网路编程

1、今日内容

  • 什么网络编程

  • 网络编程3要素(IP地址,端口号,协议)

  • 使用UDP协议实现网络编程

  • 使用TCP协议实现网络编程

  • 文件的上传

2、什么是网络编程?

网络编程主要解决的问题是:多个或者同设备间的数据的通讯,主要研究的就是进程间的通讯。

要实现网络编程有3个必要条件:
(1)IP地址:用来表示设备在网络中的唯一性。
(2)端口号:用来标识某一个进程在计算机中的唯一性。
(3)协议:定义数据传输的规则。常见的协议有UDP和TCP.

3、IP地址

IP地址可以划分为IPV4和IPV6。

IPV4

我们都知道计算机底层存储的都是二进制数据,因此ip地址也是一个二进制数据。ipv4版的ip地址是一个32位(也就是4个字节)的二进制数,通常被分割为4部分,每一部分占8位。(由于每一部分只占用1个字节,因此每一部分的取值范围是:0~255)各个部分使用 “ . ” 进行分割。如下图所示:01100100.00000100.00000101.00000110

但是由于二进制数在日常生活中使用起来并不是特别的方便,因此,后期在使用的时候会把每一部分转换成对应的十进制。这种计数方式被称之点分十进制

上面的ip地址如果使用点分十进制进行表示,那么就是:100.4.5.6 。即使,使用点分十进制的表示方式来表示一个ip地址,也并不是特别容易记忆。为了方便记忆,我们可以使用 “ 一连串用点分隔的字符 ” 来表示ip地址,比如: www.itcast.cn ;这一连串用点分隔的字符就是域名

后期可以使用域名访问指定的服务器,在进行访问的时候,首先需要经过DNS(域名解析服务器)服务器进行域名解析,解析完毕以后就可以直接通过ip地址访问对应的服务器了。

Ipv6

现今的互联网络发展蓬勃,截至2018年1月,全球上网人数已达40.21亿,IPv4仅能提供约42.9亿个IP位置。随着互联网的发展,IPV4在某一个时间点就会枯竭,为了解决ip地址枯竭问题,ipv4的下一代版本ipv6就诞生了。IPv6具有比IPv4大得多的地址空间。这是因为IPv6采用128位的地址,而IPv4使用的是32位。因此ipv6版的ip地址支持2^128(3.4 * 10 ^ 38)个ip地址。

IPv6二进位制下为128位长度,以16位为一组,每组以冒号":“隔开,可以分为8组,每组以4位十六进制方式表示。例如:2001:0db8:85a3:08d3:1319:8a2e:0370:7344 是一个合法的IPv6地址。类似于IPv4的点分十进制,同样也存在点分十六进制的写法,将8组4位十六进制地址的冒号去除后,每位以点号”."分组,例如:2001:0db8:85a3:08d3:1319:8a2e:0370:7344 则也可以标记为:2.0.0.1.0.d.b.8.8.5.a.3.0.8.d.3.1.3.1.9.8.a.2.e.0.3.7.0.7.3.4.4

同时IPv6在某些条件下可以省略:

(1)每项数字前面的0可以省略,省略后前面数字仍是0则继续,例如下组IPv6是等价的。

在这里插入图片描述

(2)可以用双冒号"::"表示一组0或多组连续的0,但只能出现一次:

在这里插入图片描述

4、常见的DOS命令

(1)ipconfig 查看本机的ip地址

(2)ping 检查本机和其他计算机的网络连通性

(3) **执行ipconfig命令以后得到的结果:**Windows IP 配置

以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::e009:947a:77b9:b3da%10 // // 本机在局域网的ipv6的ip地址
IPv4 地址 . . . . . . . . . . . . : 192.168.20.73     // 本机在局域网的ipv4的ip地址
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.20.1
C:\Users\itcast_hly>

%10 : 表示的含义就是这个ipv6的ip地址在网卡标号为10的网卡上可以使用。

(4)查看网卡的编号:
① cmd
② netsh
③ interface ipv6
④ 使用show joins命令

(5)ping命令的使用格式: ping ip地址/域名

**(6)特殊的ip地址:**127.0.0.1被称之本地回环地址,表示的就是本机。提供本地回环地址的目的:进行测试。

5、InetAddress类的使用

作用: 用来表示IP地址(因为Java语言是面向对象的语言,任何东西都可以看做成对象,ip地址也不列外)

需要掌握的内容:如何获取InetAddress的实例

public static InetAddress getByName(String host) ; // host参数可以是主机名也可以是ip地址的字符串表现形式

注意:这个主机名称尽量不要使用中文,如果使用中文,后期在使用dubbo这个框架的时候很有可能会出现问题。

6、端口号

端口号:用来标识某一个进程在计算机中的唯一性。端口号的取值范围:0~65535 ,0~1023之间的端口号用于一些知名的网络服务和应用(一般都是系统服务和应用),用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。

注意: 一般情况下一个应用程序占用一个端口。

7、协议UDP和TCP的区别(重点)

(1)协议:就是数据传输的规则。常见的协议: UDP和TCP

(2)UDP协议和TCP协议的区别:

UDP协议的特点:

​ ① 面向无连接
​ ② 传输数据的速度是比较快,但是传输数据有大小限制,一次最多传输64K
​ ③ 不安全的,数据有可能会丢失

  • 常见的应用场景:对讲机,发短信

TCP协议的特点:

​ ① 面向连接的协议
​ ② 传输数据的速度是比较慢的,传输数据没有大小限制
​ ③ 安全的,不会产生数据的丢失

  • 常见的应用场景:打电话

8、基于UDP协议的网络编程

基于UDP协议的网络编程:在进行通信的时候,使用的都是DatagramSocket来实现通讯的。数据在进行传输的时候传输的都是数据包,在Java中给我们提供了一个类用来表示这个数据包:DatagramPacket

(1)发送端的代码实现

实现步骤:

1、 创建DatagramSocket对象(无参的构造方法)
2、 创建DatagramPacket对象(主要是用来封装我们需要传输的数据)
3、 调用DatagramSocket对象中方法来完成数据的发生(send方法)
4、 释放资源

代码的实现

public class Send {
    public static void main(String[] args) throws IOException {
        //1.找码头(无参的构造方法)
        DatagramSocket ds = new DatagramSocket();

        //2.打包礼物(数据数组,数组长度,主机地址,主机端口)
        String str = "Hello接收端";
        byte[] bytes = str.getBytes();
        InetAddress byName = InetAddress.getByName("127.0.0.1");
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,byName,10000);

        //3.由码头发送包裹
        ds.send(dp);

        //4.付钱走羊
        ds.close();
    }
}

(2)接收端的代码实现

实现步骤:

1、 创建DatagramSocket对象(有参构造方法)
2、 创建一个DatagramPacket对象(接收发生端所传递过来的数据包)
3、 调用DatagramSocket对象中的方法(receive)接收数据
4、 解析数据包(就是从DatagramPacket对象中获取数据)
5、 释放资源

代码的实现:

public class Receive {
    //注意点:
    //1.要先运行接收端,再运行发送端
    //2.如果接收端再启动之后,没有接收到数据,那么会死等(阻塞).
    //3.在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节
    public static void main(String[] args) throws IOException {

        //1.找码头   --表示接收端从10000端口接收数据的
        DatagramSocket ds = new DatagramSocket(10000);
        //2.创建一个新箱子
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);

        //3.接受礼物,把礼物放到新的箱子中
        ds.receive(dp);

        //4.从新的箱子里面获取礼物
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));

        //5.拿完走羊
        ds.close();
    }
}

9、组播的实现(了解)

概述: 发送者发送的数据可以被小组内的所有接收者所接收。组播的接收范围介于单播和广播之间。

组播ip地址:224.0.0.0~239.255.255.255

要想实现组播,我们需要使用DatagramSocket的子类: MulticastSocket

(1)发送端的代码实现:

public class send {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        ds.send(dp);
        ds.close();
    }
}

(2)接收端的代码实现:

public class receive {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket(10000);
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        //把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        ms.receive(dp);
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));

        ms.close();
    }
}

10、广播的实现

要实现广播,在进行数据发送的时候就需要指定广播地址:255.255.255.255

具体的代码实现:

1、 发送端

public static void main(String[] args) throws IOException {
    DatagramSocket ds = new DatagramSocket();

    String s = "广播 hello";
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName("255.255.255.255");
    int port = 10000;
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

    ds.send(dp);

    ds.close();
}

(2)接收端

public static void main(String[] args) throws IOException {
    DatagramSocket ds = new DatagramSocket(10000);
    DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
    ds.receive(dp);

    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data,0,length));

    ds.close();
}

11、基于TCP协议实现网络编程

基于TCP协议实现网络编程思想:TCP协议在进行通讯的时候首先需要建立连接,就需要保证接收端(服务器端)首先启动起来。连接建立以后再进行输出传输的时候使用的io流实现数据的传输。要完成进程间的通讯,就需要创建两端(发送端和接收端),Java针对不同的两端提供了不同的类:
① 发送端所对应的类(Socket
② 接收端所对应的类(ServerSocket

(1)发送端的代码实现步骤:

​ ① 创建Socket对象
​ ② 获取输出流对象
​ ③ 写数据
​ ④ 释放资源

代码实现如下:

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost",10000);
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        os.close();
        socket.close();
    }
}

(2)接收端的代码实现步骤:

​ ① 创建ServerSocket对象
​ ② 监听客户端(阻塞式的)
​ ③ 获取输入流对象
​ ④ 读取数据
​ ⑤ 释放资源

代码的实现:

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();
        InputStream is = accept.getInputStream();
        int b;
        while((b=is.read())!=-1){
            System.out.print((char)b);
        }
        is.close();
        ss.close();
    }
}

12、TCP通讯的原理

(1)原理

① 服务端调用accept这个方法的时候是一个阻塞式。
② 客户端和服务端进行连接的时候需要经过3次握手,客户端和服务端断开连接的时候需要经过4次挥手。
read方法是一个阻塞式的方法,阻塞的前提:就是通道中没有可读数据了就阻塞了。当客户端和服务端连接断开以后阻塞结束。

(2)三次握手

3次握手的过程大致如下:

① 客户端到服务端: 我要连接
② 服务端到客户端: 好的,已经连接上了
③ 客户端到服务端: 收到,确认已连接上了

从底层数据传输的角度而言,总共进行3次数据的传输:

① 从客户端到服务端
② 从服务端到客户端
③ 从客户端到服务端

要想获取这些数据的传输过程,可以使用一些常见的抓包工具进行数据的抓取:wireshark

客户端代码

// 创建Socket对象
// 169.254.170.6这个地址是安装wireshark的时候产生的本地回环地址
Socket socket = new Socket("169.254.170.6" , 9999) ;   

// 让线程休眠60s
TimeUnit.SECONDS.sleep(60);

服务端代码

// 创建ServerSocket对象
 ServerSocket serverSocket = new ServerSocket(9999) ;
// 让线程休眠60s
 TimeUnit.SECONDS.sleep(60);

首先运行服务端,然后在运行客户端,在wireshark工具中捕获完整的通信过程,结果如下图所示:

在这里插入图片描述

①第一次握手

在这里插入图片描述

在第一次“握手"时,**客户端向服务端发送SYN标志位,目的是与服务端建立连接。Seq代表sequence number(发送数据流序号), Seq**的值是0,代表客户端第一次给服务端发送数据。另外Len=0也可以看出来是没有数据可供发送的。客户端仅仅发送一个SYN标志位到服端代表要进行连接。

说明:Seq的值是5,说明在数据流中曾经一共发送了 1, 2, 3,4 这4次数据。而在本次"握手"中,

② 第二次握手

在这里插入图片描述

第二次"握手"时,服务端向客户端发送 SYN ACK 标志位,其中ACK标志位表示是对收到的数据包的确认,说明服务端接收到了客户端的连接。**ACK**的值是1,表示服务端期待下一次从客户端发送数据流的序列号是1,而Seq=0代表服务端曾经并没有给客户端发送数据,而本次也没有发送数据,因为Len=0也证明了这一点。

③ 第三次握手

在这里插入图片描述

第三次“握手”时,客户端向服务端发送的ACK标志位为1, Seq的值是1。Seq=l代表这正是服务端所期望的Ack=1。Len=0说明客户端这次还是没有向服务端传递数据,而客户端向服务端发送ACK 标志位为1的信息,说明客户端期待服务端下一次传送的Seq的值是1。

在这里插入图片描述

(3)四次挥手

客户端与服务端在断开连接的时候需要进行4次"挥手",4次"挥手"的过程如下:

① 客户端到服务端:我关了
② 服务端到客户端:好的,收到
③ 服务端到客户端:我也关了
④ 客户端到服务端:好的,收到

抓包图示
在这里插入图片描述

首先运行服务端,然后在运行客户端,在wireshark工具中捕获完整的通信过程,结果如下图所示:

第一次"挥手"如下图所示
在这里插入图片描述

在第一次"挥手"时,客户端到服务器发送标志位FIN ACK,告知服务端客户端关闭了。Seq=1表示本次数据流的序号为1,Ack=1表示客户端期望服务端下一次发送的数据流的序号为1。len=0,说明没有数据传输到服务端。

第二次"挥手"如下图所示
在这里插入图片描述

在第二次"挥手"时,服务端向客户端发送标志位ACK,Seq=1代表的正是客户端想看的Ack=1。Ack=2表示服务端期望下一次客户端发送的数据流的序号为2。len=0,说明没有数据传输到客户端。

第三次"挥手"如下图所示
在这里插入图片描述

在第三次"挥手"时,服务端向客户端发送标志位FIN ACK,告知客户端服务端关闭了。Seq=1代表的正是客户端想看的Ack=1。Ack=2表示服务端期望下一次客户端发送的数据流的序号为2。len=0,说明没有数据传输到客户端。

第四次"挥手"如下图所示
在这里插入图片描述

在第四次"挥手"时,客户端向服务端发送标志位ACK,告知服务端客户端已经收到服务端关闭信息。Seq=2代表的正是服务端想看的Ack=2,ACK=2表示客户端期望下一次服务端发送的数据流的序号为2。

连接断开的过程总结:
在这里插入图片描述

13、TCP的练习

需求:客户端向服务端发送数据,服务端接收到数据以后给客户端进行反馈,客户端去读取反馈在控制台进行输出。

客户端的代码:

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost",10000);

        //建立网络输入流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("hello你好");
        bw.flush();
        //bos.close();如果在这里关流,会导致整个socket都无法使用
        socket.shutdownOutput();//给服务器一个结束标记,告诉服务器文件已经传输完毕

        //建立网络输出流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str;
        while ((str=br.readLine())!=null){
            System.out.println(str);
        }
        br.close();
        socket.close();
    }
}

服务器端的代码:

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();

        //建立网络输出流
        BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
        String str;
        while ((str=br.readLine())!=null){
            System.out.println(str);
        }
        //br.close();//如果在这里关流,会导致整个socket都无法使用

        //建立网络输入流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("我已收到!");
        bw.flush();
        bw.close();

        accept.close();
        ss.close();
    }
}

14、TCP的练习2

文件上传的原理:

img
代码1

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10000);
        //是本地的流,用来读取本地文件的.
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D://111.png"));

        //建立网络输出流(字节)
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        int b;
        while((b=bis.read())!=-1){
            bos.write(b);
        }
        bos.flush();
        //bos.close();//如果在这里关流,会导致整个socket都无法使用
        //给服务器一个结束标记,告诉服务器文件已经传输完毕
        socket.shutdownOutput();


        //建立网络输入流(字符流)。作用读取服务器响应的数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str;
        while((str=br.readLine())!=null){
            System.out.println(str);
        }
    }
}

服务器

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();

        //获取网络中的输入流
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        //获取本地输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("I:\\heima_idea1\\project01\\day17Socket\\Source\\"+UUID.randomUUID()+".png"));
        int b;
        while((b=bis.read())!=-1){
            bos.write(b);
        }

        //获取网略输出流(字符流)
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("上传成功");
        bw.flush();
        bw.close();

        bos.close();
        bis.close();
        accept.close();
        ss.close();

    }
}
代码2

改进:服务器端添加while(true){可以接收多个服务器上传文件}

改进:服务器保存文件,文件名修改为UUID,不会出现文件覆盖。

服务器

/**
 * 改进:服务器端添加while(true){可以接收多个服务器上传文件}
 */
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        while (true){
            Socket accept = ss.accept();
            //获取网络中的输入流
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //获取本地输出流
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("I:\\heima_idea1\\project01\\day17Socket\\Source\\"+UUID.randomUUID()+".png"));
            int b;
            while((b=bis.read())!=-1){
                bos.write(b);
            }

            //获取网略输出流(字符流)
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.flush();
            bw.close();

            bis.close();
            bos.close();
            accept.close();
            ss.close();
        }
    }
}
代码3

在这里插入图片描述

线程类

public class ThreadSocket implements Runnable{
    private Socket acceptSocket;

    public ThreadSocket(Socket acceptSocket) {
        this.acceptSocket = acceptSocket;
    }

    @Override
    public void run() {

        BufferedInputStream bis=null;
        BufferedOutputStream bos=null;
        BufferedReader br = null;
        try {
            //获取网络中的输入流
            bis = new BufferedInputStream(acceptSocket.getInputStream());
            //获取本地输出流
            bos = new BufferedOutputStream(new FileOutputStream("I:\\heima_idea1\\project01\\day17Socket\\Source\\"+ UUID.randomUUID()+".png"));
            int b;
            while((b=bis.read())!=-1){
                bos.write(b);
            }

            //获取网略输出流(字符流)
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上传成功");
            bw.flush();
            bw.close();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


服务器

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        while (true){
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            new Thread(ts).start();
        }
    }
}

代码4

改进:添加线程池。

服务器端

/**
 * 改进:添加线程池
 */
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,   //线程池的总数量
                60,   //临时线程空闲时间
                TimeUnit.SECONDS, //临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );

        while (true){
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
    }
}

15、服务端代码问题分析

(1)服务器只能监听一个客户端,没有办法监听多个客户端

解决方案:就是使用循环进行改进

(2) 文件名称重复问题

解决该问题的思想:保证文件名称唯一即可

如何保证唯一:可以使用JDK所提供的UUID这个类中的public static UUID randomUUID()

(3) 服务器的效率太低

解决方案:针对每一个客户端需要去开启一个线程

虽然使用多线程可以提高服务器端的效率,但是针对每一个客户端都开启一个线程也会造成资源的浪费。为了解决服务器的资源可以线程池进行改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值