学习笔记 :网络编程基础及基础传输功能实现

网络编程

计算机和计算机之间数据通过网络传输。

常见软件架构

CS : (ClientServer 客户端/服务器) 在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。如:QQ,Steam

BS :(Browser/Server 浏览器/服务器) 只需要一个浏览器啊,用户可通过不同的网址,访问不同的服务器。 如:淘宝,京东

网络编程三要素

IP : 设备在网络中的地址(唯一标识)

  • IPV4 :互联网通信协议第四版(只有42亿数量已经分配完)
  • IPV6 : 解决IPV4不够用的问题
  • 特殊IP地址 : 127.0.0.1,也可以是localhost: 是回送地址也称本地回环地址。也称本地IP。如,向IP 127.0.0.1发送数据,是不经过路由器的,直接发送到本地。

端口号 : 应用程序在设备中唯一的标识(取值范围:0-65535,其中0-1023之间的端口号用于一些知名网络服务或者应用,我们自己使用1024以上即可)

  • 一个端口号只能被一个应用程序使用

协议 : 数据在网络中传输的规则,常见协议有UDP,TCP,HTTP,HTTPS,FTP

InetAddress类

public class MyInetAddressDemo1 {
    public static void main(String[] args) throws UnknownHostException {

        //获取InetAddress对象
            //IP的对象 一台电脑的对象(IP唯一)
        InetAddress address = InetAddress.getByName("LAPTOP-8QI8KDR8");
        System.out.println(address);

        //获取IP地址
        String ip = address.getHostAddress();
        System.out.println(ip);
    

        //获取主机名
        String hostName = address.getHostName();
        System.out.println(hostName);

    }
}

因为电脑的IP地址唯一,获取了IP对象可以视为获取的电脑的对象。

Inet4Address,Inet6Address为InetAddress的子类,在获取IP对象的时候,会自动判断是IPv4还是IPv6,并创建。

协议

TCP/IP参考模型

每一层都有自己的协议

应用层 :HTTP,FTP,DNS…

传输层 :TCP,UDP…

网络层 : IP,ICMP…

物理链路层 : 硬件设备

数据发送从应用层到物理链路层一层层转换,接收方电脑从物理链路层向上解析到应用层。

UDP协议

用户数据协议

  • UDP是面向无连接通信协议 速度快,有大小限制,一次最多发送64K,数据不安全容易丢失数据。(不会去确定是否有链接便直接发送消息)
  • 适用于丢一点数据无所谓的场景,如视频通话

UDP通信程序

发送数据

1.创建发送端的DatagramSocket对象

2.数据打包

3.发送数据

4.释放资源

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
        //创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();//可绑定端口,不写则为随机端口使用

        //打包数据
        String str = "hello,UDP";
        byte[] bytes = str.getBytes();//转为字节数组
        InetAddress address = InetAddress.getByName("127.0.0.1");//发送给该电脑
        int port = 10086;//发送到该端口号

        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, address, port);

        ds.send(datagramPacket);//发送数据
        ds.close();

    }
}

接收数据

1.创建DatagramSocket对象,绑定端口,且要与发送的端口一致

2.接收数据包

3.解析数据包

4.释放资源

public class ReceiveMessageDemo {
    public static void main(String[] args) throws IOException {
        //创建DatagramSocket对象,绑定端口,且要与发送的端口一致
        DatagramSocket datagramSocket = new DatagramSocket(10086);

        //接收数据包
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);

        //阻塞式方法,等待接收数据
        datagramSocket.receive(dp);

        //解析数据包
        byte[] data = dp.getData();
        int length = dp.getLength();
        InetAddress address = dp.getAddress();
        int port = dp.getPort();

        System.out.println("接收到的数据:" + new String(data, 0, length));
        System.out.println("发送方的IP地址:" + address.getHostAddress() + ", 端口号:" + port);

        //释放资源
        datagramSocket.close();
    }
}

测试样例:

public class ReceiveMessage {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(10086);
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        while (true) {
            ds.receive(dp);
        //第二次接收数据时,会将第一次接收的数覆盖
            byte[] data = dp.getData();
            int length = dp.getLength();
            int port = dp.getPort();
            String hostAddress = dp.getAddress().getHostAddress();
            String hostName = dp.getAddress().getHostName();
            System.out.println("hostAddress: "
                    + hostAddress
                    + ", hostName: "+ hostName
                    + ", port: " + port
                    + ", length: " + length
                    + ", data: " + new String(data, 0, length));
        }
    }
}
public class SendMessage {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("Enter the message to send: ");
            String str = sc.nextLine();
            if("886".equals(str)){
                System.out.println("Connection closed");
                break;
            }
            byte[] bytes = str.getBytes();

            InetAddress address = InetAddress.getByName("localhost");
            int port = 10086;

            ds.send(new DatagramPacket(bytes, bytes.length, address, port));
        }

        ds.close();
    }
}

测试:

Enter the message to send:
yg我爱你
Enter the message to send:
muamua

Enter the message to send:
886
Connection closed

打印结果:

hostAddress: 127.0.0.1, hostName: 127.0.0.1, port: 58751, length: 11, data: yg我爱你
hostAddress: 127.0.0.1, hostName: 127.0.0.1, port: 58751, length: 6, data: muamua

UDP的三种通信方式

1.单播:一对一

2.组播:发送到组播地址(可以表示多个电脑):224.0.0.0–239.255.255.255

​ 其中224.0.0.0–224.0.0.255为预留的组播地址,可以我们自己使用

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
        //创建MulticastSocket
        MulticastSocket ms =new MulticastSocket();

        //创建DatagramPacket
        String str = "yg我爱你啾咪";
        byte[] bytes = str.getBytes();
        InetAddress address = InetAddress.getByName("224.0.0.1");//需指定组播地址
        int port = 10086;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ms.send(dp);
        ms.close();
    }
}
public class ReceiveMessageDemo1 {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms =new MulticastSocket(10086);

        //将当前本机添加到224.0.0.1的组播组中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

        ms.receive(datagramPacket);

        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
    }
}
public class ReceiveMessageDemo2 {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms =new MulticastSocket(10086);

        //将当前本机添加到224.0.0.1的组播组中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

        ms.receive(datagramPacket);

        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
    }
}
public class ReceiveMessageDemo3 {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms =new MulticastSocket(10086);

        //将当前本机添加到224.0.0.1的组播组中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

        ms.receive(datagramPacket);

        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
    }
}

3.广播: 给局域网中所有电脑发送数据

TCP协议

传输控制协议

  • TCP协议是面向连接协议
  • 速度慢,无大小限制,数据安全(确认连接再发送,不会造成数据丢失)如:下载软件,文字聊天

TCP通信程序

它在两端各建立一个Socket对象,通信之前保证链接已经建立,通过Socket产生IO流来进行网络通信。

客户端 ----------> (通信之前保证链接已经建立)---------> 服务器

(Socket) 输出流 输入流 (ServerSocket)

1.创建客户端的Socket对象与指定服务端连接 1.创建服务器端的Socket对象

2.获取输出流,写数据 2.监听客户端连接,返回一个Socket对象

3.释放资源 3.获取输入流,读数据,并把数据显示在 控制台

​ 4.释放资源

Client:

public class Client {
    public static void main(String[] args) throws IOException {
        //TCP协议,发送数据
        //创建对象的同时会连接服务器,连接失败会报错
         // 此处连接包含三次握手协议保证链接建立
        Socket socket = new Socket("127.0.0.1", 10086);
		//连接成功,服务端返回连接对象
        
        //从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        os.write("yg i love u".getBytes());

        os.close();//流存在于连接通道中,若关闭了通道,流也会一并关闭,可以不写。
        socket.close();//包含四次挥手协议,利用这个协议断开连接,保证断开连接时连接通道中数据被处理完毕
    }
}

Server:

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据
        ServerSocket ss = new ServerSocket(10086);
		
        //监听客户端的链接
        Socket socket = ss.accept();//服务端创建后,程序会阻塞在这里监听,直到有客户端连接,并返回连接对象
		
        //从连接通道获取输入流,读数据
        InputStream is = socket.getInputStream();
        int b;
        while ((b = is.read()) != -1) {
            //字节转换为字符
            System.out.print((char) b);
        }//在循环中,is.read() 方法用于读取文件中的一个字节,并将其存储在变量 b 中。当 b 不等于 -1 时,表示读取到了文件中的一个字节,而不是读取到了文件末尾。

        //释放资源
        socket.close();
        ss.close();
    }
}

此时只能传输纯英文字符,传输中文会出现乱码。因为此时未指定编码表,将会把一个中文拆成3个字节,并将字节传输到服务器,字节流一次读一个字节并且转为字符,无法转成字符。需改成字符流

修改后的Server:

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据
        ServerSocket ss = new ServerSocket(10086);

        //监听客户端的链接
        Socket socket = ss.accept();//服务端创建后,程序会阻塞在这里监听,直到有客户端连接,并返回连接对象

        //从连接通道获取输入流,读数据
        InputStream is = socket.getInputStream();\
        //转成字符流
        InputStreamReader isr = new InputStreamReader(is);
        //添加缓冲流
        BufferedReader br = new BufferedReader(isr);
        int b;
        while ((b = br.read()) != -1) {
            //字节转换为字符
            System.out.println((char) b);
        }//在循环中,br.read() 方法用于读取文件中的一个字节,并将其存储在变量 b 中。当 b 不等于 -1 时,表示读取到了文件中的一个字节,而不是读取到了文件末尾。

        //释放资源
        socket.close();//关闭连接
        ss.close();//关闭服务器
    }
}

额外:

回写数据

当服务器向客户端回写数据时,对于服务器来讲,是输出,要用输出流。客户端接收回写数据时,是输入流。

客户端:

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost",10086);
        OutputStream os = socket.getOutputStream();
        String str = "熬哦 熬哦";
        os.write(str.getBytes());
        //结束标记
        socket.shutdownOutput();


        //接收服务器回写数据
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        while((b = isr.read()) != -1){
            System.out.print( (char)b );
        }



        socket.close();
    }
}

服务器:

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

        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        while((b = isr.read()) != -1){
            //细节:read方法会从通道中读取数据
            //未读到结束标记时将会继续回到read方法,等待数据
            //需要有结束标记,循环才能停止,进入下面的回写数据
            System.out.print( (char)b );
        }

        //回写数据
        OutputStream os = socket.getOutputStream();
        String str = "雪豹闭嘴";
        os.write(str.getBytes());


        socket.close();
        ss.close();
    }
}

文件传输

Client :

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

        //获取本地文件,并传入服务器中
        //若文件较大,直接用字节流将不合适,一个字节一个字节传输太慢了
        //可使用缓冲流包裹
        BufferedInputStream bufferedInputStream = new BufferedInputStream(
                new FileInputStream("F:\\java代码\\JavaJC\\socketnet\\image\\社区头像1.jpg")
        );
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bufferedInputStream.read(bytes)) != -1) {
            bufferedOutputStream.write(bytes, 0, len);
        }
        //将缓冲区的内容刷新到磁盘,确保数据被写入到文件中。
        bufferedOutputStream.flush();
        //写出结束标记
        socket.shutdownOutput();

        //接受服务器回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);

        socket.close();
    }
}

Server :

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

        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\java代码\\JavaJC\\socketnet\\target\\test.jpg"));
        int len;
        byte[] bytes = new byte[1024];
        while( (len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }

        //回写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("文件发送成功");
        bw.newLine();
        bw.flush();

		bos.close();
        socket.close();
        ss.close();
    }
}

注意:数据传输经过连接通道时,必须将缓冲区内容刷新到磁盘再关闭连接,否则可能会导致有数据没有被写入磁盘,导致文件传输不完整。

多线程版本的文件传输服务端:

利用循环使每次传输文件都开启一条线程去执行

改写Server:

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(12345);
        while (true) {
            Socket socket = ss.accept();
            //将socket传入Myrunnable
            new Thread(new MyRunnable(socket)).start();
        }
    }
}

创建MyRunnable:

public class MyRunnable implements Runnable{
    Socket socket;
    public MyRunnable(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            //使用UUID生成唯一ID防止文件名重复
            String name = UUID.randomUUID().toString().replace("-","");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\java代码\\JavaJC\\socketnet\\target\\"+name+".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while( (len = bis.read(bytes)) != -1){
                bos.write(bytes,0,len);
            }

            //回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("文件发送成功");
            bw.newLine();
            bw.flush();


            bos.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

反复创建线程池且消费十分浪费资源,使用线程池优化

public class Server {
    public static void main(String[] args) throws IOException {

        //创建线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                   5,//核心线程数量
                10,//线程池总大小
                60,//空闲时间
                TimeUnit.SECONDS,//空闲时间单位
                new ArrayBlockingQueue<>(10),//仍无队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略
        );

        ServerSocket ss = new ServerSocket(12345);
        while (true) {
            Socket socket = ss.accept();
            //将socket传入Myrunnable
            //new Thread(new MyRunnable(socket)).start();

            //任务提交到线程池
            pool.submit(new MyRunnable(socket));
        }
    }
}
  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值