网络编程 --java学习笔记

什么是网络编程?

  • 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)

java.net.*包下提供了网络编程的解决方案

基本的通信架构

  • 基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)

无论是CS架构,还是BS架构的软件都必须依赖网络编程!

网络通信的关键三要素

IP、端口、协议

IP地址

  • IP(Internet Protocol):全称”互联网协议地址”是分配给上网设备的唯一标志
  • IP地址有两种形式:IPV4、IPV6

IPV4地址

IPv6地址

  • IPv6:共128位,号称可以为地球每一粒沙子编号
  • IPV6分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号(:)分开

公网IP,内网IP

  • 公网IP:是可以连接互联网的IP地址:内网IP:也叫局域网IP,只能组织机构内部使用
  • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用

特殊IP地址:

  • 127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机

IP常用命令:

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通

InetAddress类

  • 代表IP地址

InetAddress的常用方法

端口

  • 标记正在计算机设备上运行的应用程序的,被规定为一个16 位的二进制,范围是0~65535

分类

  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序
  • 动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配

注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错

通信协议

  • 网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议

开放式网络互联标准:0SI网络参考模型

  • OSI网络参考模型:全球网络互联标准
  • TCP/IP网络模型:事实上的国际标准

传输层的2个通信协议

UDP(User Dataqram Protocol):用户数据报协议;TCP(Transmission ControlProtocol):传输控制协议。

传输层的2个通信协议

  • UDP(User Dataqram Protocol):用户数据报协议
  • TCP(Transmission ControlProtocol):传输控制协议
UDP协议
  • 特点:无连接、不可靠通信
  • 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等
  • 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的

TCP协议
  • 特点:面向连接、可靠通信
  • TCP的最终目的:要保证在不可靠的信道上实现可靠的传输
  • TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接
三次握手建立连接

三次握手:客户端发消息,服务器端确定客户端发消息没问题,服务器端回消息,客户端确认服务器端收发消息没问题,客户端再发消息,服务器端确认客户端收消息也没问题,这就是三次握手建立可靠连接

传输数据时,客户端给服务端发送一个消息,服务端一定要回一个消息确认收到此消息,否则客户端将认为服务端未收到消息,继续发送此消息

四次握手断开连接

首先,客户端发送断开连接请求,此时服务端可能还没接收完数据,会返回一个响应让客户端再等一下,等服务端接收完数据,再返回一个相应表示可以断开连接了,但是在返回这个相应的时候极大可能客户端同时也发送了一些消息,此时客户端接收完这些消息之后,就可以发出正式确认断开连接了

UDP通信

需要用到DatagramSocket类和DatagramPacket类

DatagramSocket:用于创建客户端、服务端

DatagramPacket:创建数据包

客户端实现步骤

  1. 创建DatagramSocket对象(客户端对象)
  2. 创建DatagramPacket对象封装需要发送的数据(数据包对象)
  3. 使用DatagramSocket对象的send方法,传入DatagramPacket对象
  4. 释放资源
服务端实现步骤
  1. 创建DatagramSocket对象并指定端口(服务端对象)
  2. 创建DatagramPacket对象接收数据(数据包对象)
  3. 使用DatagramSocket对象的receive方法,传入DatagramPacket对象
  4. 释放资源
代码演示:

客户端:

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class Client2 {
    public static void main(String[] args) throws Exception{
        //1、创建客户端对象
        DatagramSocket socket = new DatagramSocket();

        Scanner sc = new Scanner(System.in);
        //2、创建数据包对象封装要发出去的数据
        /*DatagramPacket
        参数一:封装要发出去的数据
        参数二:发送出去的数据大小(字节个数)
        参数三:服务端的IP地址(找到服务端主机)
        参数四:服务端程序的端口
         */
        while (true) {
            System.out.print("请说:");
            String msg = sc.nextLine();
            byte[] bytes = (msg).getBytes();
            DatagramPacket packet = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),9999);
            //3、正式发送数据包出去
            socket.send(packet);
            if(msg.equals("exit")){
                break;
            }
        }

        System.out.println("客户端数据发送完毕!");
        socket.close(); //释放资源
    }
}

服务端:

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

public class Server2 {
    public static void main(String[] args) throws Exception{
        System.out.println("-----服务端启动-----");
        //1、创建一个服务端对象
        DatagramSocket socket = new DatagramSocket(9999);

        while (true) {
            //2、创建一个数据包对象,用来接收数据
            byte[] buffer = new byte[1024 * 64]; //64kb,一包数据不会超过64kb
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length);

                //3、使用数据包接收客户端发送的数据
                socket.receive(packet);

            //4、从字节数组中把接收的数据直接打印出来
            //接收多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0,len);
            if(rs.equals("exit")){
                System.out.println("客户端" + packet.getAddress().getHostAddress() + ":" + packet.getPort() + "下线了");
                System.out.println("------------------------------");
                socket.close();
                break;
            }
            System.out.println(rs);
            //客户端的IP地址+客户端的端口
            System.out.println(packet.getAddress().getHostAddress() + ":" + packet.getPort());
            System.out.println("------------------------------");
        }
    }
}

注意要先启动服务端再启动客户端

运行结果:

TCP通信

  • 特点:面向连接、可靠通信,
  • 通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端
  • Java提供了一个java.net.Socket类来实现TCP通信

TCP通信之-客户端开发

  • 客户端程序就是通过java.net包下的Socket类来实现的

客户端实现步骤

① 创建客户端的Socket对象,请求与服务端的连接
②使用socket对象调用qetOutputStream()方法得到字节输出流
③ 使用字节输出流完成数据的发送
④ 释放资源:关闭socket管道

TCP通信-服务端程序的开发

  • 服务端是通过java.net包下的Serversocket类来实现的

服务端实现步骤

① 创建ServerSocket对象,注册服务端端口。
② 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象
③ 通过Socket对象调用getinputStream()方法得到字节输入流、完成数据的接收
④ 释放资源:关闭socket管道

代码演示:

客户端:
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建Socket对象,并同时请求与服务端程序连接
        Socket socket = new Socket("127.0.0.1",8888);

        //从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        //3、把字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("请说:");
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                break;
            }
            //4、发送数据
            dos.writeUTF(msg);
        }
        dos.close();
        socket.close();
    }
}
服务端:一发一收
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws Exception {
        //1、创建ServerSocket的对象,同时未服务端注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = null;
        DataInputStream dis = null;


            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            socket = serverSocket.accept();

            //3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();

            //4、把原始的字节输入流包装成数据输入流
            dis = new DataInputStream(is);

            //5、使用数据输入流读取客户端发送过来的消息
        while (true) {
            try {
                String rs = dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                System.out.println(socket.getRemoteSocketAddress() + "离线了");
                dis.close();
                socket.close();
                serverSocket.close();
                break;
            }
            System.out.println(socket.getRemoteSocketAddress());
            System.out.println("----------------------------------");
        }

    }
}

运行结果:

服务端:多发多收
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server2 {
    public static void main(String[] args) throws Exception {
        //1、创建ServerSocket的对象,同时未服务端注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = null;
        DataInputStream dis = null;

        while (true) {
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            socket = serverSocket.accept();
            //3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }

    }
}

ServerReaderThread线程

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

public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);

            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(rs);
                } catch (Exception e) {
                    System.out.println(socket.getRemoteSocketAddress() + "离线了");
                    System.out.println("----------------------------------");
                    dis.close();
                    socket.close();
                    break;
                }
                System.out.println(socket.getRemoteSocketAddress());
                System.out.println("----------------------------------");
            }

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

    }
}

运行结果:

TCP:端口转发

群聊案例:

客户端:

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception{
        //1、创建Socket对象,并同时请求与服务端程序连接
        Socket socket = new Socket("127.0.0.1",8888);

        //启动客户端接收消息线程
        new ClientReaderThread(socket).start();

        //从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        //3、把字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        while (true) {
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                break;
            }
            //4、发送数据
            dos.writeUTF(msg);
            dos.flush();
        }
        dos.close();
        socket.close();
    }
}

客户端读线程类:

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

public class ClientReaderThread extends Thread{
    private Socket socket;

    public ClientReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);

            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    System.out.println("我离线了");
                    System.out.println("----------------------------------");
                    dis.close();
                    break;
                }
            }

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

服务端:

import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    public static ArrayList<Socket> sockets = new ArrayList<>();    //客户端群,记录所有客户端
    public static void main(String[] args) throws Exception {
        //1、创建ServerSocket的对象,同时未服务端注册端口
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = null;
        DataInputStream dis = null;
        System.out.println("------------服务端启动------------");

        while (true) {
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            socket = serverSocket.accept();
            sockets.add(socket);    //每连接到一个客户端就加入客户端群(集合)
            //3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }

    }
}

服务端读线程类:

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

public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();   //字节输入流
            DataInputStream dis = new DataInputStream(is);  //打包为数据输入流

            while (true) {
                try {
                    System.out.println("----------------------------------");
                    String msg = dis.readUTF();  //收到消息
                    System.out.println(socket.getRemoteSocketAddress() + ":" + msg);
                    SendMsgToAll(socket.getRemoteSocketAddress() + ":" + msg);
                } catch (Exception e) {
                    System.out.println(socket.getRemoteSocketAddress() + "离线了");
                    Server.sockets.remove(socket);
                    System.out.println("----------------------------------");
                    dis.close();
                    socket.close();
                    break;
                }
                System.out.println("----------------------------------");
            }

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

    }

    private void SendMsgToAll(String msg) throws Exception{
        for (Socket socket : Server.sockets) {  //遍历群聊里每位成员
            OutputStream os = socket.getOutputStream();     //从每位成员的通信管道中得到一个字节输出流
            DataOutputStream dos = new DataOutputStream(os);    //包装成数据输出流
            dos.writeUTF(msg);   //发出数据
            dos.flush();    //刷新
        }
    }
}

运行结果:

BS架构的基本原理

将web浏览器作为客户端,统一了客户端

注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据

HTTP协议规定:响应给浏览器的数据格式必须满足如下格式

使用线程池进行优化

代码演示:

服务端:

package com.zeyu.tcp;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class browser_Server {
    public static void main(String[] args) throws Exception {
        System.out.println("------------服务器启动------------");
        //1、创建ServerSocket的对象,同时为服务器注册端口
        ServerSocket serverSocket = new ServerSocket(8080);

        //创建出一个线程池,负责处理通信管道的任务
        ThreadPoolExecutor poll = new ThreadPoolExecutor(16 * 2, 16 * 2, 0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        while (true) {
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接
            Socket socket = serverSocket.accept();

            System.out.println("有人访问" + socket.getRemoteSocketAddress());

            //3、把这个客户端对应的socket通信管道,交给一个独立的线程处理
            poll.execute(new ServerRunnable(socket));
        }

    }
}

ServerRunnable

package com.zeyu.tcp;

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

public class ServerRunnable implements Runnable{
    private Socket socket;

    public ServerRunnable(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            OutputStream os = socket.getOutputStream();
            PrintStream pos = new PrintStream(os);
            pos.println("HTTP/1.1 200 OK");
            pos.println("Content-Type:text/html;charset=UTF-8");
            pos.println();
            pos.println("<div style='color:blue;font-size:60px;text-align:center'>世界都承载在你小小的肩膀上</div>");
            pos.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端(浏览器):

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A泽予

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值