Socket(七)

文章目录

    • 1. 单文件服务器
    • 2. 重定向器Redirector
    • 3. 功能完备的HTTP服务器

1. 单文件服务器

要研究HTTP服务器,先从一个简单的服务器开始,无论接受什么请求,这个服务器都始终发送同一个文件。这个单文件服务器名为SingleFileHTTPServer,如下:

public class QuizCardBuilder {
    //获取Logger对象,用于记录日志
    private static final Logger logger=Logger.getLogger("SingelFileHTTPServer");
    //服务端发送给客户端的数据
    private final  byte[] content;
    //请求头
    private final  byte[] header;
    //端口
    private final int port;
    //编码方式
    private final  String encoding;
    //构造函数
    public QuizCardBuilder(String data, String encoding, String mimeType, int port) throws UnsupportedEncodingException {
    //调用另一个构造函数
        this(data.getBytes(encoding),encoding,mimeType,port);
    }
    public QuizCardBuilder(byte[] data, String encoding,String mimeType ,int port){
        this.content=data;
        this.port=port;
        this.encoding=encoding;
        String header="HTTP/1.0 200 Ok\r\n"
                +"Server:OneFile 2.0\r\n"
                +"Content-length:"+ this.content.length+ "\r\n"
                +"Content-type:"+mimeType+"; charset="+encoding+"\r\n\r\n";
        this.header=header.getBytes(Charset.forName("US-ASCII")) ;
    }
    public void start()  {
        ExecutorService pool=Executors.newFixedThreadPool(100);
        try(ServerSocket serverSocket=new ServerSocket(this.port)){
            logger.info("Accepting connections on port "+ serverSocket.getLocalPort());
            logger.info("Data to be sent:");
            logger.info(new String(this.content,encoding));
            while (true){
                try{
                    Socket connection=serverSocket.accept();
                    pool.submit(new HTTPHandler(connection));
                }catch (IOException e){
                    logger.log(Level.WARNING,"Exception accepting connection",e);
                }catch (RuntimeException e){
                    logger.log(Level.SEVERE,"Unexpected errors",e);
                }
            }
        }catch (IOException ex){
            logger.log(Level.SEVERE,"Could not start server"+ex);
        }
    }

    private  class  HTTPHandler implements Callable<Void>{
        private final Socket connection;
        HTTPHandler(Socket connection){
            this.connection=connection;
        }

        @Override
        public Void call() throws Exception {
            try{
            //获得输出流
                OutputStream out=new BufferedOutputStream(connection.getOutputStream());
                //获得输入流
                InputStream in=new BufferedInputStream(connection.getInputStream());
                StringBuilder request=new StringBuilder(80);
                while(true){
                    int c=in.read();
                    if(c=='\r' || c=='\n' || c==-1)break;
                    request.append((char) c);
                }
                //如果是HTTP/1.0以后的版本则发送一个MIME首部
                if(request.toString().indexOf("HTTP/")!=-1){
                     out.write(header);
                }
                out.write(content);
                out.flush();
            }catch (IOException e){
                logger.log(Level.WARNING,"Error writing to client ",e);
            }finally {
                connection.close();
            }
            return null;
        }
    }

    public static void main(String[] args) {
           String encoding="UTF-8";
           try{
               //以绝对路径获取文件
               Path path= Paths.get("/Users/jackchai/Desktop/自学笔记/java项目/leetcode/leetcodetest/MyText.txt");
               //将文件内容读成字节流
               byte[] data= Files.readAllBytes(path);
               String contentType= URLConnection.getFileNameMap().getContentTypeFor("/Users/jackchai/Desktop/自学笔记/java项目/leetcode/leetcodetest/MyText.txt");
               QuizCardBuilder server=new QuizCardBuilder(data,encoding,contentType,8080);
               server.start();
               } catch (ArrayIndexOutOfBoundsException ex) {
                   System.out.println("Usage:java QuizCardBuilder filename port encoding");
               }catch (IOException e){
               logger.severe(e.getMessage());
           }
    }

}

下面是服务端日志

在这里插入图片描述

下面使用Telnet测试结果

在这里插入图片描述

2. 重定向器Redirector

重定向是特殊用途的HTTP服务器的另一个简单而有用的应用程序。他能从一个Web网站重定向到另一个网站。下面代码从指定的URL和端口,在这个端口打开一个服务器Socket,使用302 FOUND编码将接收的所有请求重定向到新URL表示的网站。

public class QuizCardBuilder {
   //获取Logger对象,用于日志
   private static  final Logger logger=Logger.getLogger("Redirecotr");
   //端口
   private final int port;
   //重定向的地址
   private final String newSite;
   //构造函数
   public QuizCardBuilder(String newSite, int port){
       this.port=port;
       this.newSite=newSite;
   }
   public void start(){
       try(ServerSocket serverSocket=new ServerSocket(port)){
           logger.info("Redirecting connections on port "+ serverSocket.getLocalPort()+" to "+newSite);
           while(true){
               try {
                   //打开Socket开始等待
                   Socket s=serverSocket.accept();
                   //当接受到一个请求时,开启一个新的线程
                   Thread t=new RedirectThread(s);
                   //开启新线程
                   t.start();
               }catch (IOException e){
                   logger.warning("Exception accepting connection");
               }catch (RuntimeException ex){
                   logger.log(Level.SEVERE,"Unexpected error",ex);
               }
             
           }
       }catch (BindException ex){
           logger.log(Level.SEVERE,"Could not start server." +ex);
       }catch (IOException ex){
           logger.log(Level.SEVERE,"Error opening server socket ",ex);
       }
   }
   private class RedirectThread extends Thread{
       private final Socket connection;
       RedirectThread(Socket s){
           this.connection=s;
       } 
       public void run(){
           try{
               //打开输出流
               Writer out=new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(),"US-ASCII"));
               //打开输入流
               Reader in=new InputStreamReader(new BufferedInputStream(connection.getInputStream()));
               StringBuilder request=new StringBuilder(90);
               //读取用户请求的第一行
               while(true){
                   int c=in.read();
                   if(c=='\r' || c=='\n' || c==-1){
                       break;
                   }
                   request.append((char)c);
               }
               
               String get=request.toString();
               String[] pieces =get.split("\\w*");
               String theFile=pieces[1];
               
               if(get.indexOf("HTTP")!=-1){
                   out.write("HTTP/1.0 302 FOUND\r\n");
                   Date now=new Date();
                   out.write("Date:"+now+"\r\n");
                   out.write("Server:Redirector  1.1\r\n");
                   out.write("Location:"+newSite+theFile+"\r\n");
                   out.write("Content-type: text/html\r\n\r\n");
                   out.flush();
               }
               //并不是所有的浏览器都支持重定向,所以我们需要生成HTML指出文档转移到哪里
               out.write("<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>\r\n");
               out.write("<BODY><H1>Document moved</H1>\r\n");
               out.write("The document"+theFile
               +" has moved to \r\n  <A HREF\""+newSite+theFile+"\">"
               +newSite+theFile
               +"</A>,\r\n Please update your bookmarks<p>");
               out.write("</BODY><HTML>\r\n");
               logger.log(Level.INFO,"Redirected "+connection.getRemoteSocketAddress());
           }catch (IOException e){
               logger.log(Level.WARNING,"Error talking to "+connection.getRemoteSocketAddress(),e);
           }finally {
               try{
                   connection.close();
               } catch (IOException e) {
               
               }
           }
       }
   }

    public static void main(String[] args) {
       String  theSite;
        try{
            theSite="www.baidu.com";
            //删除末尾斜线
            if(theSite.endsWith("/")){
                theSite=theSite.substring(0,theSite.length()-1);
            }
        }catch (RuntimeException e){
            System.out.println("Usage: java Redirector http://www.baidu.com/port");
            return; 
        }
        QuizCardBuilder redirector=new QuizCardBuilder(theSite,8080);
        redirector.start();
    }
}

允许代码,在浏览器中访问http://localhost:8080,就会发生重定向

在这里插入图片描述

3. 功能完备的HTTP服务器

上面介绍了单文件的HTTP服务器,它会接受用户请求,将本地的一个文件内容以流的形式返回给客户端。这里将开发一个功能完备的HTTP服务器,名为JHTTP,它可以提供一个完整的文档树,包括图像、applet、HTML文件、文本文件等。它与前面的单文件服务器非常相似,只不过它所关注的是GET请求。

下面是JHTTP web服务器

public class QuizCardBuilder {
    //返回由 Java 语言规范定义的基础类的规范名称。如果基础类没有规范名称(即,如果它是本地或匿名类或其组件类型没有规范名称的数组),则返回 null。
  private static  final Logger logger=Logger.getLogger(QuizCardBuilder.class.getCanonicalName());
  //线程数量
  private  static final int NUM_THREADS=50;
  private static final String INDEX_FILE="index.html";
  private final File rootDeirectory;
  private final int port;
  public QuizCardBuilder(File rootDeirectory, int port)throws IOException{
      //如果rootDeirectory不是个文件目录则抛出异常
      if(!rootDeirectory.isDirectory()){
          throw new IOException(rootDeirectory+" does not exist as a directory");
      }
      this.rootDeirectory=rootDeirectory;
      this.port=port;
  }
  public void start()throws IOException{
      //定义线程池
      ExecutorService pool= Executors.newFixedThreadPool(NUM_THREADS);
      try(ServerSocket serverSocket=new ServerSocket(port)){
          logger.info("Accepting connections on port "+serverSocket.getLocalPort());
          logger.info("Document Root "+rootDeirectory);
          while (true){
              try{
                  //等待客户端请求
                  Socket socket=serverSocket.accept();
                  //创建一个线程,参数为文件的服务端文件系统的根目录,文件索引和服务器Socket
                  Runnable r=new RequestProcessors(rootDeirectory,INDEX_FILE,socket);
                  //提交线程任务
                  pool.submit(r);
              }catch (IOException ex){
                  logger.log(Level.WARNING,"Error accepting connection",ex);
              }
          }
      }
  }

    public static void main(String[] args) {
        File docroot;
        try{
            docroot=new File("/Users/jackchai/Desktop/自学笔记/java项目/leetcode/leetcodetest");
        }catch (ArrayIndexOutOfBoundsException ex){
            System.out.println("Usage: java JHTTP docroot port");
            return;
        }
        //设置要监听的端口
        int port=8080;
        try {
            QuizCardBuilder Jhttp=new QuizCardBuilder(docroot,port);
            Jhttp.start();
        }catch (IOException e){
            logger.log(Level.SEVERE,"Server could not start ",e );
        }
    }
}
class RequestProcessors implements Runnable{
    //获取Logger对象,用于日志管理
    private final static Logger logger=Logger.getLogger(RequestProcessor.class.getCanonicalName());
    //文件系统的根目录
    private File rootDirectory;
    //文件索引
    private String indexFilename="index.html";
    //服务端Socket
    private Socket connection;

    public RequestProcessors(File rootDirectory,String indexFilename,Socket connection){       //如果文件系统的根目录是一个文件
        if(rootDirectory.isFile()){
            throw new IllegalArgumentException("rootDirectory must be a directory, not a file");
        }
        try {
        //返回此抽象路径名的规范形式。等效于 new File(this.getCanonicalPath)。
            rootDirectory=rootDirectory.getCanonicalFile();
        }catch (IOException e){

        }
        this.rootDirectory=rootDirectory;
        if(indexFilename!=null) this.indexFilename=indexFilename;
        this.connection=connection;
    }
    @Override
    public void run() {
        //安全检查
        String root=rootDirectory.getPath();
        try {//获得输出流
            OutputStream raw=new BufferedOutputStream(connection.getOutputStream());
            Writer out=new OutputStreamWriter(raw);
            //获得输入流
            Reader in =new InputStreamReader(new BufferedInputStream(connection.getInputStream()),"US-ASCII");
            //用于存储用户都请求头
            StringBuilder requestLine=new StringBuilder();
            while(true){
                int c=in.read();
                if(c=='\r'||c=='\n'||c==-1)break;
                requestLine.append((char) c);
            }
            String get=requestLine.toString();
            //获得客户端地址
            logger.info(connection.getRemoteSocketAddress()+" "+get); 
            String[] tokens=get.split("\\s+");
            //请求方法
            String method=tokens[0];
            //获得HTTP版本
            String version="";
            if(method.equals("GET")){
                //用户请求的文件名
                String filename=tokens[1];
                if(filename.endsWith("/"))filename+=indexFilename;
                //获得文件类型(这里肯定是text/html
                String contentType= URLConnection.getFileNameMap().getContentTypeFor(filename);
                if(tokens.length>2){
                    version=tokens[2];
                }
                File thefile=new File(rootDirectory,filename.substring(1,filename.length()));
                if(thefile.canRead()
                    //不要让客户端超出文档根之外
                        && thefile.getCanonicalPath().startsWith(root)){
                    byte[] thedata= Files.readAllBytes(thefile.toPath());
                    if(version.startsWith("HTTP/")){
                        sendHeader(out,"HTTP/1.0 200 OK",contentType,thedata.length);
                    }
                    //发送文件,这可能是一个图像或其他二进制数据
                    //所以要用底层流输出
                    //而不是writer
                    raw.write(thedata);
                    raw.flush();
                }else {
                    String body=new StringBuilder("<HTML>\r\n")
                            .append("<HEAD><TITLE>FIle Not Found</TITLe>\r\n")
                            .append("</HEAD>\r\n")
                            .append("<BODY>\r\n")
                            .append("<H1>HTTP Error 404: File Not Found <H1>\r\n")
                            .append("<BODY></HTML>\r\n").toString();
                    if(version.startsWith("HTTP/")){
                        sendHeader(out,"HTTP/1.0 404 File Not Found","text/html; charset=utf-8",body.length());
                    }
                    out.write(body);
                    out.flush();
                }
            }else {//方法不等于GET
                String body = new StringBuilder("<HTML>\r\n")
                        .append("<HEAD><TITLE>Not Implemented</TITLe>\r\n")
                        .append("</HEAD>\r\n")
                        .append("<BODY>\r\n")
                        .append("<H1>HTTP Error 501: Not Implemented <H1>\r\n")
                        .append("<BODY></HTML>\r\n").toString();
                if(version.startsWith("HTTP/")){
                    sendHeader(out,"HTTP/1.0 501 Not Implemented ","text/html; charset=utf-8",body.length());
                }
                out.write(body);
                out.flush();
            }
        }catch (IOException ex){
            logger.log(Level.WARNING,"Error talking to "+connection.getRemoteSocketAddress(),ex);
        }finally {
            try {
                connection.close();
            }catch (IOException e){}
        }

    }
    private void sendHeader(Writer out,String responseCode,String contentType,int length) throws IOException {
        out.write(responseCode+"\r\n");
        Date now=new Date();
        out.write("Date: "+now+"\r\n");
        out.write("Server:"+length+"\r\n");
        out.write("Content-length:"+length+"\r\n");
        out.write("Content-type:"+contentType+"\r\n\r\n");
        out.flush();
    }
}

运行成功获取文件

在这里插入图片描述

也可以获取图片

在这里插入图片描述
上面这个服务器提供了我们获取根目录下的文件,但还是十分简单,如果真的希望使用JHTTP来运行一个高流量网站,还可以做一些事情来加速这个服务器,最主要的是实现智能缓冲,跟踪记录接收到的请求,将最频繁请求的文件中的数据存储在一个Map中,使它们保存在内存中,使用一个低优先级的线程更新这个缓存。还可以使用非阻塞I/O和通道来代替线程和流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/26103.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

车辆CAN信号,依据DBC文件解析流程

CAN信号解析流程 1.车辆CAN对应dbc文件 DBC文件是一种用于描述CAN&#xff08;Controller Area Network&#xff09;数据通信协议的文件格式&#xff0c;DBC文件中包含了CAN数据的信号定义、编码方式、单位、范围等信息&#xff0c;可以用于解析和生成CAN数据帧。 一个DBC文件…

ChatGPT的4个不为人知却非常实用的小功能

重点介绍四个ChatGPT很实用的小功能。 一、停止生成 如果在ChatGPT输出内容的过程中&#xff0c;我们发现结果不是自己想要的&#xff0c;可以直接点击“Stop generating”按钮&#xff0c;这样它就会立即停止输出。 二、复制功能 在ChatGPT返回对话的右侧&#xff0c;有三个图…

MySQL主存复制

介绍 配置-主库master 第一步&#xff1a;修改MySQL数据库的配置文件/etc/my.cnf [mysqld] log-binmysql-bin #[必须]启用二进制日志 server-id100 #[必须]服务器唯一id第二部&#xff1a;重启MySQL服务 systemctl restart mysqld第三步&#xff1a;登录MySQL操作&#x…

Linux:软件管理器yum编辑器vim

软件管理器yum&&编辑器vim &#x1f506;软件管理器yum软件包是什么rzsz网络通畅性验证查看软件包怎么安装软件安装yum扩展源怎么卸载软件 &#x1f506;编辑器vim基本概念基本操作正常模式指令集末行模式指令集简单配置vim配置文件的位置常用配置选项使用插件参考资料…

DVWA——Brute Force

文章目录 Brute Force&#xff08;暴力&#xff08;破解&#xff09;&#xff09;&#xff08;1&#xff09;Low等级&#xff08;2&#xff09;Medium等级&#xff08;3&#xff09;High等级&#xff08;4&#xff09;Impossible Brute Force&#xff08;暴力&#xff08;破解&…

chatgpt赋能python:Python如何快速复制上一行?

Python 如何快速复制上一行&#xff1f; 在编写Python代码时&#xff0c;经常需要快速复制上一行代码进行修改。如果只是简单的手动复制粘贴&#xff0c;会造成不必要的时间浪费并且容易出错。本文将介绍三种快速复制上一行代码的方法。 方法一&#xff1a;使用快捷键 在Pyt…

Apache的配置与应用(构建web、日志分割及AWStats分析系统)

Apache的配置与应用 一、构建虚拟Web主机二、httpd服务支持的三种虚拟机类型1、基于域名的虚拟主机2、基于IP地址的虚拟主机3、基于端口的虚拟主机 三、构建web虚拟目录与用户授权限制1、创建用户认证数据文件2、添加用户授权配置3、验证用户访问权限4、在客户机中浏览器访问 四…

Jenkins+GitLab+Docker搭建前端自动化构建镜像容器部署

前言 &#x1f680; 需提前安装环境及知识点&#xff1a; 1、Docker搭建及基础操作 2、DockerFile文件描述 3、Jenkins搭建及基础点 &#x1f680; 目的&#xff1a; 将我们的前端项目打包成一个镜像容器并自动发布部署&#xff0c;可供随时pull访问 一、手动部署镜像及容器 1…

校园高校共享单车管理系统nodejs+vue+express

设计的管理员的详细功能见下图&#xff0c;管理员登录进入本人后台之后&#xff0c;管理单车和区域&#xff0c;审核租赁订单和还车订单&#xff0c;收取租赁费用&#xff0c;查看单车租赁统计信息。 vue的文件结构其实就是一个index.html 中间的内容&#xff0c;用的是vue&am…

同步阻塞与异步非阻塞

同步阻塞消息处理 假如有这样一个系统功能&#xff0c;客户端提交Event至服务器&#xff0c;服务器接收到客户请求之后开辟线程处理客户请求&#xff0c;经过比较复杂的业务计算后将结果返回给客户端 以上设计存在几个显著的缺陷&#xff0c;具体如下。同步Event提交&#xf…

Python学习36:文本分析与加密

类型&#xff1a;字符串‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬‪…

Vue.js 中的路由是什么?如何使用路由?

Vue.js 中的路由是什么&#xff1f;如何使用路由&#xff1f; 在 Vue.js 中&#xff0c;路由是指为不同的 URL 地址提供不同的页面内容或视图的机制。Vue.js 中的路由可以使用 Vue Router 库来实现&#xff0c;它是 Vue.js 官方提供的路由管理库。 Vue Router 简介 Vue Route…

海绵城市智慧监测系统功能、适用范围有哪些?

一、海绵城市在线监测系统拓扑图 海绵城市在线监测系统是通过RTU使用3G/4G信号进行无线传输等方式&#xff0c;将温度、水位、风速、风向、雨量、色度、浊度等数据传输到在线监测平台。经过软件平台的系统性处理后再通过LED显示屏直观、快捷的展现给用户。用户也可以通过电脑、…

如何使用Node.js REPL

目录 1、Nodejs REPL 2、_特殊变量 3、向上箭头键 4、点命令 5、从JavaScript文件运行REPL 1、Nodejs REPL REPL代表Read-Evaluate-Print-Loop&#xff0c;是交互式解释器。 node命令是我们用来运行Node.js脚本的命令&#xff1a; node script.js 如果我们运行node命令…

Unity WebGL打包配置本地服务器

第一步打包 1&#xff09;、先对Player Setting进行设置 2&#xff09;、设置打包的窗口大小 3&#xff09;、遇到异常以及压缩格式 第一个启用异常&#xff0c;指用户指定在运行时意外的代码行为&#xff08;通常被认为错误&#xff09;如何被处理&#xff0c;有三个选项…

3.java高级之GUI编程

1.gui (graphical user interface) awt sun公司最早的gui,不美观,不兼容各平台swing : awt升级2.顶层容器(只有一个)(其他的只能放这里)Jframe Jdialog对话框 Jwindow(x) //在main方法写fnew Jframe(); f.setSize(300,300); //设置大小f.setTitle("hello"); //设置…

【linux】web基础与HTTP协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 web基础与HTTP协议 一、DNS/HTML1.域名的概述2.域名注册3.网页的概念4.HTML概述5.网页基本标签 二、web基础1.web概述2.静态网页3.静态网页特点4.动态网页5.动态网页的特点 三…

redis数据持久化

Redis是基于内存的&#xff0c;如果不想办法将数据保存在硬盘上&#xff0c;一旦Redis重启(退出/故障)&#xff0c;内存的数据将会全部丢失。(业务库中缓存的数据 , 存储的一些重要的标签, 状态数据) 我们肯定不想Redis里头的数据由于某些故障全部丢失(导致所有请求都走MySQL)&…

python基础----05-----函数的多返回值、函数的多种参数使用形式、函数作为参数传递、lambda匿名函数

一 函数的多返回值 if __name__ __main__:# 演示使用多个变量&#xff0c;接收多个返回值def test_return ():return 1,hello,Truex,y,z test_return()print(x)print(y)print(z)1helloTrue二 函数的多种参数使用形式 分为以下四种。 2.1 位置参数 位置参数调用函数时根据…

大数据分析案例-基于决策树算法构建世界杯比赛预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…