08-网络编程

网络编程

简介

  • 概述:

    • 网络编程也叫Socket编程,也叫套接字编程,就是用来实现 网络互联的 不同的计算机上 运行的程序间 可以进行数据交互
  • 原理:

    • 通信两端都独有自己的Socket对象, 数据在两个Socket之间通过IO流 或者 **数据报包(DatagramPacket)**的方式进行传输
    • 例如: 你给你女朋友打电话, 看起来好像是你们两个人在沟通, 其实是: 两个手机在沟通.

软件结构:

  • C/s结构:
    • 概述:指的就是 Client(客户端) / **Server(服务器端)**结构,即:需要用户安装指定的软件.
    • 好处:
      1.界面相对比较美观.
      2.提高用户体验. 界面美观,用起来舒服.
      3.降低服务器压力,因为可以把一些非重要级别的文件让用户提前安装.
    • 弊端:
      • 当服务器端升级的时候,客户端也需要同步升级更新.
  • B/s结构:
    • 概述:指的是Browser(浏览器端) / **Server(服务器端)**结构,即:用户只需要安装一个浏览器即可.
    • 好处:
      1. 提高用户体验 操作简单,只需要一个浏览器即可.
      2. 不受地域(设备)限制.
    • 弊端:
      服务器压力过大,所需的所有的资料和数据都需要从服务器下载.

三大要素

IP地址:

  • 概述:指的是设备(电脑,手机,Ipad…)在网络中的唯一标识.

  • 组成: //此处以IPv4举例.

    • 网络(网关)号码 + 主机地址
  • 分类:

    • 城域网: 1 + 3
    • 广域网: 2 + 2
    • 局域网: 3 + 1,即:前三段属于网关号码, 最后一段是主机地址.
      例如: 192.168.16.52, 192.168.16.99 …
  • 两个特殊的IP:

    • 127.0.0.1 本机回环(回路)地址,即:代表本机.
    • 255.255.255.255 广播地址
  • 两个DOS指令:

    • 查看IP的: ipconfig/all
    • 测试网络连接的: pig ip地址/网址 -t
InetAddress:IP地址的包装类
  • 概述:它是java.net包下的,用之前,需要先导包.它表示IP地址对象形式.
成员方法:

String形式的 IP -> IP 地址对象

  • public static InetAddress getByName(String ipOrHostname); 根据主机名或者字符串ip, 获取其对应的IP地址对象

IP地址对象 -> String形式的IP

  • public String getHostAddress(); 根据Ip地址对象, 获取其对应的: 字符串形式的IP.
  • public String getHostName(); 根据Ip地址对象, 获取其对应的: 字符串形式的主机名.
案例:InetAddress简介
 package com.ithiema.api.demo;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Demo02 {
    public static void main(String[] args) throws UnknownHostException {
        String ip = "192.168.0.105";
        String host = "YULIANG";

        InetAddress inet1 = InetAddress.getByName(ip);
        InetAddress inet2 = InetAddress.getByName(host);

        //String类型的IP     ->    InetAddress IP地址对象.
        System.out.println(inet1);
        System.out.println(inet2);

        //InetAddress IP地址对象  ->  String类型的IP
        System.out.println(inet1.getHostName());
        System.out.println(inet1.getHostAddress());

        System.out.println(inet2.getHostName());
        System.out.println(inet2.getHostAddress());
        System.out.println("---------------------");
    }
}

端口号:

  • 概述:指的是程序在设备中的唯一标识.

  • 范围:065535**,其中**01234已经被系统占用了,或者作保留端口,你在自定义端口的时候,尽量不要用这个范围.

  • 和端口号相关的几个DOS命令:

    • netstat -ano 查看本机所有的端口号
    • netstat -ano /finder 3306 查看指定的端口号被哪个pid程序占用了
    • taskkill /f/pid 4636 根据pid值,强制关闭某个进程.

协议:

  • 概述:设备之间进行数据传输时所要遵循的规则,范例,就叫:协议.
  • 分类:UDP,TCP。
UDP:

//类似于: QQ群聊.

  1. 面向无连接.
  2. 采用数据报包的形式发送数据, 每个包的大小不能超过64KB.
  3. 不安全(不可靠)协议.
  4. 效率相对较高.
  5. 不区分客户端和服务器端, 叫: 发送端接收端.
涉及到的API:
  • DatagramSocket类中的方法:
    • 构造方法:
      • public DatagramSocket();
      • public DatagramSocket(int port); 指定端口号
    • 成员方法:
      • public void send(DatagramPacket dp); 发送数据报包
      • public void receive(DatagramPacket dp); 接收数据, 并封装到数据报包中.
      • public void close(); 关闭Socket对象.
  • DatagramPacket类中的方法:
    • 构造方法:
      • public DatagramPacket(byte[] bys, int length, InetAddress inet, int port);
        • 创建数据包,发送长度为len的数据包到指定主机的指定端口(要发送的数据,字节数组长度,接收端的IP对象,端口号)
      • public DatagramPacket(byte[] bys, int length);
    • 成员方法:
      • public byte[] getData(); 从数据报包中获取数据.
      • public int getLength(); 返回从数据报包获取到的具体有效字节数.
案例:演示UDP协议发送数据,接收端 收到数据,并打印

发送端

package com.ithiema.api.demo;

//发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Demo03 {
    public static void main(String[] args) throws Exception {
        //1.创建发送端的Socket对象
        DatagramSocket senderSocket = new DatagramSocket();
        //2.将要发送的数据转成对应的字节数组形式
        byte[] bys = "你好,UDP,我来了!".getBytes();
        //3.创建数据报包,关联:要发送的数据,字节数组长度,接收端的IP对象,端口号
        DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.0.104"), 10010);
        //4.发送数据报包
        senderSocket.send(dp);
        //5.关闭发送端的Socket对象
        senderSocket.close();
    }
}

接收端

package com.ithiema.api.demo;
//接收端
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Demo04 {
    public static void main(String[] args) throws Exception {
        //1.创建接收端的Socket对象,指定:自身IP对象,端口号
        DatagramSocket receiveSocket = new DatagramSocket(10086);
        //2.创建字节数组,用来存储指定的自身数据的
        byte[] bys = new byte[1024];
        //3.创建数据报包,关联:字节数组对象,字节数组的长度
        DatagramPacket dp = new DatagramPacket(bys, bys.length);
        //4.接收数据报包
        receiveSocket.receive(dp);
        //5.从数据报包中解析收到的数据,并打印
        //获取字节数组
        byte[] bys2 = dp.getData();
        //获取实际的字节长度
        int len = dp.getLength();
        //封装数据
        String s = new String(bys2, 0, len);
        System.out.println(s);
        //6.关闭接收端的Socket对象
        receiveSocket.close();
    }
}
TCP:

//类似于: 打电话

  1. 面向有连接(三次握手)
  2. 采用IO流的形式发送数据, 理论上数据无大小限制.
  3. 安全(可靠)协议.
  4. 效率相对较低.
  5. 区分客户端和服务器端.
涉及到的类

Socket类:

  • 构造方法:
    • public Socket(InetAddress inet, int port);
    • public Socket(String id, int port); 服务器ip, 端口号.
  • 成员方法:
    • public InputStream getInputStream(); 获取输入流, 可以读取服务器端写过来的内容.
    • public OutputStream getOutputStream(); 获取输出流, 可以往服务器端写数据.
    • public void close(); 关闭socket对象.

ServerSocket类: 服务器端的Socket类

  • 构造方法:
    • public ServerSocket(int port); 服务器端的Socket对象, 负责监听客户端连接 和 分配资源的.
  • 成员方法:
    • public Socket accept(); 监听客户端连接, 如果有客户端申请建立连接, 服务器端在审核数据
演示TCP协议之客户端给服务器端发送一句话

客户端

package com.ithiema.api.demo;


import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Demo05 {
    public static void main(String[] args) throws Exception {
        //1.创建客户端的Socket对象,指定:服务器端IP,端口号
        Socket socket = new Socket("192.168.0.104", 10086);

        //2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
        OutputStream os = socket.getOutputStream();

        //把获取的可以往服务器端写数据的字节输出流 -> 转换输出流 -> 字节高效输出流
        //OutputStreamWriter osw = new OutputStreamWriter(os);
        //BufferedWriter bw = new BufferedWriter(osw);
        //省略版
        //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        //3.具体的给服务器端写数据的操作
        os.write("你好呀,约吗".getBytes());

        //4.获取输入流对象,可以读取服务器端写过来的回执信息
        InputStream is = socket.getInputStream();

        //5.具体的读取服务器端写过来的回执信息的动作,并打印
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String s = new String(bys,0,len);
        System.out.println(s);

        //6.释放资源
        //os.close();     //如果Socket关闭了,和他关联的对象也会被释放
        socket.close();
    }
}

服务器端

package com.ithiema.api.demo;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Demo06 {
    public static void main(String[] args) throws Exception {
        //1.创建服务器端的Socket对象(ServerSocket),指定端口号
        ServerSocket serverScoket = new ServerSocket(10086);

        //2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
        Socket accept = serverScoket.accept();

        //3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
        InputStream is = accept.getInputStream();

        //4.具体的读取客户端写过来的数据的操作
        byte[] bys = new byte[1024];
        int len = is.read(bys);     //读取到的有效字节数

        //5.将读取到的数据转成字符串,并打印
        String s = new String(bys, 0, len);
        System.out.println("服务器端接收到数据:" + s);

        //6.获取输出流,可以往客户端写数据
        OutputStream os = accept.getOutputStream();

        //7.给客户端c吃藕,不约!".getBytes());

        //8.释放资源
        //is.close();       //可以不写,因为Socket关了,和它关联的对象也会被释放
        accept.close();
        //serverSocket.close();     //实际开发中,服务器端一般是不关闭的
    }
}
补充close()和flush()方法的区别
  • close():

    • 是用来关闭流对象释放资源的,关闭之前,会自动刷新一次缓冲区,关闭之后,流对象就不能继续使用了
  • flush():

    • 是用来刷新缓冲区,刷新之后,流对象可以继续使用
各个流的缓冲区的大小,具体如下:

​ 字符高效流:16KB

​ 字节高效流:8KB

​ Writer:2KB

演示TCP协议之文件上传案例

客户端

package com.ithiema.api.demo;

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

public class Demo07 {
    public static void main(String[] args) throws Exception {
        //1.创建客户端的Socket对象,指定:服务器端IP,端口号
        Socket socket = new Socket("192.168.0.104", 10086);

        //2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
        //OutputStream os = socket.getOutputStream();
        //3.通过转换流 和 字符高效率流,将上一步获取的字节输出流封装成:字符高效输出流
        //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        //合并版
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        //4.创建字符高效输入流,关联:数据源文件(具体要上传的文件)
        BufferedReader br = new BufferedReader(new FileReader("D:\\快捷方式\\0\\文件\\账号记录.txt"));

        //5.具体的上传文件的动作(其实就是IO流的读写代码)
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();   //换行
            bw.flush();     //记得刷新缓冲区
        }

        //6.核心步骤:给服务器一个结束的标记,意思是:我上传完毕了,你该干嘛干嘛
        socket.shutdownOutput();

        //7.通过Socket#getInputStream(),可以读取服务器端写过来的回执信息
        //8.将上一步获取到的流对象,封装成:字符高效输入流
        BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //9.具体的读取服务器端写过来的回执信息的动作,并打印
        String result = br2.readLine();
        System.out.println(result);

        //10.释放资源
        br.close();
        //bw.close();   //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
        socket.close();
    }
}

服务器端

package com.ithiema.api.demo;

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

public class Demo08 {
    public static void main(String[] args) throws Exception {
        //1.创建服务器端的Socket对象(ServerSocket),指定端口号
        ServerSocket serverSocket = new ServerSocket(10086);

        //2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
        Socket accept = serverSocket.accept();

        //3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
        //4.通过转换流和字符高效流将上一步获取的字节输入流封装成 字符高效流
        BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));

        //5.创建字符高效流,关联目的地文件(即:接收到的数据往哪里写)
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\IdeaProjects\\Java\\JavaSE\\data\\1.txt"));

        //6.具体的IO读写动作,接收上传的数据
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        //7.通过Socket#getOutputStream()获取输出流,可以给客户端写回执信息
        //8.将上述的流对象封装成 字符高效输出流
        BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        //9.具体给客户端写回执信息的操作
        bw2.write("文件上传成功!");
        bw2.newLine();  //换行
        bw2.flush();    //刷新缓冲区

        //10.释放资源
        bw.close();
        //br.close();       //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
        accept.close();
        //serverSocket.close();     //实际开发中,服务器端一般是不关闭的
    }
}
多线程版的文件上传

客户端

package com.ithiema.tcp;

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

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //1.创建客户端的Socket对象,指定:服务器端IP,端口号
        Socket socket = new Socket("192.168.0.104", 10086);

        //2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
        //OutputStream os = socket.getOutputStream();
        //3.通过转换流 和 字符高效率流,将上一步获取的字节输出流封装成:字符高效输出流
        //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        //合并版
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        //4.创建字符高效输入流,关联:数据源文件(具体要上传的文件)
        BufferedReader br = new BufferedReader(new FileReader("D:\\快捷方式\\0\\文件\\账号记录.txt"));

        //5.具体的上传文件的动作(其实就是IO流的读写代码)
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();   //换行
            bw.flush();     //记得刷新缓冲区
        }

        //6.核心步骤:给服务器一个结束的标记,意思是:我上传完毕了,你该干嘛干嘛
        socket.shutdownOutput();

        //7.通过Socket#getInputStream(),可以读取服务器端写过来的回执信息
        //8.将上一步获取到的流对象,封装成:字符高效输入流
        BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //9.具体的读取服务器端写过来的回执信息的动作,并打印
        String result = br2.readLine();
        System.out.println(result);

        //10.释放资源
        br.close();
        //bw.close();   //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
        socket.close();
    }
}

服务器端

package com.ithiema.tcp;

import java.net.ServerSocket;
import java.net.Socket;

/*
//改造1. 创建一个MyRunnable类(资源类),实现Runnable接口
//改造2. 定义成员变量Socket对象,表示具体的和客户端交互的Socket对象
//改造3. 定义带参构造,接收具体的Socket对象,它负责和客户端交互
//改造4. 重写run()方法,里面记录的是:服务器端具体的接收客户端上传的文件,并给出回执的 一套完整逻辑
//改造5. 细节:注意文件重名问题,因为可能要上传多个文件,所以文件名千万不要重复,否则就是覆盖了
//改造6. 因为run()方法中有异常是不能抛,只能try的,所以我们用try来修改代码
//改造7. 扩展:加入try.catch.finally的嵌套,来释放资源。
//改造8. 把之前的ServerDemo类中,完整的一套 接收客户端上传并给出回执的代码拷贝到run()方法
//改造9. 修改ServerDemo类中的代码,把一次监听改为无限监听
//改造10. 每监听到有新的客户端申请建立连接,在审核数据合法后,就创建一个线程对象,负责和它交互
//改造11. 注意:该线程对象封装的是:MyRunnable资源类对象。
*/
public class ServerDemo {
    public static void main(String[] args) throws Exception{
        //1.创建服务器端的Socket对象(ServerSocket),指定端口号
        ServerSocket serverSocket = new ServerSocket(10086);

        //2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
        //改造9. 修改ServerDemo类中的代码,把一次监听改为无限监听
        while (true) {
            Socket accept = serverSocket.accept();
            //改造10. 每监听到有新的客户端申请建立连接,在审核数据合法后,就创建一个线程对象,负责和它交互
            //格式:new Thread(Runnable接口中的子类对象).start();

            //改造11. 注意:该线程对象封装的是:MyRunnable资源类对象。
            //new Thread(new MyRunnable(accept)).start();
            new Thread(new MyRunnable(accept)).start();
        }
    }
}

MyRunnable类

package com.ithiema.tcp;

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

//改造1. 创建一个MyRunnable类(资源类),实现Runnable接口
public class MyRunnable implements Runnable {
    //改造2. 定义成员变量Socket对象,表示具体的和客户端交互的Socket对象
    private Socket accept;

    //改造3. 定义带参构造,接收具体的Socket对象,它负责和客户端交互
    public MyRunnable(Socket accept) {
        this.accept = accept;
    }

    //改造4. 重写run()方法,里面记录的是:服务器端具体的接收客户端上传的文件,并给出回执的 一套完整逻辑
    @Override
    public void run() {
        //改造5. 细节:注意文件重名问题,因为可能要上传多个文件,所以文件名千万不要重复,否则就是覆盖了
        int count = 1;
        File destFile = new File("javaSE/data/copy(" + ++count + ").txt");
        while (destFile.exists()) {
            destFile = new File("javaSE/data/copy(" + ++count + ").txt");
        }

        //自定义字符输出流,关联:目的地文件
        BufferedWriter bw = null;

        //改造6. 因为run()方法中有异常是不能抛,只能try的,所以我们用try来修改代码

        try {
            //改造8. 把之前的ServerDemo类中,完整的一套 接收客户端上传并给出回执的代码拷贝到run()方法

            //3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
            //4.通过转换流和字符高效流将上一步获取的字节输入流封装成 字符高效流
            BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));

            //5.创建字符高效流,关联目的地文件(即:接收到的数据往哪里写)
            bw = new BufferedWriter(new FileWriter(destFile));

            //6.具体的IO读写动作,接收上传的数据
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }

            //7.通过Socket#getOutputStream()获取输出流,可以给客户端写回执信息
            //8.将上述的流对象封装成 字符高效输出流
            BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            //9.具体给客户端写回执信息的操作
            bw2.write("文件上传成功!");
            bw2.newLine();  //换行
            bw2.flush();    //刷新缓冲区


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

            //改造7. 扩展:加入try.catch.finally的嵌套,来释放资源。
            //10.释放资源
            try {
                if (bw != null) {
                    bw.close();
                    bw = null;      //GC会优先回收null对象
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {

                //br.close();       //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
                try {
                    if (bw != null) {
                        accept.close();
                        accept = null;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //serverSocket.close();     //实际开发中,服务器端一般是不关闭的
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值