欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java笔记 —— 网络编程

程序员文章站 2022-03-05 15:41:00
...

计算机网络的定义

计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

网络通信协议的分类:

网络通信协议,可以比喻成大家都说普通话,所以我们可以正常的交流。但如果我们每个人都说一种方言,那么就会导致我们听不懂对方的意思,也无法交流。

1.OSI的体系结构
(从高到低)应用层—表示层—会话层—运输层—网络层—数据链路层—物理层
2.TCP/IP的体系结构
应用层——运输层(TCP或UDP)——网际层IP——网络接口层
3.五层协议的体系结构(两个协议的折中结构)
应用层——运输层——网络层——数据链路层——物理层

网络编程的目的:

进行数据交换,信息交流等,完成不同ip地址的主机之间的通信

如果实现网络中的主机之间的相互通信

  1. 进行通信的主机,双方的ip地址和端口号
  2. 网络协议,比如TCP/IP协议

利用ip地址在网络中找到这台主机,然后用端口号找到具体的应用程序

IP

  1. ip地址是Internet上的计算机的唯一标识,也是进行通信的实体,借助ip地址可以唯一定位一台网络上的计算机
  2. 本机localhost是127.0.0.1
  3. IP地址的分类方式为IPV4和IPV6
  4. 通常我们会用域名来取代具体的ip地址,这样方便记忆
    比如在cmd窗口ping www.baidu.com
    Java笔记 —— 网络编程
    实际上ping的还是百度的ip地址,但是用域名方便了我们记住
    输入一个主机的域名时,域名服务器(DNS)负责将域名转化成IP地址,从而连接到对应的主机
//InetAddress类  表示Internet协议(IP)地址

package TCPDemo;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressDemo1 {
    public static void main(String[] args) {
        try{
            //查询本机地址
            InetAddress inetAddress1 = InetAddress.getByName("127.0.0.1");
            System.out.println(inetAddress1);

            InetAddress inetAddress2 = InetAddress.getByName("localhost");
            System.out.println(inetAddress2);

            InetAddress inetAddress3 = InetAddress.getLocalHost();
            System.out.println(inetAddress3);

            //查询网站ip地址
            InetAddress inetAddress4 = InetAddress.getByName("www.baidu.com");
            System.out.println(inetAddress4);  //www.baidu.com/14.215.177.39

            //常用方法
            //获取该网站的ip地址
            System.out.println(inetAddress4.getHostAddress()); //14.215.177.39
            //获取该网站的域名
            System.out.println(inetAddress4.getHostName()); //www.baidu.com

        }catch(UnknownHostException e){
            e.printStackTrace();
        }
    }
}

结果为
Java笔记 —— 网络编程

端口号

  1. 端口号用来标识正在计算机上运行的进程(程序),不同的进程有不同的端口号
  2. 端口是一个 16 位的整数,范围是0~65535
  3. TCP和UDP各有65535个端口号。TCP于UDP可以取一样的端口号,是因为在两个协议下。但是在同一个协议下,端口号不能重复
  4. 端口号的分类
    (1)公认端口 0~1023
    公认端口一般与明确表明了某种服务的协议绑定,如:80端口对应HTTP
    (2)注册端口 1024~49151
    注册端口是用来分配给用户和程序的
    (3)动态 / 私有端口 49152~65535
    理论上,不会给服务提供这些端口

可以通过 netstat -ano 查看所有的端口
可以通过 netstat -ano|findstr “端口号” 查看指定的端口

package TCPDemo;
import java.net.InetSocketAddress;
public class InetSocketAddressDemo1 {
    public static void main(String[] args) {
        InetSocketAddress address1 = new InetSocketAddress("127.0.0.1",8080);
        InetSocketAddress address2 = new InetSocketAddress("localhost",8080);
        System.out.println(address1); //  /127.0.0.1:8080
        System.out.println(address2); //  localhost/127.0.0.1:8080
        System.out.println(address1.getAddress()); // /127.0.0.1
        //获取地址
        System.out.println(address1.getHostName()); // 127.0.0.1
        //获取端口
        System.out.println(address1.getPort()); // 8080
    }
}

TCP与UDP

TCP

  1. 类似于打电话,需要通信双方建立稳定的连接,任何一方掉线都会导致通信的结束
  2. TCP的客户端和服务端有着明确的界限

TCP的服务端和客户端建立连接的过程可以看这篇博客
java通信一:Socket通信原理简单理解

TCP的三次握手(建立连接)

这里用B站up @遇见狂神说的比喻,我觉得很生动形象
A是客户端,B是服务端
A:你瞅啥
B:瞅你咋地
A:打一架
翻译一下也就是
A先跟B沟通,希望建立连接
然后B要回应A,可以建立连接,然后确定A还在不在
A再回应B
这样三次握手,就可以建立连接了

TCP的四次挥手(断开连接)

A:我要断开连接了
B:我知道你要断开了(询问第一遍)
B:你真的要断开吗(再次确认一遍)
A:我真的要断开了

————————

UDP

  1. 类似于发短信,不需要建立稳定的连接,不管对方有没有准备好,都可以直接发给对方
  2. UDP的客户端和服务端没有明确的界限
  3. UDP不用连接,但是需要知道对方的地址

TCP实例

实例一、传输字符串

客户端将字符串hello传入到服务端

package TCPDemo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

//客户端
public class TCPClientDemo1 {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            // 1.要知道服务器的地址,端口号
            InetAddress serverIP = InetAddress.getByName("127.0.0.1");
            int port = 9999;
            // 2.创建一个socket连接
            socket = new Socket(serverIP,port);
            // 3.发送消息 用IO流的方式
            /*
            客户端上的使用
            1.getInputStream方法可以得到一个输入流,
                客户端的Socket对象上的getInputStream方法得到输入流其实就是从服务器端发回的数据。
            2.getOutputStream方法得到的是一个输出流,
                客户端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给服务器端的数据。
             */
            OutputStream os = socket.getOutputStream();
            os.write("hello".getBytes());

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

package TCPDemo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

//服务端
public class TCPServerDemo1 {
    public static void main(String[] args) {
        //在这里初始化是为了在finally中关闭资源
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            //1. 服务端首先要有一个地址
            //这里的端口号是自己编的,ip地址用的本地地址127.0.0.1
            serverSocket = new ServerSocket(9999);

            //2. 等待客户端的连接
            //这里的socket对象和客户端里面的socket对象是一个对象,只不过写到两个类里面
            //accept()方法是监听客户端连接过来的socket,并返回一个socket
            //服务端与客户端建立连接,用的是这一个socket连接,所以两个对象相同
            socket = serverSocket.accept();

            // 3.读取客户端的消息
            /*
            服务器端上的使用
            1.getInputStream方法得到的是一个输入流,
                服务端的Socket对象上的getInputStream方法得到的输入流其实就是从客户端发送给服务器端的数据流。
            2.getOutputStream方法得到的是一个输出流,
                服务端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给客户端的数据。
             */
            is = socket.getInputStream();

            //具体读取数据有两种方式
            //方式一,常规的,只用InputStream
            /*
                byte[] buffer = new byte[1024];
                int len;
                //将数据读入缓冲区数组buffer中,再将buffer数组转为字符串
                while((len=is.read(buffer))!=-1){
                    String s = new String(buffer, 0, len);
                    System.out.println(s);
                }
             */

            //方式二,用ByteArrayOutputStream
            //is.read(buffer)将客户端的数据读入到buffer缓冲数组中
            //再用ByteArrayOutputStream将buffer数组中的数据写入自己的字节数组缓冲区
            //这样做的好处是可以避免乱码
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while((len=is.read(buffer))!=-1){
                baos.write(buffer,0,len);
            }
            System.out.println(baos.toString());


            /*
             //这样可以一直接收客户端发来的消息
            while(true){
                socket = serverSocket.accept();
                is = socket.getInputStream();
                baos = new ByteArrayOutputStream();

                byte[] buffer = new byte[1024];
                int len;
                while((len=is.read(buffer))!=-1){
                    baos.write(buffer,0,len);
                }
                System.out.println(baos.toString());
            }
             */


        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            //关闭资源
            //注意关闭的顺序
            if(baos!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }
    }
}

运行时,先运行服务端
后运行客户端
最后在服务端的输出窗口看到字符串
Java笔记 —— 网络编程
将服务端的代码改成while循环结构,就可以让服务端一直接收客户端发来的消息,客户端运行几次,服务端打印几条消息
Java笔记 —— 网络编程

实例二、传输文件
package TCPDemo;

/**
 * 这是客户端的代码,作用是将图片文件picture.jpg传入服务端
 * 文件传入完毕后,立刻开始接收服务端传来的消息
 */


import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TCPClientDemo2 {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket链接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);
        //2.创建一个输出流
        OutputStream os = new FileOutputStream("picture/picture.jpg");

        //3.读取文件
        FileInputStream fis = new FileInputStream("picture/picture.jpg");
        //4.写出文件
        byte[] buffer = new byte[1024];
        int len;
        while((len=fis.read(buffer))!=-1){
            os.write(buffer,0,len);
        }

        //通知服务器,我已经传输完毕了
        socket.shutdownOutput();

        //确定服务器接收完毕,才能断开连接
        InputStream inputStream = socket.getInputStream();
        //用ByteArrayOutputStream将inputStream里面的数据写出来
        //ByteArrayOutputStream里面的缓冲区是字节数组,这样保证不会格式转换错误
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int len1;
        while((len1=inputStream.read(bytes))!=-1){
            baos.write(bytes,0,len1);
        }
        System.out.println(baos.toString());


        //5.关闭流
        baos.close();
        inputStream.close();
        fis.close();
        os.close();
        socket.close();


    }
}

package TCPDemo;

/**
 * 这是服务端的代码,作用是接收客户端传来的文件
 * 然后将文件写入本地文件夹当中
 * 文件写入完毕后,给客户端发送消息
 */

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerDemo2 {
    public static void main(String[] args) throws IOException {
        //1.创建服务
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.监听客户端的连接
        Socket socket = serverSocket.accept();

        //获取输入流
        InputStream is = socket.getInputStream();
        //文件输出
        FileOutputStream fos = new FileOutputStream(new File("picture/picture1.jpg"));
        byte[] buffer = new byte[1024];
        int len;
        while((len=is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }

        //通知客户端我接收完毕了
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("ok".getBytes());

        //关闭资源
        outputStream.close();
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

运行结果
Java笔记 —— 网络编程
Java笔记 —— 网络编程

UDP实例

package TCPDemo;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;


//UDP不需要连接服务器,所以这里也没有区分Client和Server
public class UDPDemo1 {
    public static void main(String[] args) throws IOException {
        // 1.建立一个Socket连接
        //如果这里DatagramSocket socket = new DatagramSocket(8080);
        //那么UDPDemo2也可以发送包到这个8080端口,然后UDPDemo1也可以接收包
        //所以说UDP没有明显区分客户端和服务端
        DatagramSocket socket = new DatagramSocket();

        // 2. 建立一个包
        String msg = "hello";

        //发送给谁
        InetAddress localhost = InetAddress.getByName("localhost");
        int port = 9090;

        // 参数含义:第一个参数是数据,中间两个参数是数据长度的起点与终点,最后两个参数是要发送给谁
        DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,localhost,port);

        // 3. 发送包
        socket.send(packet);

        //这段代码的主体步骤是第一、三步,即建立连接然后发送包
        //中间的步骤都是在创建包

        // 4.关闭流
        socket.close();

    }
}

package TCPDemo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//这里用来接收UDPDemo1 发送的包,但是这里不是服务器
//同样可以用UDPDemo2 来给UDPDemo1 发送包
//UDPDemo2 相当于一直在监听状态,直到UDPDemo1将包发送过来
public class UDPDemo2 {
    public static void main(String[] args) throws IOException {
        //开放端口,如果不建立Socket连接,那么UDPDemo1怎么发包都没用,因为没有人接收
        DatagramSocket socket = new DatagramSocket(9090);
        //接收数据包
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

        socket.receive(packet); //阻塞接收,也就说直到成功接收前,都一直存活

        //获取发送方的ip地址
        System.out.println(packet.getAddress().getHostAddress());
        //输出接收到的包里面的数据
        System.out.println(new String(packet.getData(),0,packet.getLength()));

        //关闭连接
        socket.close();

    }
}

结果为
Java笔记 —— 网络编程