JavaSE进阶、网络编程

目录

一、网络通信

 二、网络通信基本模式

C/S模式

 B/S模式

三、网络通信三要素

1、IP地址

2、IP地址操作类-InetAddress

3、端口号

4、协议

三次握手原理

 四次挥手原理

四、UDP通信

1、UDP快速入门

2、UDP通信的实现 (一发一收)

3、UDP实现多发多收

Address already in use: bind 经典错误

4、广播和组播

​编辑

五、TCP通信

1、TCP实现多发多收

2、TCP实现多发多收,同时接收多个客户端的消息

3、使用线程池优化处理多个客户端

4、即时通信

 5、B/S模式设计


一、网络通信

 二、网络通信基本模式

  • C/S模式

  •  B/S模式

客户端在网页实现,Web开发使用B/S模式

客户端又称用户端是给用户使用的,服务端是为客户端提供服务,相当于后台

三、网络通信三要素

1、IP地址

 

 本计算机要访问网站并不会使用ip地址,而是使用它的域名,计算机会将这个域名发送到自带的DNS域名解析服务器中,返回存储的域名对应的ip地址,如果本计算机中没有记录,他就会通过网络访问运营商的DNS域名解析服务器来获取ip地址,再返回给本计算机,本计算机通过这个ip地址访问网站服务器,然后返回信息到本计算机。

ip地址与域名是一对多的关系。一个ip地址可以对应多个域名,但是一个域名只有一个ip地址。ip地址是数字组成的,不方便记忆,所以有了域名,通过域名地址就能找到ip地址。 

2、IP地址操作类-InetAddress

public static void main(String[] args) throws Exception {
        //1、获取本机地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1);
        //获取本机名
        System.out.println(ip1.getHostName());
        //获取本机ip
        System.out.println(ip1.getHostAddress());

        //2、获取域名的ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        //3、获取公网IP对象
        InetAddress ip3 = InetAddress.getByName("36.152.44.96");
        System.out.println(ip3.getHostAddress());
        System.out.println(ip3.getHostName());

        //4、判断能否ping互通  多少毫秒内能否互通
        System.out.println(ip1.isReachable(5000));

    }

3、端口号

标记主机中运行的进程(程序)

4、协议

 

三次握手原理

 第一次握手相当于向服务器确认客户端能否发送信息给服务端,第二次握手相当于向客户端确认服务端能否接收和回应消息,第三次握手相当于向服务端确认客户端能否接受和回应消息。

 四次挥手原理

四、UDP通信

1、UDP快速入门

模型概念:UDP相当于两个人以丢掷的方式传菜,发送端看准接收端将菜丢过去,然后让接收端去接菜,中间的风险因素不可控,菜(数据)可能会丢失,所以不安全。

 也可以指定发送端的端口号

2、UDP通信的实现 (一发一收)

public class ClientDemo1 {
    //发送端  客户端
    public static void main(String[] args) throws Exception {
        System.out.println("============客户端启动==========");
        //1、创建发送端对象 (人)
        DatagramSocket socket = new DatagramSocket();

        //2、创建一个数据包对象封装数据 (韭菜盘子)
        /**
         * public DatagramPacket(byte buf[], int length,
         *                           InetAddress address, int port) {
         *         this(buf, 0, length, address, port);
         *     }
         *     参数一:封装要发送的数据 (韭菜)
         *     参数二:发送数据的大小
         *     参数三:接收端的IP地址
         *     参数四:接收端的端口号
         */
        byte[] buffer = "我是一个卷心菜".getBytes();
        //服务端也在本机,所以填本机的端口号,不在本机则InetAddress.getByName()
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

        //3、发送数据
        socket.send(packet);

        //4、关闭管道对象
        socket.close();

    }
}


//一发一收
public class ServerDemo2 {
    //接收端 服务端
    public static void main(String[] args) throws Exception {
        //先启动服务端再启动客户端
        System.out.println("============服务端启动==========");
        //1、创建接收端对象  (接菜的人)
        DatagramSocket socket = new DatagramSocket(8888);

        //2、创建一个数据包接收对象 (韭菜盘子)
        //每个数据包大小限制在64kB以内
        byte[] bytes = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);

        //3、等待接收数据
        socket.receive(packet);

        //4、取出数据
        //读多少取多少
        int len = packet.getLength();
        String s = new String(bytes,0,len);
        System.out.println(s);

        //5、获取发送端的ip地址
        String ip = packet.getSocketAddress().toString();
        System.out.println("对方地址" + ip);
        int port = packet.getPort();
        System.out.println("对方端口号" + port);

        //6、关闭管道
        socket.close();
    }
}

3、UDP实现多发多收

//多发多收
public class ClientDemo2 {
    //发送端  客户端
    public static void main(String[] args) throws Exception {
        System.out.println("============客户端启动==========");
        //1、创建发送端对象 (人)
        //可以指定端口号
        DatagramSocket socket = new DatagramSocket(6666);

        //扫描器对象
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入:");
            //用户输入弹幕
            String s = sc.nextLine();

            if ("N".equals(s)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            //封装用户数据
            byte[] buffer = s.getBytes();
            //2、创建一个数据包对象封装数据 (韭菜盘子)
            //服务端也在本机,所以填本机的端口号,不在本机则InetAddress.getByName()
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

            //3、发送数据
            socket.send(packet);
        }
    }
}


public class ServerDemo2 {
    //接收端 服务端
    public static void main(String[] args) throws Exception {
        //先启动服务端再启动客户端
        System.out.println("============服务端启动==========");
        //1、创建接收端对象  (接菜的人)
        DatagramSocket socket = new DatagramSocket(8888);

        //2、创建一个数据包接收对象 (韭菜盘子)
        //每个数据包大小限制在64kB以内
        byte[] bytes = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(bytes,bytes.length);

        while (true) {
            //3、等待接收数据
            socket.receive(packet);

            //4、取出数据
            //读多少取多少
            int len = packet.getLength();
            String s = new String(bytes,0,len);
            System.out.println(s);
        }

    }
}

Address already in use: bind 经典错误

一个端口只能被一个程序使用,这里绑定了两个8888的端口,会报错

4、广播和组播

//广播的实现
//创建一个数据包对象封装数据 (韭菜盘子)
//服务端也在本机,所以填本机的端口号,不在本机则InetAddress.getByName()
//使用广播地址255.255.255.255,通过广播地址通信
//局域网内声明同样的端口就能接收到消息
DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
        InetAddress.getByName("255.255.255.255"),8888);

//3、发送数据
socket.send(packet);

System.out.println("============服务端启动==========");
        //1、创建接收端对象  (接菜的人)
        MulticastSocket socket = new MulticastSocket(8888);
        //绑定客户端组播地址
        //socket.joinGroup(InetAddress.getByName("224.1.1.1"));
        //参数一:当前组播地址
        //参数二:当前主机所在网段
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.1.1.1"),8888),
                NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));

//客户端
//封装用户数据
            byte[] buffer = s.getBytes();
            //2、创建一个数据包对象封装数据 (韭菜盘子)
            //服务端也在本机,所以填本机的端口号,不在本机则InetAddress.getByName()
            //组播地址224.0.0.0 ~ 239.255.255.255,区间内的地址都可以作为组播地址
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
                    InetAddress.getByName("224.1.1.1"),8888);

五、TCP通信

管道通信。在java中只要是使用java.net.Socket类实现通信,底层都是使用TCP协议

接收端

public class ClientDemo1 {
    //Socket网络编程 客户端开发
    public static void main(String[] args) throws Exception {
        //1、创建Socket通信管道请求服务端的连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9999);

        //2、从Socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = socket.getOutputStream();
        //将低级的字节输出流包装成打印流
        PrintStream ps = new PrintStream(os);

        //3、发送数据
        ps.print("我是一个卷心菜");

        //4、刷新管道
        ps.flush();

        //5、关闭管道
        //一般不建议关闭这个管道,因为管道一旦连接代表建立了某种联系
        //如果这边关闭管道,可能导致发送的数据丢失,因为关闭管道发送的资源很小,可能会立马被接收端接收
        //如果一端管道关闭,另一端也会立马关闭,导致中间的数据丢失

    }
}

public class ServerDemo1 {
    //Socket网络编程 服务端
    public static void main(String[] args) {

        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(9989);

            //2、必须要调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信
            //只接收了一个客户端连接,目前服务端是单线程,每次只能处理一个客户端的消息
            Socket socket = serverSocket.accept();

            //3、从Socket通信管道中获取一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节输入流转换成字符输入流
            InputStreamReader isr = new InputStreamReader(is);
            //将低级的字符输入流包装成高级的字符输出流
            BufferedReader br = new BufferedReader(isr);

            //4、接收数据,一次接收一行
            String s;

            //如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
            if ((s = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "发送了:" + s);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1、TCP实现多发多收


//服务端
//如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
while ((s = br.readLine()) != null) {
     System.out.println(socket.getRemoteSocketAddress() + "发送了:" + s);
}

//客户端
  while (true) {
            //3、发送数据
            System.out.print("请说:");
            String s = sc.nextLine();
            ps.println(s);
            //4、刷新管道
            ps.flush();
        }

2、TCP实现多发多收,同时接收多个客户端的消息

 服务端只有一个主线程,内部使用一个while死循环来接收Socket连接,因为要接收多个客户端的消息,使用死循环不断接收其他客户端消息,每发来一次客户端请求,就会产生一个socket管道,死循环执行第一轮,将它交给一个独立的子线程进行处理。

 就是一个程序可以被多次同时启动

public class ServerDemo1 {
    //Socket网络编程 服务端
    public static void main(String[] args) {

        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(9989);

            /*
            定义一个死循环,由主线程负责不断的接收客户端的Socket管道连接
            */
            while (true) {
                //2、必须要调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信
                Socket socket = serverSocket.accept();
                //上线逻辑
                System.out.println(socket.getRemoteSocketAddress() + "上线了!!");
                //每接收到一个客户端的Socket管道,就交给一个独立的线程处理
                //开始创建独立线程处理socket
                //来一个管道就交给一个线程
                new ServerThread(socket).start();
                //处理接收数据写在线程的run方法中
            }

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


//创建线程类,重写run方法处理接受的客户端信息
public class ServerThread extends Thread{

    //线程类作为子线程,要接收客户端的管道
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3、从Socket通信管道中获取一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节输入流转换成字符输入流
            InputStreamReader isr = new InputStreamReader(is);
            //将低级的字符输入流包装成高级的字符输出流
            BufferedReader br = new BufferedReader(isr);

            //4、接收数据,一次接收一行
            String s;

            //如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
            while ((s = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "发送了:" + s);
            }
        } catch (Exception e) {
            //如果出异常说明客户端挂了,下线逻辑
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!");
        }
    }
}

3、使用线程池优化处理多个客户端

//多发多收,处理多个客户端
public class ServerDemo1 {
    //Socket网络编程 服务端
    //使用静态变量记住一个线程池不允许修改
    private static ExecutorService pool = new ThreadPoolExecutor(2,4,2, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(9989);
        /*
            定义一个死循环,由主线程负责不断的接收客户端的Socket管道连接
            */
                while (true) {
                    //2、必须要调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信
                    Socket socket = serverSocket.accept();
                    //上线逻辑
                    System.out.println(socket.getRemoteSocketAddress() + "上线了!!");

                    //创建任务对象
                    Runnable runnable = new MyRunnable(socket);

                    //将任务对象交给线程池处理
                    pool.execute(runnable);
                    //处理接收数据写在线程的run方法中
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


//创建Runnable任务对象
public class MyRunnable implements Runnable{
    //让每个任务都接收这个端口
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3、从Socket通信管道中获取一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节输入流转换成字符输入流
            InputStreamReader isr = new InputStreamReader(is);
            //将低级的字符输入流包装成高级的字符输出流
            BufferedReader br = new BufferedReader(isr);

            //4、接收数据,一次接收一行
            String s;

            //如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
            while ((s = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "发送了:" + s);
            }
        } catch (Exception e) {
            //如果出异常说明客户端挂了,下线逻辑
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!");
        }
    }
}

4、即时通信

/**
 * 客户端实现收数据和发数据
 */
public class ClientDemo1 {
    //Socket网络编程 客户端开发
    public static void main(String[] args) throws Exception {
        //1、创建Socket通信管道请求服务端的连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9989);

        //创建一个独立的线程专门负责这个客户端的读消息(服务端随时转发消息过来)
        /**
         * 读消息线程
         */
        new ClientThread(socket).start();

        /**
         * 主线程发消息
         */
        //2、从Socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = socket.getOutputStream();
        //将低级的字节输出流包装成打印流
        PrintStream ps = new PrintStream(os);

        Scanner sc = new Scanner(System.in);

        while (true) {
            //3、发送数据
            System.out.println("请说:");
            String s = sc.nextLine();
            ps.println(s);
            //4、刷新管道
            ps.flush();
        }

    }
}


//客户端线程
public class ClientThread extends Thread{

    //线程类作为子线程,要接收客户端的管道
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3、从Socket通信管道中获取一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节输入流转换成字符输入流
            InputStreamReader isr = new InputStreamReader(is);
            //将低级的字符输入流包装成高级的字符输出流
            BufferedReader br = new BufferedReader(isr);

            //4、接收数据,一次接收一行
            String s;

            //如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
            while ((s = br.readLine()) != null) {
                System.out.println(new Date() + "收到消息:" + s);
            }
        } catch (Exception e) {
            //如果出异常说明客户端将此管道挂了
            System.out.println("服务端把你踢出去了");
        }
    }
}
public class ServerDemo1 {
    //Socket网络编程 服务端

    //定义一个静态的List集合存储当前在线的所有soket管道
    public static List<Socket> allSocketLists = new ArrayList<>();
    public static void main(String[] args) {

        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(9989);
        /*
            定义一个死循环,由主线程负责不断的接收客户端的Socket管道连接
            */
                while (true) {
                    //2、必须要调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信
                    Socket socket = serverSocket.accept();
                    //上线逻辑
                    System.out.println(socket.getRemoteSocketAddress() + "上线了!!");

                    //将上线的socket加进集合中
                    allSocketLists.add(socket);

                    //将任务对象交给线程处理
                    new ServerThread(socket).start();

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

//服务端线程
public class ServerThread extends Thread{

    //线程类作为子线程,要接收客户端的管道
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3、从Socket通信管道中获取一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节输入流转换成字符输入流
            InputStreamReader isr = new InputStreamReader(is);
            //将低级的字符输入流包装成高级的字符输出流
            BufferedReader br = new BufferedReader(isr);

            //4、接收数据,一次接收一行
            String s;

            //如果想接收一行数据,发送端必须要发出一行数据,如果发送端不换行发送则这里会一直等待接收完,会报错
            while ((s = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "发送消息:" + s + new Date());
                //将客户端发送的数据进行端口转发 给所有端口
                //群发消息
                sendMsgToAllSocket(s);
            }
        } catch (Exception e) {
            //如果出异常说明客户端将此管道挂了
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
            //下线了端口关闭,从列表中清除
            ServerDemo1.allSocketLists.remove(socket);
        }
    }

    /**
     * 将客户端发送的数据进行端口转发 给所有端口
     * @param s
     * @throws Exception
     */
    private void sendMsgToAllSocket(String s) throws Exception {
        for (Socket socket : ServerDemo1.allSocketLists) {
            PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println(s);
            ps.flush();
        }
    }
}

 5、B/S模式设计

public class BrownServer {

    //使用静态变量记住一个线程池不允许修改
    private static ExecutorService pool = new ThreadPoolExecutor(2,4,2, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(8080);
        /*
            定义一个死循环,由主线程负责不断的接收客户端的Socket管道连接
            */
                while (true) {
                    //2、必须要调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信
                    Socket socket = serverSocket.accept();
                    //上线逻辑
                    System.out.println(socket.getRemoteSocketAddress() + "上线了!!");

                    //创建任务对象
                    Runnable runnable = new MyRunnable(socket);

                    //将任务对象交给线程池处理
                    pool.execute(runnable);
                    //处理接收数据写在线程的run方法中

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



//Runnable任务类

public class MyRunnable implements Runnable{
    //让每个任务都接收这个端口
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //浏览器 已经与本线程建立了Socket管道
            //响应消息给浏览器显示
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //必须响应HTTP协议格式,否则浏览器不认识消息
            ps.println("Http/1.1 200 OK"); //协议类型和版本,响应成功的消息
            ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页

            //必须发送一个空行开始输入文本内容
            ps.println();

            ps.println("<span style='color:red;font-size:90px'> 《HelloWorld》 </span> ");
            ps.close();
        } catch (Exception e) {
            //如果出异常说明客户端挂了,下线逻辑
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!");
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值