ホームページ ウェブフロントエンド jsチュートリアル Node静的リソースサーバーの操作方法

Node静的リソースサーバーの操作方法

May 09, 2018 am 11:47 AM
node リソース 静的

今回は、Node 静的リソースサーバーの運用方法と、Node 静的リソースサーバーを運用する際の注意事項について説明します。以下は実際のケースです。見てみましょう。

http サーバーは、TCP サーバーから継承されます。http プロトコルは、TCP に基づいています。HTTP の原理は、クライアントが接続すると、最初に接続イベントがトリガーされます。リクエストは複数回送信できます。各リクエストはリクエストイベントをトリガーします

let server = http.createServer();
let url = require('url');
server.on('connection', function (socket) {
  console.log('客户端连接 ');
});
server.on('request', function (req, res) {
  let { pathname, query } = url.parse(req.url, true);
  let result = [];
  req.on('data', function (data) {
    result.push(data);
  });
  req.on('end', function () {
    let r = Buffer.concat(result);
    res.end(r);
  })
});
server.on('close', function (req, res) {
  console.log('服务器关闭 ');
});
server.on('error', function (err) {
  console.log('服务器错误 ');
});
server.listen(8080, function () {
  console.log('server started at http://localhost:8080');
});
ログイン後にコピー

    reqはクライアントの接続を表し、サーバーはクライアントの
  1. リクエスト情報

    を解析し、それをreq

  2. resに置きます。クライアントに送信したい場合は応答 メッセージに応答するには、ソケットからの res
  3. req と res を渡す必要があります。まずソケットのデータ イベントをリッスンし、次にイベントが発生したときを待ちます。リクエストオブジェクトが発生し、それを解析し、リクエストオブジェクトを解析し、リクエストオブジェクトを作成し、リクエストオブジェクトに基づいてレスポンスオブジェクトを作成します
  4. req.url リクエストパスを取得します
  5. req.headers リクエストヘッダオブジェクト
  6. 次に、いくつかのコア機能について説明します

圧縮と解凍を深く理解して実装します

なぜ圧縮するのですか?利点は何ですか?

ファイルを圧縮した後、zlib モジュールを使用して、サイズを削減し、伝送速度を向上させ、帯域幅コードを節約できます

圧縮オブジェクトと解凍オブジェクトはすべて変換されます。変換ストリーム、二重二重ストリームから継承、つまり読み取りおよび書き込み可能なストリーム

    zlib.createGzip: Gzip ストリーム オブジェクトを返し、Gzip アルゴリズムを使用してデータを圧縮します
  1. zlib.createGunzip: Gzip を返しますストリーム オブジェクト、Gzip アルゴリズムを使用して圧縮データを解凍します。 Processing
  2. zlib.createDeflate: Deflate ストリーム オブジェクトを返し、Deflate アルゴリズムを使用してデータを圧縮します。
  3. zlib.createInflate: Deflate ストリーム オブジェクトを返し、データを解凍するための Deflate アルゴリズム
実装 圧縮と解凍

圧縮ファイルは大きくても小さくてもよいため、処理速度を上げるためにストリームを使用します

let fs = require("fs");
let path = require("path");
let zlib = require("zlib");
function gzip(src) {
 fs
  .createReadStream(src)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(src + ".gz"));
}
gzip(path.join(dirname,'msg.txt'));
function gunzip(src) {
 fs
  .createReadStream(src)
  .pipe(zlib.createGunzip())
  .pipe(
   fs.createWriteStream(path.join(dirname, path.basename(src, ".gz")))
  );
}
gunzip(path.join(dirname, "msg.txt.gz"));
ログイン後にコピー

    gzipメソッドは圧縮を実装するために使用されます
  1. gunzip メソッドは解凍を実装するために使用されます
  2. ファイル msg.txt は同じレベルのディレクトリです
  3. なぜこのように記述する必要があるのですか: gzip(path. join(dirname,'msg.txt'));
  4. なぜなら、 console.log(process.cwd ()); の場合、現在の作業ディレクトリがルート ディレクトリであることが出力されます。 gzip('msg.txt'); ファイルが見つからない場合はエラーが報告されます
  5. basename 拡張子を含むファイル名をパスから取得します。拡張子を削除するために拡張子パラメータを渡すことができます。拡張子を取得するには
  6. extnameを使用します
  7. 圧縮形式と解凍された形式は一致する必要があります。一致しない場合はエラーが報告されます
  8. 取得された文字列はストリームではないので、それを解決する方法

を取得する場合があります

let zlib=require('zlib');
let str='hello';
zlib.gzip(str,(err,buffer)=>{
  console.log(buffer.length);
  zlib.unzip(buffer,(err,data)=>{
    console.log(data.toString());
  })
});
ログイン後にコピー
圧縮されたコンテンツが元のコンテンツよりも大きくなる可能性があります。コンテンツが小さすぎると、圧縮の意味がありません

ルールがあるため、テキスト圧縮の効果が高くなります

httpで圧縮と解凍を適用します

図に示すように、以下はそのような関数を実装します:

クライアントがサーバーへのリクエストを開始すると、accept-encoding (例: Accept-Encoding: gzip、デフォルト) を渡します。私がサポートする解凍形式
  1. サーバーは Accept-Encoding で表示される形式に従って圧縮する必要があります。形式がない場合、ブラウザは解凍できないため圧縮できません
  2. クライアントが Accept-Encoding を必要とする場合フォーマットサーバーは圧縮を持たず、実装できません
    let http = require("http");
    let path = require("path");
    let url = require("url");
    let zlib = require("zlib");
    let fs = require("fs");
    let { promisify } = require("util");
    let mime = require("mime");
    //把一个异步方法转成一个返回promise的方法
    let stat = promisify(fs.stat);
    http.createServer(request).listen(8080);
    async function request(req, res) {
     let { pathname } = url.parse(req.url); 
     let filepath = path.join(dirname, pathname); 
     // fs.stat(filepath,(err,stat)=>{});现在不这么写了,异步的处理起来比较麻烦
     try {
      let statObj = await stat(filepath);
      res.setHeader("Content-Type", mime.getType(pathname));
      let acceptEncoding = req.headers["accept-encoding"];
      if (acceptEncoding) {
       if (acceptEncoding.match(/\bgzip\b/)) {
        
        res.setHeader("Content-Encoding", "gzip");
        fs
         .createReadStream(filepath)
         .pipe(zlib.createGzip())
         .pipe(res);
       } else if (acceptEncoding.match(/\bdeflate\b/)) {
        res.setHeader("Content-Encoding", "deflate");
        fs
         .createReadStream(filepath)
         .pipe(zlib.createDeflate())
         .pipe(res);
       } else {
        fs.createReadStream(filepath).pipe(res);
       }
      } else {
       fs.createReadStream(filepath).pipe(res);
      }
     } catch (e) {
      res.statusCode = 404;
      res.end("Not Found");
     }
    }
    ログイン後にコピー
  1. mime: ファイル名とパスを通じてファイルのコンテンツタイプを取得し、異なるファイルコンテンツタイプに応じて異なる Content-Type を返すことができます🎜
  2. acceptEncoding:全部写成小写是为了兼容不同的浏览器,node把所有的请求头全转成了小写

  3. filepath:得到文件的绝对路径

  4. 启动服务后,访问http://localhost:8080/msg.txt 可看到结果

深刻理解并实现缓存

为什么要缓存呢,缓存有什么好处?

  1. 减少了冗余的数据传输,节省了网费。

  2. 减少了服务器的负担, 大大提高了网站的性能

  3. 加快了客户端加载网页的速度

缓存的分类

强制缓存:

强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据
在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中

对比缓存:

浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中

再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据

两类缓存的区别和联系

强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则

实现对比缓存

实现对比缓存一般是按照以下步骤:

第一次访问服务器的时候,服务器返回资源和缓存的标识,客户端则会把此资源缓存在本地的缓存数据库中。

第二次客户端需要此数据的时候,要取得缓存的标识,然后去问一下服务器我的资源是否是最新的。

如果是最新的则直接使用缓存数据,如果不是最新的则服务器返回新的资源和缓存规则,客户端根据缓存规则缓存新的数据

实现对比缓存一般有两种方式

通过最后修改时间来判断缓存是否可用

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  //D:\vipcode\201801\20.cache\index.html
  let filepath = path.join(dirname, pathname);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      let ifModifiedSince = req.headers['if-modified-since'];
      let LastModified = stat.ctime.toGMTString();
      if (ifModifiedSince == LastModified) {
        res.writeHead(304);
        res.end('');
      } else {
        return send(req, res, filepath, stat);
      }
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath, stat) {
  res.setHeader('Content-Type', mime.getType(filepath));
  //发给客户端之后,客户端会把此时间保存起来,下次再获取此资源的时候会把这个时间再发回服务器
  res.setHeader('Last-Modified', stat.ctime.toGMTString());
  fs.createReadStream(filepath).pipe(res);
}
ログイン後にコピー

这种方式有很多缺陷

  1. 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了

  2. 某些文件的修改非常频繁,在秒以下的时间内进行修改.Last-Modified只能精确到秒。

  3. 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了

  4. 如果同样的一个文件位于多个CDN服务器上的时候内容虽然一样,修改时间不一样

ETag

ETag是根据实体内容生成的一段hash字符串,可以标识资源的状态
资源发生改变时,ETag也随之发生变化。 ETag是Web服务端产生的,然后发给浏览器客户端

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  
  let filepath = path.join(dirname, pathname);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      let ifNoneMatch = req.headers['if-none-match'];
      let out = fs.createReadStream(filepath);
      let md5 = crypto.createHash('md5');
      out.on('data', function (data) {
        md5.update(data);
      });
      out.on('end', function () {
      
        let etag = md5.digest('hex');
        let etag = `${stat.size}`;
        if (ifNoneMatch == etag) {
          res.writeHead(304);
          res.end('');
        } else {
          return send(req, res, filepath, etag);
        }
      });
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath, etag) {
  res.setHeader('Content-Type', mime.getType(filepath));
  
  res.setHeader('ETag', etag);
  fs.createReadStream(filepath).pipe(res);
}
ログイン後にコピー

客户端想判断缓存是否可用可以先获取缓存中文档的ETag,然后通过If-None-Match发送请求给Web服务器询问此缓存是否可用。

服务器收到请求,将服务器的中此文件的ETag,跟请求头中的If-None-Match相比较,如果值是一样的,说明缓存还是最新的,Web服务器将发送304 Not Modified响应码给客户端表示缓存未修改过,可以使用。

如果不一样则Web服务器将发送该文档的最新版本给浏览器客户端

实现强制缓存

把资源缓存在客户端,如果客户端再次需要此资源的时候,先获取到缓存中的数据,看是否过期,如果过期了。再请求服务器

如果没过期,则根本不需要向服务器确认,直接使用本地缓存即可

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  let filepath = path.join(dirname, pathname);
  console.log(filepath);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      send(req, res, filepath);
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath) {
  res.setHeader('Content-Type', mime.getType(filepath));
  res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString());
  res.setHeader('Cache-Control', 'max-age=30');
  fs.createReadStream(filepath).pipe(res);
}
ログイン後にコピー

浏览器会将文件缓存到Cache目录,第二次请求时浏览器会先检查Cache目录下是否含有该文件,如果有,并且还没到Expires设置的时间,即文件还没有过期,那么此时浏览器将直接从Cache目录中读取文件,而不再发送请求

Expires是服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据

Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据,如果同时设置的话,其优先级高于Expires

下面开始写静态服务器

首先创建一个http服务,配置监听端口

 let http = require('http');
 let server = http.createServer();
    server.on('request', this.request.bind(this));
    server.listen(this.config.port, () => {
      let url = `http://${this.config.host}:${this.config.port}`;
      debug(`server started at ${chalk.green(url)}`);
    });
ログイン後にコピー

下面写个静态文件服务器

先取到客户端想说的文件或文件夹路径,如果是目录的话,应该显示目录下面的文件列表

 async request(req, res) {
    let { pathname } = url.parse(req.url);
    if (pathname == '/favicon.ico') {
      return this.sendError('not found', req, res);
    }
    let filepath = path.join(this.config.root, pathname);
    try {
      let statObj = await stat(filepath);
      if (statObj.isDirectory()) {
        let files = await readdir(filepath);
        files = files.map(file => ({
          name: file,
          url: path.join(pathname, file)
        }));
        let html = this.list({
          title: pathname,
          files
        });
        res.setHeader('Content-Type', 'text/html');
        res.end(html);
      } else {
        this.sendFile(req, res, filepath, statObj);
      }
    } catch (e) {
      debug(inspect(e));
      this.sendError(e, req, res);
    }
  }
  
  sendFile(req, res, filepath, statObj) {
    if (this.handleCache(req, res, filepath, statObj)) return;
    res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
    let encoding = this.getEncoding(req, res);
    let rs = this.getStream(req, res, filepath, statObj);
    if (encoding) {
      rs.pipe(encoding).pipe(res);
    } else {
      rs.pipe(res);
    }
  }
ログイン後にコピー

支持断点续传

 getStream(req, res, filepath, statObj) {
    let start = 0;
    let end = statObj.size - 1;
    let range = req.headers['range'];
    if (range) {
      res.setHeader('Accept-Range', 'bytes');
      res.statusCode = 206;
      let result = range.match(/bytes=(\d*)-(\d*)/);
      if (result) {
        start = isNaN(result[1]) ? start : parseInt(result[1]);
        end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
      }
    }
    return fs.createReadStream(filepath, {
      start, end
    });
  }
ログイン後にコピー

支持对比缓存,通过etag的方式

handleCache(req, res, filepath, statObj) {
    let ifModifiedSince = req.headers['if-modified-since'];
    let isNoneMatch = req.headers['is-none-match'];
    res.setHeader('Cache-Control', 'private,max-age=30');
    res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString());
    let etag = statObj.size;
    let lastModified = statObj.ctime.toGMTString();
    res.setHeader('ETag', etag);
    res.setHeader('Last-Modified', lastModified);
    if (isNoneMatch && isNoneMatch != etag) {
      return fasle;
    }
    if (ifModifiedSince && ifModifiedSince != lastModified) {
      return fasle;
    }
    if (isNoneMatch || ifModifiedSince) {
      res.writeHead(304);
      res.end();
      return true;
    } else {
      return false;
    }
  }
ログイン後にコピー

支持文件压缩

  getEncoding(req, res) {
    let acceptEncoding = req.headers['accept-encoding'];
    if (/\bgzip\b/.test(acceptEncoding)) {
      res.setHeader('Content-Encoding', 'gzip');
      return zlib.createGzip();
    } else if (/\bdeflate\b/.test(acceptEncoding)) {
      res.setHeader('Content-Encoding', 'deflate');
      return zlib.createDeflate();
    } else {
      return null;
    }
  }
ログイン後にコピー

编译模板,得到一个渲染的方法,然后传入实际数据数据就可以得到渲染后的HTML了

function list() {
  let tmpl = fs.readFileSync(path.resolve(dirname, 'template', 'list.html'), 'utf8');
  return handlebars.compile(tmpl);
}
ログイン後にコピー

这样一个简单的静态服务器就完成了,其中包含了静态文件服务,实现缓存,实现断点续传,分块获取,实现压缩的功能

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

在vue-router里query动态传参步骤有哪些

本地开发环境不能用IP访问如何处理

以上がNode静的リソースサーバーの操作方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Express を使用してノード プロジェクトでファイルのアップロードを処理する方法 Express を使用してノード プロジェクトでファイルのアップロードを処理する方法 Mar 28, 2023 pm 07:28 PM

ファイルのアップロードをどのように処理するか?次の記事では、Express を使用してノード プロジェクトでファイルのアップロードを処理する方法を紹介します。

nvmでノードを削除する方法 nvmでノードを削除する方法 Dec 29, 2022 am 10:07 AM

nvm でノードを削除する方法: 1. 「nvm-setup.zip」をダウンロードして C ドライブにインストールします; 2. 「nvm -v」コマンドで環境変数を構成し、バージョン番号を確認します; 3. 「nvm」を使用しますinstall" コマンド ノードのインストール; 4. "nvm uninstall" コマンドでインストールしたノードを削除します。

115 ネットワーク ディスク上のリソースを見つける方法 115 ネットワーク ディスク上のリソースを見つける方法 Feb 23, 2024 pm 05:10 PM

115 ネットワーク ディスクには大量のリソースが存在しますが、リソースを見つけるにはどうすればよいでしょうか?ユーザーはソフトウェア内で必要なリソースを検索し、ダウンロード インターフェイスに入り、ネットワーク ディスクに保存することを選択できます。 115 ネットワーク ディスク上のリソースを検索する方法のこの紹介では、具体的な内容を説明します。 115 ネットワーク ディスク上のリソースを見つけるにはどうすればよいですか? 回答: ソフトウェアでコンテンツを検索し、クリックしてネットワーク ディスクに保存します。詳細な紹介: 1. まず、アプリに必要なリソースを入力します。 2. 次に、表示されるキーワードのリンクをクリックします。 3. 次に、ダウンロード インターフェイスに入ります。 4. 内部のネットワーク ディスクに保存をクリックします。

Nodeのプロセス管理ツール「pm2」を徹底分析 Nodeのプロセス管理ツール「pm2」を徹底分析 Apr 03, 2023 pm 06:02 PM

この記事では、Node のプロセス管理ツール「pm2」について説明し、pm2 が必要な理由、pm2 のインストール方法と使用方法について説明します。皆様のお役に立てれば幸いです。

PIノードティーチング:PIノードとは何ですか? PIノードをインストールしてセットアップする方法は? PIノードティーチング:PIノードとは何ですか? PIノードをインストールしてセットアップする方法は? Mar 05, 2025 pm 05:57 PM

ピン張りのノードの詳細な説明とインストールガイドこの記事では、ピネットワークのエコシステムを詳細に紹介します - PIノードは、ピン系生態系における重要な役割であり、設置と構成の完全な手順を提供します。 Pinetworkブロックチェーンテストネットワークの発売後、PIノードは多くの先駆者の重要な部分になり、テストに積極的に参加し、今後のメインネットワークリリースの準備をしています。まだピン張りのものがわからない場合は、ピコインとは何かを参照してください。リストの価格はいくらですか? PIの使用、マイニング、セキュリティ分析。パインワークとは何ですか?ピン競技プロジェクトは2019年に開始され、独占的な暗号通貨PIコインを所有しています。このプロジェクトは、誰もが参加できるものを作成することを目指しています

npm ノード gyp が失敗した場合の対処方法 npm ノード gyp が失敗した場合の対処方法 Dec 29, 2022 pm 02:42 PM

「node-gyp.js」が「Node.js」のバージョンと一致しないため、npm node gyp が失敗します。解決策は次のとおりです: 1. 「npm cache clean -f」を使用してノード キャッシュをクリアします; 2. 「npm install -」を使用します。 g n" n モジュールをインストールします。 3. 「n v12.21.0」コマンドを使用して、「node v12.21.0」バージョンをインストールします。

Angular と Node を使用したトークンベースの認証 Angular と Node を使用したトークンベースの認証 Sep 01, 2023 pm 02:01 PM

認証は、Web アプリケーションの最も重要な部分の 1 つです。このチュートリアルでは、トークンベースの認証システムと、それが従来のログイン システムとどのように異なるかについて説明します。このチュートリアルを終えると、Angular と Node.js で書かれた完全に動作するデモが表示されます。従来の認証システム トークンベースの認証システムに進む前に、従来の認証システムを見てみましょう。ユーザーはログイン フォームにユーザー名とパスワードを入力し、[ログイン] をクリックします。リクエストを行った後、データベースにクエリを実行してバックエンドでユーザーを認証します。リクエストが有効な場合、データベースから取得したユーザー情報を使用してセッションが作成され、セッション情報が応答ヘッダーで返され、セッション ID がブラウザに保存されます。対象となるアプリケーションへのアクセスを提供します。

なぜHan Xiaoquanには突然リソースがなくなったのでしょうか? なぜHan Xiaoquanには突然リソースがなくなったのでしょうか? Feb 24, 2024 pm 03:22 PM

Han Xiaoquan は多くの韓国ドラマを視聴できるソフトウェアですが、なぜ突然リソースがなくなったのですか?このソフトウェアには、ネットワークの問題、バージョンの問題、または著作権の問題により、リソースがない可能性があります。 Han Xiaoquan が突然リソースを失った理由についてのこの記事では、その具体的な内容を説明します。 Han Xiaoquan に突然リソースがなくなったのはなぜですか? 回答: ネットワークの問題、バージョンの問題、および著作権の問題のため、詳細な紹介: 1. ネットワーク問題の解決策: 別のネットワークを選択し、ソフトウェアに再度ログインして試すことができます。 。 2. バージョンの問題の解決策: ユーザーは、このソフトウェアの最新バージョンを公式 Web サイトからダウンロードできます。 3. 著作権問題への対応: 一部の韓国ドラマは著作権問題により棚から削除されていますが、他の韓国ドラマを選択して視聴することができます。

See all articles