ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java从零开始学网络编程(BIO篇)

2021-05-29 18:01:25  阅读:165  来源: 互联网

标签:BIO java socket new 从零开始 import Java Socket 客户端


从零开始学网络编程

概述

网络编程 离不开数据的传输,而数据的传输在java中使用的是IO流,最常用的就是InputStream字节输入流和OutputStream``字符输出流以及BufferReader字符输入流和BufferWirter```字符输出流。在此先介绍一下几个重要的概念

  1. O S I 七 层 数 据 模 型 \color{green}{OSI七层数据模型} OSI七层数据模型
    OSI为数据通信制定了一个 标 准 \color{red}{标准} 标准,这个 标 准 \color{red}{标准} 标准就是七层模型应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
    每次都有自己特定的作用并且会对数据进行一次包装。而Socket编程就处于传输层。而TCP和UDP协议就是传输层协议。
  2. 同 步 与 异 步 \color{green}{同步与异步} 同步与异步
    同步与异步:同步是指在调用的时候,在没有处理完毕就不会等到返回值,简而言之就是调用者主动等待结果返回,而异步却相反,在调用时候,不会立马得到结果,而是由客户端通知什么时候会返回。
  3. 阻 塞 与 非 阻 塞 \color{green}{阻塞与非阻塞} 阻塞与非阻塞
    阻塞与非阻塞:阻塞与非阻塞的区别在于 在等待返回值的时候调用者的状态。阻塞是指在等待返回结果之前, 当前线程不会再做其他的事情,只会乖乖的等待,而非阻塞恰恰相反,在等待的过程中可以去做其他事情,等到异步通知返回了 再去执行。
  4. B I O 是 数 据 同 步 阻 塞 式 \color{green}{BIO是数据同步阻塞式} BIO是数据同步阻塞式
    意味着在服务端收到客户端的请求时候,会一直等待客户端的一些列操作完之后(包括读写),才能进行其他客户端的请求。且会一直阻塞,啥事不做。直到改客户端退出了,再进行另一个客户端的连接与读写

正文

演示同步时服务端的示例代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
    public static void main(String[] args) {
        try {
            //1本地创建ServerSocket并且绑定端口9999
            ServerSocket server = new ServerSocket(9999);
            //2 接受客户端连(此方法是阻塞式的,如果没有连接则会一直等待)
            Socket socket = server.accept();
            //3获取客户端的字节输入流
            InputStream inputStream = socket.getInputStream();
            //将字节流转字符输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String msg = "";
            //4按行接受客户端的信息(此方法是阻塞式的,会一直读取客户端的信息)
            while((msg = bufferedReader.readLine())!=null){
                System.out.println("收到客户端的信息"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

演示同步客户端的代码

package com.demo.bio.one;

import java.io.IOException;
import java.io.OutputStream;

import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        //创建客户端套接字
        Socket socket = new Socket();
        try {
            //连接本地端口9999
            socket.connect(new InetSocketAddress(9999),1000);
            //得到字节输出流
            OutputStream outputStream = socket.getOutputStream();
            //将字节输出流转成打印流 
            PrintStream printStream =new PrintStream(outputStream);
            //获取键盘输入
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNext()){
                String next = scanner.next();
                //将数据输出
                printStream.println(next);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

先启动服务端,再启动客户端。此时客户单在控制台输入文字,然后再服务端可以接收到信息并打印在服务端的控制台。若此时又有一个客户端进行服务端的连接,会发现一直无法连接,是因为服务器是一次性的服务,在 Socket socket = server.accept()的时候,接受一次客户端连接后,此时的代码就不会在执行了,若需要执行 此时我们将服务端代码

public class Server {
    public static void main(String[] args) {
        try {
            //1本地创建ServerSocket并且绑定端口9999
            ServerSocket server = new ServerSocket(9999);
            //2 接受客户端连(此方法是阻塞式的,如果没有连接则会一直等待)
            while(true){
	            Socket socket = server.accept();
	            //3获取客户端的字节输入流
	            InputStream inputStream = socket.getInputStream();
	            //将字节流转字符输入流
	            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
	            String msg = "";
	            //4按行接受客户端的信息(此方法是阻塞式的,会一直读取客户端的信息)
	            while((msg = bufferedReader.readLine())!=null){
	                System.out.println("收到客户端的信息"+msg);
	            }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此时同时二个客户端连接服务端会发现,第二个客户端的数据无论如何也无法到达服务端,这就是因为BIO阻塞的,在main线程处理第一个客户端的时候,只要当前客户端在线,那么就无法处理其他的客户端。这时候如果要求服务端能够同时出来多个客户端,那么就需要用 线 程 \color{red}{线程} 线程单独处理一个客户端。

多客户端请求服务端代码修改

package com.demo.bio.two;

import java.net.ServerSocket;
import java.net.Socket;
public class Server {
    public static void main(String[] args) {
         try{
             ServerSocket serverSocket = new ServerSocket(9999);
             while (true){
                 Socket socket = serverSocket.accept();
                 //当新的客户端到达时,开启新的线程去处理。然后主线程继续接受新的客户端
                 new Thread(  new HandlerSocket(socket)).start();
             }
         }catch (Exception e){
             e.printStackTrace();
         }
    }
}

客户端处理请求HandlerSocket线程

package com.demo.bio.two;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class HandlerSocket implements Runnable {
    private Socket socket;
    public HandlerSocket(Socket socket) {
        this.socket = socket;
    }
    public void run() {
       try(InputStream inputStream = socket.getInputStream()){
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
           String msg =null;
           while((msg=bufferedReader.readLine())!=null){
               System.out.println("收到客户端消息"+msg);
           }
       }catch (Exception e){
       }
    }
}

原来客户端代码不变。此时可以接受来自不同客户端的数据,分别在不同的客户端控制台输入
客户端一
客户端二
服务端
那么此时就会出现一个问题。就是随着客户端的不断增加 ,此时服务端的线程数也随着客户端的增加而不断的增加。如果当线程的数量到达一定值时,JVM无法为新的线程创建栈的空间,那么就会出现````OutOfMemeroy```的错误。系统就崩溃了。这是我们不愿意看见的。此时就需要控制线程的数量,那么如果控制线程的数量,既然是用线程处理了客户端的连接,那么我们就用线程相关的技术,使用线程池技术,将服务端的代码改造

线程池改造服务端代码

package com.demo.bio.three;

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

public class Server {
    public static void main(String[] args) {
        try (ServerSocket server = new ServerSocket(9999)) {
        //创建一个线程池
            HandlerSocketThreadPools pools = new HandlerSocketThreadPools(3,10);
            while(true){
                Socket socket = server.accept();
                pools.execute(new HandlerSocket(socket));
            }
        }catch (Exception e){
          e.printStackTrace();
        }
    }
}

HandlerSocketThreadPools 的代码如下

package com.demo.bio.three;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class HandlerSocketThreadPools {
    private ThreadPoolExecutor threadPoolExecutor;
    public HandlerSocketThreadPools(int maxSize,int size){
        threadPoolExecutor= new ThreadPoolExecutor(3,maxSize,120, TimeUnit.MICROSECONDS, new ArrayBlockingQueue(size));
    }
    public void execute(Runnable runnable){
        threadPoolExecutor.execute(runnable);
    }
}

此时就可以在一定数量的线程池中执行原来的代码,但是这里设置了核心线程数为3个,意味着最多只有3个客户端能够连接,当第四个客户端连接的时候,就只能进入阻塞队列中等待,这时候就只能用NIO来解决。

通过本篇文章 我们总结一下。
BIO是一种同步阻塞IO模型。一个客户端对应这一个线程。如果客户端过多,容易使程序崩溃。

下面使用BIO开发一个聊天室

聊天室服务端代码

package com.demo.bio.chatroom;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;
public class Server {
     //创建线程安全的集合 江所有在线的客户端保存
    public static CopyOnWriteArrayList<Socket> onLine = new CopyOnWriteArrayList();
    public static void main(String[] args) {
        try{
            ServerSocket server = new ServerSocket();
            server.bind(new InetSocketAddress(9999));
            while(true){
                Socket socket = server.accept();
                //将socket保存到在线列表中
                onLine.add(socket);
                new Thread(new HandlerSocket(socket)).start();
            }
        }catch (Exception e){
              e.printStackTrace();
        }
    }
}

服务端单独处理客户端线程

package com.demo.bio.chatroom;

import java.io.*;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

public class HandlerSocket  implements Runnable{
     //当前连接的客户端
    private Socket socket;
    private int port;

    public HandlerSocket(Socket socket){
        this.socket = socket;
        this.port = socket.getPort();
    }
    @Override
    public void run() {
        System.out.println("客户端"+port+"加入聊天室");
        try{
           //获取当前客户端的字符输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            //想所有的在线客户端发送新客户端上线消息
            sendLoginToOnline(socket);
            //读取客户端发送的数据
            while((msg=reader.readLine())!=null){
                System.out.println("客户端:"+port+"发送的消息"+msg);
                sendMsgToOnline(socket,msg);
            }
            reader.close();
        }catch (Exception e){
            e.printStackTrace();
            Server.onLine.remove(socket);
        }
    }
   //向所有的客户端发送上线消息
    private void sendLoginToOnline(Socket socket) {
        CopyOnWriteArrayList<Socket> onLine = Server.onLine;
        for (Socket data : onLine) {
            if(socket!=data){
                try{
                    PrintStream printStream = new PrintStream( data.getOutputStream());
                    printStream.println(socket.getPort()+"加入聊天室");
                    printStream.flush();
                }catch (Exception e){
                    System.out.println("发送数据出现错误");
                    onLine.remove(data);
                }
            }

        }
    }
   //想所有的在线客户端转发消息
    private void sendMsgToOnline(Socket socket,String msg) {

        CopyOnWriteArrayList<Socket> onLine = Server.onLine;
        for (Socket data : onLine) {
            if(socket!=data){
                try{
                    PrintStream printStream = new PrintStream( data.getOutputStream());
                    printStream.println(socket.getPort()+":"+msg);
                    printStream.flush();
                }catch (Exception e){
                    System.out.println("发送数据出现错误");
                    onLine.remove(data);
                }
            }

        }
    }
}

客户端代码

package com.demo.bio.chatroom;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {

    public static void main(String[] args) {
        Socket socket=null;
        PrintStream printStream = null;
        try{
             socket = new Socket();
            socket.connect(new InetSocketAddress(9999));
             //启动一个线程专门读取服务端转发给客户端的消息
            new Thread(new HandlerRead(socket)).start();

            OutputStream outputStream = socket.getOutputStream();
             printStream = new PrintStream(outputStream);
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()){
                String msg = scanner.nextLine();
                printStream.println(msg);
                printStream.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
            try {
                socket.close();
                printStream.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

客户端单独接受服务端转发消息的线程

package com.demo.bio.chatroom;

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

public class HandlerRead implements Runnable {

    private Socket socket;


    public HandlerRead(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
            try{
                   InputStream inputStream = socket.getInputStream();
                   BufferedReader read = new BufferedReader(new InputStreamReader(inputStream));
                   String msg;
                   while((msg=read.readLine())!=null){
                       System.out.println(msg);
                   }
               
            }catch (Exception e){
                  e.printStackTrace();
            }
    }
}

标签:BIO,java,socket,new,从零开始,import,Java,Socket,客户端
来源: https://blog.csdn.net/zhs145612zhs/article/details/116985662

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有