Socket 网络编程(一)

Socket

Socket,又称“套接字”,应用程序通常通过“套接字”向网络发出请求或应答网络请求。

Socket 和 ServerSocket类位于 java.net 包中。对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同级别,它们的工作都是通过 SocketImpl 类及其子类完成的。

套接字之间的连接过程可以分为四个步骤:
1)服务器监听
2)客户端请求服务器
3)服务器确认
4)客户端确认

示例代码:

Server.java

public class Server {

    final static int PROT = 8765;
    
    public static void main(String[] args) {
        
        ServerSocket server = null;
        try {
            server = new ServerSocket(PROT);
            System.out.println(" server start .. ");
            //进行阻塞
            Socket socket = server.accept();
            //新建一个线程执行客户端的任务
            new Thread(new ServerHandler(socket)).start();
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(server != null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
    }

Client.java

public class Client {

    final static String ADDRESS = "127.0.0.1";
    final static int PORT = 8765;
    
    public static void main(String[] args) {
        
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        
        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            
            //向服务器端发送数据
            out.println("接收到客户端的请求数据...");
            out.println("接收到客户端的请求数据1111...");
            String response = in.readLine();
            System.out.println("Client: " + response);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

ServerHandler.java

public class ServerHandler implements Runnable{

    private Socket socket ;
    
    public ServerHandler(Socket socket){
        this.socket = socket;
    }
    
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while(true){
                body = in.readLine();
                if(body == null) break;
                System.out.println("Server :" + body);
                out.println("服务器端回送响的应数据.");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

运行结果:

服务端

Server :接收到客户端的请求数据...
Server :接收到客户端的请求数据1111...

客户端

Client: 服务器端回送响的应数据.

采用线程池和任务队列实现伪异步 IO,将客户端的 socket 封装成 task 任务(实现 Runnable 接口的类),然后投递到线程池中去。

示例代码:

Server.java

public class Server {

    final static int PORT = 8765;

    public static void main(String[] args) {
        ServerSocket server = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            server = new ServerSocket(PORT);
            System.out.println("Server start");
            Socket socket = null;
            HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
            while(true){
                socket = server.accept();
                executorPool.execute(new ServerHandler(socket));
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(server != null){
                try {
                    server.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            server = null;              
        }   
    }   
}

HandlerExecutorPool.java

public class HandlerExecutorPool {

    private ExecutorService executor;
    public HandlerExecutorPool(int maxPoolSize, int queueSize){
        this.executor = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(),
                maxPoolSize, 
                120L, 
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize));
    }
    
    public void execute(Runnable task){
        this.executor.execute(task);
    }   
}

ServerHandler.java

public class ServerHandler implements Runnable {

    private Socket socket;
    public ServerHandler (Socket socket){
        this.socket = socket;
    }
    
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while(true){
                body = in.readLine();
                if(body == null) break;
                System.out.println("Server:" + body);
                out.println("Server response");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;          
        }       
    }
}

Client.java

public class Client {
    
    final static String ADDRESS = "127.0.0.1";
    final static int PORT =8765;
    
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            
            out.println("Client request");
            
            String response = in.readLine();
            System.out.println("Client:" + response);
            
            
        }  catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;              
        }       
    }
}

运行结果:

服务端

Server start
Server:Client request

客户端

Client:Server response

IO (BIO) 和 NIO 的区别:其本质就是阻塞和非阻塞的区别。

阻塞:应用程序在获取网络数据时,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。

非阻塞:应用程序直接可以获取已经准备好的数据,无需等待。

IO 为同步阻塞,NIO 为同步非阻塞。NIO 并没有实现异步,在 JDK1.7之后,升级了 NIO 库包,支持异步非阻塞通信模型,即 NIO2.0(AIO)。

同步和异步:同步和异步一般是面向操作系统与应用程序对 IO 操作的层面上来区别的。

同步时,应用程序会直接参与 IO 读写操作,并且应用程序会直接阻塞到某一个方法上,直到数据准备就绪,或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。

异步时,所有的 IO 读写操作交给操作系统处理,与应用程序没有直接关系,当操作系统完成 IO 读写操作时,会给应用程序发送通知,应用程序直接拿走数据即可。

NIO 编程介绍

NIO 中三个重要概念:Buffer,Channel,Selector。

Buffer:Buffer 是一个对象,它包含一些要写入或者要读取的数据。在面向流的 IO 中,可以将数据直接写入或读取到 Stream 对象中。在 NIO 库中,所有数据都是用缓冲区处理的。缓冲区实际上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他类型的数组。每一种 Java 基本数据类型都对应了一种缓冲区(除了 boolean 类型)。

Channel:通道与流的不同之处在于,通道是双向的,而流是单向的(一个流必须是 InputStream 或 OutputStream 的子类)。通道可以用于读、写或者二者同时进行,最重要的是可以和多路复用器结合起来,有多种的状态位,方便多路复用器去识别。

Selector:Selector 会不断地轮询注册在其上的通道,如果某个通道发生了读写操作,这个通道就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以取得就绪的 Channel 集合,从而进行后续的 IO 操作。一个 Selector 可以负责成千上万个 Channel 通道,没有上限,这也就意味着,只要一个线程负责 Selector 的轮询,就可以接入成千上万个客户端。

Selector 线程就类似一个管理者,管理了成千上万个管道,然后轮询哪个管道的数据已经准备好,通知 CPU 执行 IO 的读取或写入操作。

Selector 模式:当 IO 事件(管道)注册到 Selector 以后,Selector 会分配给每个管道一个 key 值。Selector 以轮询的方式进行查找注册的所有 IO 事件(管道),当 IO 事件(管道)准备就绪后,Selector 就会识别,通过 key 值找到相应的管道,进行相关的数据处理操作。

示例代码:

Server.java

public class Server implements Runnable{
    //1 多路复用器(管理所有的通道)
    private Selector seletor;
    //2 建立缓冲区
    private ByteBuffer readBuf = ByteBuffer.allocate(1024);
    //3 
    private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
    public Server(int port){
        try {
            //1 打开路复用器
            this.seletor = Selector.open();
            //2 打开服务器通道
            ServerSocketChannel ssc = ServerSocketChannel.open();
            //3 设置服务器通道为非阻塞模式
            ssc.configureBlocking(false);
            //4 绑定地址
            ssc.bind(new InetSocketAddress(port));
            //5 把服务器通道注册到多路复用器上,并且监听阻塞事件
            ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
            
            System.out.println("Server start, port :" + port);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while(true){
            try {
                //1 必须要让多路复用器开始监听
                this.seletor.select();
                //2 返回多路复用器已经选择的结果集
                Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator();
                //3 进行遍历
                while(keys.hasNext()){
                    //4 获取一个选择的元素
                    SelectionKey key = keys.next();
                    //5 直接从容器中移除就可以了
                    keys.remove();
                    //6 如果是有效的
                    if(key.isValid()){
                        //7 如果为阻塞状态
                        if(key.isAcceptable()){
                            this.accept(key);
                        }
                        //8 如果为可读状态
                        if(key.isReadable()){
                            this.read(key);
                        }
                        //9 写数据
                        if(key.isWritable()){
                            //this.write(key); //ssc
                        }
                    }
                    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void write(SelectionKey key){
        //ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
        //ssc.register(this.seletor, SelectionKey.OP_WRITE);
    }

    private void read(SelectionKey key) {
        try {
            //1 清空缓冲区旧的数据
            this.readBuf.clear();
            //2 获取之前注册的socket通道对象
            SocketChannel sc = (SocketChannel) key.channel();
            //3 读取数据
            int count = sc.read(this.readBuf);
            //4 如果没有数据
            if(count == -1){
                key.channel().close();
                key.cancel();
                return;
            }
            //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
            this.readBuf.flip();
            //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
            byte[] bytes = new byte[this.readBuf.remaining()];
            //7 接收缓冲区数据
            this.readBuf.get(bytes);
            //8 打印结果
            String body = new String(bytes).trim();
            System.out.println("Server : " + body);
            
            // 9..可以写回给客户端数据 
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

    private void accept(SelectionKey key) {
        try {
            //1 获取服务通道
            ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
            //2 执行阻塞方法
            SocketChannel sc = ssc.accept();
            //3 设置阻塞模式
            sc.configureBlocking(false);
            //4 注册到多路复用器上,并设置读取标识
            sc.register(this.seletor, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        
        new Thread(new Server(8765)).start();;
    }
}

Client.java

public class Client {

    //需要一个Selector 
    public static void main(String[] args) {
        
        //创建连接的地址
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
        
        //声明连接通道
        SocketChannel sc = null;
        
        //建立缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        try {
            //打开通道
            sc = SocketChannel.open();
            //进行连接
            sc.connect(address);
            
            while(true){
                //定义一个字节数组,然后使用系统录入功能:
                byte[] bytes = new byte[1024];
                System.in.read(bytes);
                
                //把数据放到缓冲区中
                buf.put(bytes);
                //对缓冲区进行复位
                buf.flip();
                //写出数据
                sc.write(buf);
                //清空缓冲区数据
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(sc != null){
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
}

运行结果:

客户端(控制台输入)

hello nio

服务端

Server start, port :8765
Server : hello nio
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,555评论 1 143
  • 原文地址http://www.importnew.com/19816.html 概述 NIO主要有三个核心部分:C...
    期待现在阅读 864评论 0 4
  • 继上一篇文章《网络编程之IO与NIO阻塞分析》的讲解,已经知道了网络编程的基本方式,今天将继续进行网络编程相关概念...
    landy8530阅读 1,116评论 0 3
  • 上一张自己写的字,不喜勿喷~用滤镜自己做的~ 昨天晚上昊昊突然对我说感觉和舍友走不到一起,三观不一样,好像无论宿...
    一米六姑娘阅读 175评论 3 2
  • 人生难得好知己,己人己己不己心。苦短情短意更短,是是非非何处断。子欲亲为力不足,上有妻老下有少。苦逼生活何是头,为...
    小世界的大热巴之静初张阅读 141评论 0 0