客户端&服务器访问方式的演进

目录

一、网络编程基本知识

第一种方式:BIO/OIO 

二、网络编程基本实例

1、客户端发一句话给服务端

2、客户端给服务端发一个文本

3、客户端给服务端发送完消息后,服务端再给客户端一个回馈

三、URL编程


一、网络编程基本知识

前言:

        端口号与IP地址的组合得出一个网络套接字:Socket,网络编程通常称为socket编程。

1、网络模型:

    a.通讯方式:TCP、UDP

        i. TCP : 可靠连接,使命必达,速度慢

        ii. UDP : 不可靠,速度快

2、 TCP的编程模型

    a. BIO / OIO

        i. Blocking IO / Old IO

    b. NIO(linux支持)

         i. New IO : Non-Blocking IO

         ii. Selector

         iii. ByteBuffer (single pointer)

    c. AIO(仅仅windows支持)

         i. Asynchronous IO

2、Netty

    a. 封装了NIO ByteBuf(read pointer、writer pointer)

第一种方式:BIO/OIO 

写一个简单的例子:客户端连接服务器,只写一句话

第一步:创建客户端和服务端,并让客户端连接上服务端

server:

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8899));
System.out.println("hello");
Socket accept = serverSocket.accept();
System.out.println("world");

client:

Socket socket = new Socket("localhost",8899);

解析:

在以上的server端的代码中,运行以后”hello“会输出出来,但是,accept()方法是阻塞的,也就是说,如果客户端没有启动,或者说没有客户端连接到服务端,此时”world不会输出出来“。客户端启动,则”world“就会输出出来。

第二步:

客户端连接上服务端传给服务端一些信息

server:
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8899));
        System.out.println("hello");
        Socket accept = serverSocket.accept();
        System.out.println("world");
        // 获取输入流
        InputStream is = accept.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String str = br.readLine();
        
        System.out.println(str);
        // 关闭资源
        is.close();
        accept.close();
        serverSocket.close();
client:
        Socket socket = new Socket("localhost",8899);
        OutputStream oos = socket.getOutputStream();
        // 获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
        bw.write("你好啊,我是Michael");
        bw.newLine();
        bw.flush();
        // 关闭资源
        bw.close();

解析:此时服务端不仅仅会输出”hello“、”world“还会输出客户端传过来的信息:”你好啊,我是Michael“.

此时server中的br.readLine()是一个阻塞方法,即:如果客户端只是连接上,但是没有传过来数据,服务端会在br.readLine()处阻塞。如第三步所示

第三步:

server端的代码不变

client代码如下:

        Socket socket = new Socket("localhost",8899);
        OutputStream oos = socket.getOutputStream();
        // 获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
        // 下面的代码是等待键盘输入内容,如果不输入内容,下面的代码会进入阻塞状态
        System.in.read();

解析:此时启动客户端,服务端仅仅会输出"hello"、"world"。并进入阻塞状态。

这种情况其实是一种网络攻击,叫拒绝服务攻击,也就是说我连接上你的服务器,但就是什么事也不干,其他正常的连接也连接不上来,这就是一种攻击。

这种模式类似于:一个餐厅开门后只能服务于一个客户,客户吃完饭,餐厅会关门,如果需要对下一个客户提供服务,需要重新开张。

以上模式只能接受一个客户端。如何改进呢:

第一种改进方式:

server:        
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8899));
        boolean started = true;
        while (started){
            Socket accept = serverSocket.accept();
            // 获取输入流
            InputStream is = accept.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String str = br.readLine();

            System.out.println(str);
            // 关闭资源
            is.close();
            accept.close();
        }
        serverSocket.close();
client
        Socket socket = new Socket("localhost",8899);
        OutputStream oos = socket.getOutputStream();
        // 获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(oos));
        bw.write("你好啊,我是Michael");
        bw.newLine();
        bw.flush();
        bw.close();

解析:启动服务端,重复启动客户端,没启动一起客户端,服务端会收到"你好啊,我是Michael"。这种模式类似于,饭店每次只能允许一个人在里面吃饭,此时其他想要吃饭的客户必须得等上一个客户吃完饭走出去餐厅,才能进去。也就是其他客户端会进入阻塞模式。

第二种改进方式:

这种方式为每一个客户端生成一个线程,当大量客户端同时访问此服务时,由于起的线程太多,服务基本上就挂了

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket socket = new ServerSocket();
        socket.bind(new InetSocketAddress("localhost", 8888));
        boolean started = true;
        while (started) {
            new Thread(()->{
                try {
                    Socket s = socket.accept();
                    BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                    String str = br.readLine();
                    System.out.println(str);
                    br.close();
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

        }
        socket.close();
    }
}
public class Client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("localhost", 8888);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        bw.write("mashibing");
        bw.newLine();
        bw.flush();
        bw.close();
    }
}

这种方式类似于:每个顾客去餐厅吃饭时,餐厅都找一个属于这个顾客的服务员(也就是线程)去为他服务,不妨碍其他顾客吃饭。而主线程的作用就是为其分配服务员(线程)。

第二种方式:NIO

NIO中使用的是ServerSocketChannel模式,channel就是通道的意思

public class Server2 {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ServerSocket socket = ssc.socket();
        socket.bind(new InetSocketAddress("localhost",8899));
        // 将此通道设置为非阻塞式
        ssc.configureBlocking(false);

        System.out.println("Server started, listening on:" + ssc.getLocalAddress());
        // 启动大管家
        Selector selector = Selector.open();
        // 我让大管家管什么事呢?让大管家负责有连接需要连接过来的服务
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            // 开始轮询,有事情就会处理事情,在轮询时是阻塞状态
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()){
                SelectionKey key = it.next();
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) {
        // 客户端连接的情况
        if(key.isAcceptable()){
            try {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                // 有客户端进来时单独建立一个新的通道,同时也将此通道设置为非阻塞式
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                // 此通道注册一个读数据的通道还是什么不清楚
                sc.register(key.selector(),SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
            }
        }else if(key.isReadable()){
            SocketChannel sc = null;
            try {
                sc = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(512);
                buffer.clear();
                int len = sc.read(buffer);
                if(len!=-1){
                    System.out.println(new String(buffer.array(),0,len));
                }
                ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
                sc.write(bufferToWrite);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(sc!=null){
                    try {
                        sc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

以上代码是NIO代码,只用了一个线程。Nertty就是对其的封装。NIO使用了ByteBuffer,其底层封装了一个字节数组,和一个指针,记录了读写数据的位置,但是它不好用,也容易出错。而Netty底层使用了ByteBuf,也是一个字节数组,但是其有两个指针分别记录读写数据的位置,并且,Netty可以操作直接内存即零拷贝,不需要经过JDK的内存,直接使用操作系统的内存,少了拷贝数据的过程,增加效率。

此种模型因为是单线程的,当大管家在处理某一张桌子(客户端)的需求时,其他需求都需要排队等待,改进方式如下:

NIO另一种模型:一个大管家+多个服务员(线程),线程数固定,大管家只负责接客,服务员负责每个客人的业务需求。服务员忙完后回到线程池进入等待状态,有了新的客户再去服务。但是ByteBuffer的问题依然没有解决

Netty:

以下只有服务端代码,没有客户端代码,客户端代码可以使用BIO中的客户端

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 负责接客
        NioEventLoopGroup bossGroups = new NioEventLoopGroup(2);
        // 负责服务
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);
        // Server启动辅助类
        ServerBootstrap b = new ServerBootstrap();

        b.group(bossGroups,workerGroup);
        // 异步全双工
        b.channel(NioServerSocketChannel.class);
        // netty 帮我们内部处理accept的过程
        b.childHandler(new MyChildInitializer());
        b.bind(8888).sync();

    }
}
class MyChildInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        System.out.println("a client connected !");
    }
}

一个完整的Netty例子

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 负责接客
        NioEventLoopGroup boosGroup = new NioEventLoopGroup(2);
        // 负责服务
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);
        // server启动辅助类
        ServerBootstrap b = new ServerBootstrap();
        b.group(boosGroup,workerGroup);
        // 指定使用的channel类型:异步全双工
        b.channel(NioServerSocketChannel.class);

        b.childHandler(new MyChildInitializer2());
        ChannelFuture future = b.bind(8899).sync();
        // 优雅的关闭方式
        future.channel().closeFuture().sync();
        boosGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();

    }
}
class MyChildInitializer2 extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new MyChildHandler2());
    }
}
class MyChildHandler2 extends ChannelInboundHandlerAdapter{

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println(buf.toString());
        ctx.writeAndFlush(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}
public class NettyClient {
    public static void main(String[] args) throws InterruptedException, IOException {
        NioEventLoopGroup worker = new NioEventLoopGroup(1);
        Bootstrap bs = new Bootstrap();
        bs.group(worker);
        bs.channel(NioSocketChannel.class);
        bs.handler(new MyChannelInit());
        ChannelFuture future = bs.connect("localhost", 8899).sync();
        // 等待关闭
        future.channel().closeFuture().sync();
        System.out.println("go on");
        worker.shutdownGracefully();
    }
}
class MyChannelInit extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast(new MyHandler());
    }
}
class MyHandler extends ChannelInboundHandlerAdapter{
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg.toString());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("mashbing".getBytes());
        ctx.writeAndFlush(buf);
    }
}

详情请看马士兵关于线程与高并发的视频

二、网络编程基本实例

1、客户端发一句话给服务端

public class client {
    public static void main(String[] args)  {
        Socket socket = null;
        OutputStream oos = null;
        try {
            // 1、创建socket对象,指明服务器端的ip和端口号
            InetAddress address = InetAddress.getByName("localhost");
            socket = new Socket(address, 8899);
            // 2、获取一个输出流,用于输出数据
            oos = socket.getOutputStream();
            // 3、写出数据的操作
            oos.write("你好啊,我是client".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4、资源关闭
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class SeverTest {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            // 1、创建服务器端的ServerSocket,指明自己的端口号
            ss = new ServerSocket(8899);
            // 2、调用accept方法,表示接受客户端的socket
            socket = ss.accept();
            // 3、获取输入流
            is = socket.getInputStream();
//        // 不建议这个方式:
//        byte[] bytes = new byte[10];
//        int len;
//        while ((len = is.read(bytes)) != -1) {
//            String str = new String(bytes,0,len);
//            System.out.println(str);
//        }
            // 4、读取输入流中的数据
            // ByteArrayOutputStream内部自己封装了一个字节数组,也会自动扩容,使用这个时,会先将数据写入自己的字节数组中。
            baos = new ByteArrayOutputStream();
            byte[] bytes = new byte[10];
            int len;
            while ((len = is.read(bytes)) != -1) {
                baos.write(bytes, 0, len);
            }
            System.out.println(baos.toString());
            System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            is.close();
            ss.close();
            baos.close();
            socket.close();
        }
    }
}

2、客户端给服务端发一个文本

public class client1 {
    public static void main(String[] args) throws Exception {
        Socket socket = null;
        OutputStream oos = null;
        FileInputStream fis = null;
        try {
            socket = new Socket(InetAddress.getByName("localhost"), 8899);
            oos = socket.getOutputStream();
            fis = new FileInputStream(new File("hello.txt"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fis.read(bytes)) != -1) {
                oos.write(bytes,0,len);
            }
            System.out.println("文件写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
            oos.close();
            fis.close();
        }
    }
}
public class server1 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            serverSocket = new ServerSocket(8899);
            socket = serverSocket.accept();
            is = socket.getInputStream();
            fos = new FileOutputStream(new File("b.txt"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len=is.read(bytes))!=-1){
                fos.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            serverSocket.close();
            socket.close();
            is.close();
            fos.close();
        }
    }
}

3、客户端给服务端发送完消息后,服务端再给客户端一个回馈

public class client2 {
    public static void main(String[] args) throws Exception {
        Socket socket = null;
        OutputStream oos = null;
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            socket = new Socket(InetAddress.getByName("localhost"), 8899);
            oos = socket.getOutputStream();
            fis = new FileInputStream(new File("hello.txt"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fis.read(bytes)) != -1) {
                oos.write(bytes,0,len);
            }
            // 此语句的作用是告诉客户端我的消息发送停止了,如果没有此语句,
            // 因为server的read方法是阻塞的,所以会erver会一直在读数据
            socket.shutdownOutput();
            System.out.println("文件写入成功");

            InputStream is = socket.getInputStream();
            baos = new ByteArrayOutputStream();
            byte[] bytes1 = new byte[1024];
            int len2;
            while ((len2=is.read(bytes1))!=-1){
                baos.write(bytes1,0,len2);
            }
            System.out.println(baos.toString());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            fis.close();
            oos.close();
            socket.close();
            baos.close();
        }
    }
}
public class server2 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        FileOutputStream fos = null;
        OutputStream oos = null;
        try {
            serverSocket = new ServerSocket(8899);
            socket = serverSocket.accept();
            is = socket.getInputStream();
            fos = new FileOutputStream(new File("c.txt"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len=is.read(bytes))!=-1){
                fos.write(bytes,0,len);
            }
            System.out.println("图片传输完成");
            oos = socket.getOutputStream();
            oos.write("你好,信的内容我已经收到了,很好,不错!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            fos.close();
            serverSocket.close();
            socket.close();
            is.close();
            oos.close();
        }
    }
}

三、URL编程

public class URLTest {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("https://www.bilibili.com/video/BV1Kb411W75N?p=629");
        // 获取该URL的协议名
        System.out.println(url.getProtocol());
        // 获取该URL的主机名
        System.out.println(url.getHost());
        // 获取该URL的端口号
        System.out.println(url.getPort());
        // 获取该URL的文件路径
        System.out.println(url.getPath());
        // 获取该URL的文件名
        System.out.println(url.getFile());
        // 获取该URL的查询名
        System.out.println(url.getQuery());
    }
}
public class URLTest02 {
    public static void main(String[] args) throws IOException {
        URL url = new URL("\"https://www.bilibili.com/video/BV1Kb411W75N?p=629\"");
        HttpURLConnection urlConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.connect();
            is = urlConnection.getInputStream();
            fos = new FileOutputStream("d.txt");
            byte[] bytes = new byte[1024];
            int len;
            while ((len=is.read(bytes))!=-1){
                fos.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            is.close();
            fos.close();
            urlConnection.getClass();
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值