BIO TCP/IP协议 网络通信

解释:BIO → Block input output(阻塞式的输入和输出)
使用场景:一般应用于传统的网络通信;请求 (客户端)、响应 (服务端)两方,约定好使用TCP/IP通信;双方约定好报文格式 (报文头+报文体)及编码格式 (这里用UTF-8),报文头内容 (约定好长度比如8位,不够前面补零)里面内容为报文体长度,再根据报文头内容,获取后面的报问体的内容。
例如:报文示例: 00000007aaaaaaa;报文体内容为7个a,所以报文头长度为7不够八位前面补零。

一、 作为服务端

SocketServerThread: 用来监听端口的服务类
ThreadOperate:处理每一个请求的新线程

/**
 *  serverSocket服务 
 *  
 *  此线程只负责接收客户端请求,接收到以后再创造新的线程去处理
 * @author zhb
 *
 */
public class SocketServerThread extends Thread{
    // 此服务端线程只会在启动时创建一个
    private ServerSocket serversocket;

    /**
     * 创建服务端线程  
     * @param serversocket 服务端的一个socket
     * @throws IOException
     */
    public SocketServerThread(ServerSocket serversocket) throws IOException{
        if(serversocket==null){
            this.serversocket = new ServerSocket(Constant.serverSocketPort);
        }
    }

    // 线程运行体
    @Override
    public void run(){
        // 如果此服务线程没有被中断
        while(!this.isInterrupted()){           
            try {   
                System.out.println("服务线程开启成功,正在等待客户端的请求");
                // 服务线程一直处于阻塞状态,直到有客户端请求
                Socket socket = serversocket.accept();
                System.err.println("请求来了");
                // 设置接收超时的时间 单位毫秒
                socket.setSoTimeout(Constant.reqTimeOut);   
                if(socket != null && !socket.isClosed()){
                    // 创建一个新线程去处理请求信息
                    new Thread(new ThreadOperate(socket)).start();
                }
            } catch (IOException e) {
                System.err.println("服务线程出现异常:" + e.getMessage());
            } 
        }   
    }

    // 开启线程主函数
    public static void main(String[] args) throws IOException {
        new SocketServerThread(null).start();
    }

}
/**
 * 处理每一个请求开的新的线程
 * @author zhb
 */
public class ThreadOperate implements Runnable {

    // 处理某个客户端请求的socket
    private Socket socket;
    public ThreadOperate(Socket socket){
        this.socket = socket;
    }
        
    public void run() {
        InputStream in = null;
        OutputStream out = null;    
        System.err.println("处理客户请求的新线程开启成功");
        try {
            in = socket.getInputStream();
            out = socket.getOutputStream();
            // 存放报文头的字节数组
            byte[] reqHeadByte = new byte[Constant.reqHeadLength];
            //读取报文头内容
            in.read(reqHeadByte);
            String reqHeadStr = new String(reqHeadByte, Constant.charset);

            //报文头内容是报问体的长度
            int reqBodylength = Integer.valueOf(reqHeadStr);
            
            // 创建容纳报文体的字节数组
            byte[] bodyByte = new byte[reqBodylength];
            in.read(bodyByte);
            
            // 将报问体数组转成报文字符串
            String reqBodyStr = new String(bodyByte, Constant.charset);
            System.err.print("接受报文内容为:" + reqBodyStr);
            
            // 这里要处理接收到的请求报文内容reqBodyStr,完后返回处理结果
            
            //返回响应内容体
            String resBodyStr = "这里是响应内容→ "+ reqBodyStr;
            //返回响应内容的字节数组
            byte[] respBodyByte = resBodyStr.getBytes(Constant.charset);
            
            //转成约定好的报文头长度,不足的前面补零
            StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
            byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
            
            // 输出报文头信息
            out.write(respHeadByte);
            // 输出报文体内容
            out.write(respBodyByte);
            // 发送响应信息
            out.flush();
            
        } catch (IOException e) {
            System.err.println("读取报文内容报异常::" + e.getMessage());
        }finally{   
            //线程结束后的处理
            endHandler(in, out);
        }
    }
    
    /**
     * 线程结束后的处理
     * @param in
     * @param out
     */
    private void endHandler(InputStream in, OutputStream out) { 
        if(in != null){
            try {
                in.close();
            } catch (IOException e) {
                System.err.println("关闭输入流出现异常:" + e.getMessage());
            }
        }   
        if(out != null){
            try {
                out.close();
            } catch (IOException e) {
                System.err.println("关闭输出流出现异常:" + e.getMessage());
            }
        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                System.err.println("关闭线程出现异常:" + e.getMessage());
            }
        }
    }


}

一、 作为客户端

ClientTest : 模拟客户端请求开启线程
ClientThread:具体的某个客户端线程的请求和响应
Constant : 服务端和客户端都用到的常量参数

/**
 *  测试客户端发送请求
 * @author zhb
 */
public class ClientTest {

    // 用于测试并发用
    private static CountDownLatch cdl = new CountDownLatch(1);
    
    public static void main(String[] args) throws IOException, InterruptedException {
        
        for(int i=0; i < 10 ; i++){
            Socket socket = new Socket("localhost", Constant.serverSocketPort);
            new Thread(new ClientThread(socket, i, cdl)).start();
        }
        Thread.sleep(3000);
        cdl.countDown();
    }
    
}


/**
 * 客户端发送请求线程,并接受服务器响应信息 
 * @author zhb
 *
 */
public class ClientThread implements Runnable {
    
    // 客户端的socket
    private Socket socket;
    // 用于标记第几个请求
    private int i;
    // 用于模拟 客户端的并发
    private CountDownLatch cdl;

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

    public ClientThread(Socket socket, int i, CountDownLatch cdl) {
        this.socket = socket;
        this.i =  i;
        this.cdl = cdl;
    }

    public void run() {
    
        try {
            //并发测试信息号
            cdl.await();
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }       
        
        OutputStream out = null;
        InputStream in = null;
        try {   
            // 客户端请求信息
            clientReqSend(out);
            // 接收服务端返回的信息
            clientRespReceive(in);  
    
        } catch (IOException e) {
            e.printStackTrace();
        }finally{   
            //线程结束后的处理
            endHandler(in, out);
        }
    }

    /**
     * 客户端发送请求信息
     * 
     * @param out
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    private void clientReqSend(OutputStream out) throws IOException, UnsupportedEncodingException {
        out = socket.getOutputStream();
        String reqBodyStr = "测试请求内容"+String.valueOf(i);
        System.err.println(reqBodyStr);
        // 请求报文体 字节数组
        byte[] reqBodyByte = reqBodyStr.getBytes(Constant.charset);
        // 请求报文头字节数组,不够位数前面补零
        StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
        byte[] reqHeadByte = String.format(format.toString(), reqBodyByte.length).getBytes(Constant.charset);
        // 输出报文头内容
        out.write(reqHeadByte);
        // 输出报文体内容
        out.write(reqBodyByte);
        // 发送
        out.flush();
    }   

    /**
     * 客户端接收响应信息
     * 
     * @param in
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    private void clientRespReceive(InputStream in) throws IOException, UnsupportedEncodingException {
        
        in = socket.getInputStream();       
        // 存放报文头的字节数组
        byte[] reqHeadByte = new byte[Constant.reqHeadLength];
        //读取报文头内容
        in.read(reqHeadByte);
        String reqHeadStr = new String(reqHeadByte, Constant.charset);
        
        //报文头内容是报问体的长度
        int reqBodylength = Integer.valueOf(reqHeadStr);
        
        // 创建容纳报文体的字节数组
        byte[] bodyByte = new byte[reqBodylength];
        
        int off = 0;
        int nReadLength = 0;
        //循环读取直到读取到报文体长度
        while(off < reqBodylength){
            nReadLength = in.read(bodyByte, off, reqBodylength - off);
            if(nReadLength > 0){
                off = off + nReadLength;
            }else{
                break;
            }
        }
        
        // 将报问体数组转成报文字符串
        String reqBodyStr = new String(bodyByte, Constant.charset);
        System.err.println("接受服务端的请求信息内容为:" +i+ reqBodyStr);
    }
    
    /**
     * 线程结束后的处理
     * @param in
     * @param out
     */
    private void endHandler(InputStream in, OutputStream out) { 
        if(in != null){
            try {
                in.close();
            } catch (IOException e) {
                System.err.println("关闭输入流出现异常:" + e.getMessage());
            }
        }
        if(out != null){
            try {
                out.close();
            } catch (IOException e) {
                System.err.println("关闭输出流出现异常:" + e.getMessage());
            }
        }   
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                System.err.println("关闭线程出现异常:" + e.getMessage());
            }
        }
    }


}
/**
 * 系统常量
 * 
 * @author zhb
 *
 */
public class Constant {
    
    // 请求报文头的位数
    public static final int reqHeadLength = 8;
    // 返回响应报文头的长度
    public static final int resHeadLength = 8;  
    // 字节数组和字符创之间的转换时的编码 
    public static final String charset = "UTF-8";
    //接收请求的最长时间 单位毫秒
    public static final int reqTimeOut = 5000;
    //接收请求的服务端口
    public static final int serverSocketPort = 8080;    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容