网络编程

131 阅读10分钟

计算机网络

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

计算机网络主要是由一些通用的、可编程的硬件互连而成的,而这些硬件并非专门用来实现某一特定目的(例如,传送数据或视频信号)。这些可编程的硬件能够用来传送多种不同类型的数据,并能支持广泛的和日益增长的应用。

网络编程的目的

无线电台..传输交流信息,数据交换、通信

网络编程需要解决的问题

  1. 地理位置不同,如何连接
  2. 如何把多台网络通信设备连接在一起

想要达到这个效率需要什么

  1. 如何准确定位网络上的一台主机 -> 127.0.0.1:port,定位到这个计算机上的资源(比如定位到QQ、微信等)

网络通信的要素

  1. 如何实现网络通信?
  • 通信双方地址
    1. ip
    1. 端口号Port
    1. 127.0.0.1:port ->可以定位到某台计算机上的某个程序
  • 规则:网络通信的协议(http、ftp、smtp、tcp、udp.....)
    1. TCP/IP参考模型

image.png

小结

image.png

1. IP [InetAddress]

  1. 唯一定位一台网络上的计算机
  2. 格式:xxx.xxx.xxx.xxx:port
  3. ip地址的分类
  • IPV4/IPV6
  • IPV4 127.0.0.1 4个字节组成 0-255 十进制点分法。总共有42亿个 30亿都在北美 4亿在亚洲 2011年就用完了。
  • IPV6 128位 (16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(: )分开,如: 3ffe:3201:1401:1280:c8ff:fe4d:db39:1984,能为地球上的每一粒沙子编号
  1. 公网(互联网)/私网(局域网)
  1. IP在java中的常用方法
import java.net.InetAddress;
import java.net.UnknownHostException;

public class TestInetAddress {

    public static void main(String[] args) throws UnknownHostException {
        // 查询本机地址
        InetAddress inetAddress1 = InetAddress.getByName("127.0.0.1");
        InetAddress inetAddress2 = InetAddress.getByName("localhost");
        InetAddress inetAddress3 = InetAddress.getLocalHost();
        System.err.println(inetAddress1);
        System.err.println(inetAddress2);
        System.err.println(inetAddress3);

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

        // 常用方法
        String canonicalHostName = inetAddress4.getCanonicalHostName(); // 规范的名字
        String hostAddress = inetAddress4.getHostAddress(); // IP
        String hostName = inetAddress4.getHostName(); // 域名
        System.err.println(canonicalHostName);
        System.err.println(hostAddress);
        System.err.println(hostName);
    }
}

2. 端口

  1. 端口表示计算机上一个程序的进程
  2. 不同的端口号用来区分不同的进程,用来区分软件
  3. 端口号的范围 0~65535
  4. TCP、UDP 各自有 0~65535,因此端口号共有65536*2
  5. TCP使用80端口,UDP也可以使用80端口,不同协议下允许使用相同端口,单个协议下端口号不能冲突
  6. 端口分类
  • 0~1023 公有端口(自定义端口时,不要使用范围内的端口)
    • HTTP 80
    • HTTPS 443
    • FTP 21
    • SSH 22
    • Telent 23
  • 1024~49151 程序注册端口, 分配给用户或者程序
    • Tomcat 8080
    • Mysql 3306
    • Oracle 1521
    • Redis 6379
  • 49152~65535 私有、公有,尽量别使用

image.png

通信协议

  1. TCP/IP协议簇
  • 其中重要的两个协议
    • TCP 传输控制协议,安全的,丢包会自动重传
    • UDP 用户报文协议,不安全的,会丢包
  • 出名的两个协议
    • TCP
    • IP 网络互联协议
  1. TCP和UDP的对比
  • TCP:打电话
    • 连接,稳定
    • 三次握手(至少需要三次,保证建议稳定的连接)
      • A: 你瞅啥?
      • B:瞅你咋滴?
      • A:那一起吃顿饭吧!
    • 四次挥手
      • A: 我要走了
      • B: 你真的要走了吗?
      • B: 你真的真的要走了吗?
      • A: 我真的要走了!
    • 分为客户端、服务端
    • 传输完成,会释放连接,效率低
  • UDP:发短信
    • 不连接,不稳定
    • 客户端、服务端没有明确的界限,因为是发包,所以可以客户端发给客户端
    • 不管有没有准备好,都可以发送,不关注是否能不能接受,因此效率高
    • 额外知识:DDOS 洪水攻击(饱和攻击),一直发送大量的垃圾包,你不接收,就会被占用端口,导致网络崩溃

TCP

TCP实现简单聊天

  • 客户端
    • 1.连接服务器Socket
    • 2.发送消息
    • 3.关闭资源
  • 服务端
    • 1.接收客户端socket
    • 2.accept()
    • 3.获取消息
    • 4.关闭资源
  • 先启动服务端,再启动客户端
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * TCP实现简单聊天 客户端
 */
public class TcpChatClient {
    public static void main(String[] args) {
        Socket socket = null;
        OutputStream os = null;
        try {
            // 要知道服务器地址、端口号
            InetAddress ip = InetAddress.getByName("localhost");
            int port = 10001;

            // 连接socket
            socket = new Socket(ip, port);

            // 发送消息
            os = socket.getOutputStream();
            os.write("你好 服务端😊".getBytes(StandardCharsets.UTF_8));

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != os) {
                    os.close();
                }
                if (null != socket) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * TCP实现简单聊天 服务端
 */
public class TcpChatServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket accept = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            // 我得有一个地址
            serverSocket =  new ServerSocket(10001);

            // 等待客户端传来消息
            accept = serverSocket.accept();

            // 获取消息
            is = accept.getInputStream();

            // 管道流,缓冲区
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len=is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }

            System.err.println(baos);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != baos) {
                    baos.close();
                }
                if (null != is) {
                    is.close();
                }
                if (null != accept) {
                    accept.close();
                }
                if (null != serverSocket) {
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

TCP实现文件传输

  • 客户端
    • 设置ip,port,创建socket连接
    • 创建输出流
    • 读取文件
    • 写出文件
    • 通知服务端传输完毕
    • 获取服务端接收完毕的消息
    • 关闭资源
  • 服务端
    • 开启ServerSocket服务
    • 阻塞式监听客户端的连接
    • 获取客户端的输入流
    • 文件输出
    • 通知客户端接收完毕
    • 关闭资源
  • 先启动服务端,再启动客户端
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * TCP实现文件传输 - 客户端
 */
public class TcpFileClient {
    public static void main(String[] args) throws Exception {
        // 设置服务器的地址,端口,创建Socket连接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10000);

        // 创建输出流
        OutputStream os = socket.getOutputStream();

        // 读取文件
        FileInputStream fis = new FileInputStream("test.jpg");

        // 看情况设置缓冲区的大小,过大会浪费资源
        byte[] buffer = new byte[1024];
        int len;

        // 读取文件流,发送文件
        while ((len=fis.read(buffer)) != -1) {
            os.write(buffer, 0 ,len);
        }

        // 通知服务端,发送完毕了
        socket.shutdownOutput();

        // 接收服务端的接收完毕消息
        InputStream finishIs = socket.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] finishBuffer = new byte[1024];
        int finishLen;
        while ((finishLen = finishIs.read(finishBuffer)) != -1) {
            baos.write(finishBuffer, 0, finishLen);
        }

        System.err.println(baos);

        // 先开后闭
        baos.close();
        finishIs.close();
        fis.close();
        os.close();
        socket.close();
    }
}
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * TCP实现文件传输 - 服务端
 */
public class TcpFileServer {
    public static void main(String[] args) throws Exception {
        // 开启一个socket服务
        ServerSocket serverSocket = new ServerSocket(10000);

        // 监听客户端的连接,阻塞式监听
        Socket socket = serverSocket.accept();

        // 读取客户端的输入流
        InputStream is = socket.getInputStream();

        // 文件输出流
        FileOutputStream fos = new FileOutputStream("tcp_file.jpg");

        // 写出文件
        byte[] buffer = new byte[1024];
        int len;
        while ((len=is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        
        // 需要客户端的shutdownOutput(),否则会一直等待

        // 通知客户端,已经接收完毕了
        OutputStream os = socket.getOutputStream();
        os.write("服务端已经接收完毕了,你可以断开了🍵".getBytes(StandardCharsets.UTF_8));

        // 先开后闭
        os.close();
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

Tomcat

  • 服务端
    • 自定义服务器 C/S
    • Tomcat服务器 B/S
  • 客户端
    • 自定义客户端 C/S
    • 浏览器 B/S

UDP

UDP实现消息发送

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

/**
 * UDP发送消息,不需要连接服务器
 */
public class UdpMsg {
    public static void main(String[] args) throws IOException {
        // 1.建立一个Socket
        DatagramSocket socket = new DatagramSocket();

        String msg = "你好啊,UDP服务器😊";

        System.err.println(msg.length());

        // 2.建个包
        DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(StandardCharsets.UTF_8), 0, msg.length(),
                InetAddress.getByName("127.0.0.1"), 10002);

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

        socket.close();
    }
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * TODO:: 字符串接收不完整
 */
public class UdpMsgServer {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(10002);

        // 给packet开辟空间
        byte[] buffer = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buffer, 0, buffer.length);

        // 接收消息
        datagramSocket.receive(datagramPacket);

        System.err.println(datagramPacket.getAddress());
        System.err.println(datagramPacket.getAddress().getHostAddress());
        System.err.println(datagramPacket.getAddress().getHostName());

        // 解析消息
        String msg = new String(datagramPacket.getData(), 0, datagramPacket.getLength());

        System.err.println(msg);

        datagramSocket.close();

    }
}

UDP实现单向咨询

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

/**
 * 学生提问
 */
public class UdpStudent {
    public static void main(String[] args) throws IOException {
        // 模拟一个学生进程
        DatagramSocket student = new DatagramSocket(10005);
        // 监控系统输入的内容
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

        while (true) {
            String data = bufferedReader.readLine(); // 这个数据是不可读的,需要转成字节
            byte[] datas = data.getBytes();
            DatagramPacket datagramPacket = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("127.0.0.1", 10006));
            student.send(datagramPacket);
            if (data.equals("拜拜")) {
                break;
            }
        }

        student.close();
    }
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * 老师接收
 */
public class UdpTeacher {
    public static void main(String[] args) throws IOException {
        // 创建接收者服务
        DatagramSocket teacher = new DatagramSocket(10006);

        while (true) {
            // 先创建一个接收消息的箱子
            byte[] box = new byte[1024];
            DatagramPacket packet = new DatagramPacket(box, 0, box.length);

            // 用箱子接收数据
            teacher.receive(packet);

            // 从填装后的箱子中拿数据
            byte[] data = packet.getData();
            String msg = new String(data, 0, packet.getLength()); // 需要用packet.getLength(); 否则会输出很多空格
            System.err.println(msg);

            if (msg.equals("拜拜")) {
                break;
            }
        }

        teacher.close();
    }
}

UDP实现多线程在线咨询

  • 文件目录

image.png

  • 聊天发送者
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

/**
 * 聊天发送者
 */
public class ChatSender implements Runnable {
    /**
     * 从哪个IP发送的,表明自己的身份
     */
    private String fromIP;

    /**
     * 从哪个端口发送的,表明自己的身份
     */
    private int fromPort;

    /**
     * 发送给指定IP
     */
    private String toIP;

    /**
     * 发送给指定端口
     */
    private int toPort;

    /**
     * UDP Socket
     */
    private DatagramSocket socket = null;

    /**
     * 字符流,读取输入信息
     */
    private BufferedReader reader = null;

    /**
     * 创建聊天发送者
     */
    public ChatSender(String fromIP, int fromPort, String toIP, int toPort) {
        this.fromIP = fromIP;
        this.fromPort = fromPort;
        this.toIP = toIP;
        this.toPort = toPort;

        try {
            socket = new DatagramSocket(new InetSocketAddress(fromIP, fromPort));
            reader = new BufferedReader(new InputStreamReader(System.in));
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                // 获取聊天消息
                String msg = reader.readLine();
                // 创建聊天消息盒子
                byte[] data = msg.getBytes(StandardCharsets.UTF_8);
                DatagramPacket packet = new DatagramPacket(data, 0, data.length, new InetSocketAddress(toIP, toPort));
                socket.send(packet);
                if (msg.equals("拜拜")) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            reader.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 聊天接收者
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * 聊天接收者
 */
public class ChatReceiver implements Runnable {

    /**
     * 自己对外开放的端口
     */
    private int myPort;

    /**
     * 接收消息的来源
     */
    private String fromAddress;

    /**
     * UDP Socket
     */
    private DatagramSocket socket = null;

    /**
     * 创建接收者
     */
    public ChatReceiver(int myPort, String fromAddress) {
        this.myPort = myPort;
        this.fromAddress = fromAddress;

        try {
            socket = new DatagramSocket(myPort);
        } catch (SocketException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        while (true) {
            try {
                // 创建一个容器,用于接收数据
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);

                // 容器初始化完成,填充数据
                socket.receive(packet);

                // 获取数据,并解析
                byte[] data = packet.getData();
                String receiveData = new String(data, 0, packet.getLength());
                String fromWhere = "[" + packet.getAddress() + "]";
                System.err.println(fromWhere + Thread.currentThread().getName() + " -> " + fromAddress + ":" + receiveData);

                if (receiveData.equals("拜拜")) {
                    break;
                }

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


        socket.close();
    }
}
  • 学生(既是接收者、也是发送者)
/**
 * 聊天的学生
 */
public class Student {
    public static void main(String[] args) {
        String toIP = "127.0.0.1";
        int toPort = 10011;
        String toName = "王老师";

        // 学生发消息给老师
        new Thread(new ChatSender("127.0.0.1", 10020, toIP, toPort), "学生发送端").start();

        // 学生接收老师的消息,接收者的端口需要与发送者的端口分开
        new Thread(new ChatReceiver(10021, toName), "学生接收端").start();
    }
}
  • 老师(既是发送者、也是接收者)
/**
 * 聊天的老师
 */
public class Teacher {
    public static void main(String[] args) {
        String toIP = "127.0.0.1";
        int toPort = 10021;
        String toName = "李学生";

        // 老师发消息给学生
        new Thread(new ChatSender("127.0.0.1", 10010, toIP, toPort), "老师发送端").start();

        // 老师接收学生的消息,接收者的端口需要与发送者的端口分开
        new Thread(new ChatReceiver(10011, toName), "老师接收端").start();
    }
}

疑问:如何获取发送方的IP 端口 是谁? 如何获取接收方的端口 是谁?

URL

URL也称统一资源定位符:定位互联网上某一个资源

www.baidu.com

URL的组成:协议://IP地址:端口/项目名/资源

下载网络资源

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class TestUrlDownload {
    public static void main(String[] args) throws IOException {
        URL url = new URL("https://xxxxxxx/style/web2/img/outchain/loading.gif?0420e99f0cc868c0ae4ea02e8b37ab3b");

        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        InputStream inputStream = urlConnection.getInputStream();

        FileOutputStream fos = new FileOutputStream("url.gif");
        byte[] buffer = new byte[1024];
        int len;
        while((len = inputStream.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }

        fos.close();
        inputStream.close();
        urlConnection.disconnect();
    }
}