目录
一、网络编程基本知识
第一种方式: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)
写一个简单的例子:客户端连接服务器,只写一句话
第一步:创建客户端和服务端,并让客户端连接上服务端
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中使用的是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的问题依然没有解决
以下只有服务端代码,没有客户端代码,客户端代码可以使用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); } }详情请看马士兵关于线程与高并发的视频