当前位置: 首页 > news >正文

网站建设叁金手指花总2网站服务理念

网站建设叁金手指花总2,网站服务理念,杭州动漫设计公司最新招聘,seo网络优化平台jsnbd介绍 本文主要介绍一个名为jsnbd的开源项目#xff0c;位于GitHub - openbmc/jsnbd#xff0c;它实现了一个前端#xff08;包含HTML和JS文件#xff09;页面#xff0c;作为存储服务器#xff0c;可以指定存储内容#xff1b;还包含一个后端的代理#xff0c;这…jsnbd介绍 本文主要介绍一个名为jsnbd的开源项目位于GitHub - openbmc/jsnbd它实现了一个前端包含HTML和JS文件页面作为存储服务器可以指定存储内容还包含一个后端的代理这里所谓的代理实际上的作用是将nbd-client和前端连接起来作为客户端可以通过网络下载前面指定的存储内容。该开源项目在OpenBMCOpenBMC · GitHub中有使用。 在介绍jsnbd之前先介绍整体原理这需要从NBD开始。 NBD全称Network Block Device可以让用户下图左侧部分将一个远程主机下图右侧部分的磁盘空间当作一个块设备来使用就像使用本地的硬盘一样。其大致框架如下图所示 两台服务器之间通过网络通信一台安装有nbd-client作为客户端另一台安装有nbd-server这样作为客户端就可以从服务端获取服务端硬盘的数据而且由于NBD的封装从客户端用户来看就像是操作普通硬盘设备一样。为此无论是客户端还是服务端它们的操作系统都还需要包含NBD模块用于底层的操作。其结构如下 这一部分由操作系统提供并不会在本文中详细介绍。 而对于jsnbd来说服务端稍有不同它不再使用NBD Server应用而是替换成一个Web程序所以第一张图变为如下的形式 但是实际使用时并没有这么简单因为Web端程序无法直接通过网络跟nbd-client交互Web需要首先跟Web服务器交互而Web服务器又需要通过代理转到nbd-client上所以这里还有两层实际的框图应该是这样的 而本文介绍的重点就是上图的橙色部分这三个部分分别是 前端这里由一个JS文件和一个HTML文件完成前者提供一个WebSocket的实现后者提供一个简单的操作界面用于选择文件作为存储“设备”供客户端访问。Web服务器lighttpd前端作为服务端需要与后端作为客户端的Web服务器沟通这里使用lighttpd作为Web服务器除了作为Web服务器它还需要实现WebSocket的转发这可以通过lighttpd自带的mod来实现。nbd-proxy作为jsnbd的后端部分其代码也已经由jsnbd项目提供作为nbd-client和lighttpd之间的代理工具lighttpd的转发会被它处理而它的实现简单来说就是接收数据和操作nbd-client。 当然从图中还可以看到额外的一些内容 Linux由于jsnbd只实现了Linux版本所以需要在Linux下测试本文使用Windows操作系统的VMware虚拟机安装Ubuntu来作为测试用Linux。 NBD模块它是Linux内核模块通过CONFIG_BLK_DEV_NBDy包含需要注意WSLWindows自带的虚拟机中没有NBD模块所以无法使用WSL来测试NBD会报错 jwHOME:~/code/nbdjs$ sudo modprobe nbd modprobe: FATAL: Module nbd not found in directory /lib/modules/5.15.90.1-microsoft-standard-WSL2nbd-clientLinux下的应用完成与底层NBD的交互。 它们可能会在本文提到但并不会重点介绍。 本文使用的代码已经上传https://gitee.com/jiangwei0512/nbdjs.git经过测试可以完全基本的使用。 lighttpd 这里首先介绍lighttpd因为它是前后端交互的基础。 lighttpd是一款轻量级的开源Web服务器跟Apache、Nginx功能差不多对应的官网http://www.lighttpd.net/。 lighttpd目前只支持Linux所以这里在虚拟机Linux上编译和使用lighttpd对应的Linux版本 jwubuntu:~/code/www/html$ uname -a Linux ubuntu 5.15.0-82-generic #91~20.04.1-Ubuntu SMP Fri Aug 18 16:24:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux编译和使用 目前下载到的最新版本是lighttpd-1.4.71.tar.gz。 首先解压缩源代码 jwubuntu:~/code$ tar -xzvf lighttpd-1.4.71.tar.gz安装依赖 jwubuntu:~/code/lighttpd-1.4.71$ sudo apt install zlib1g-dev libpcre2-dev进入解压缩得到的目录然后进行configure jwubuntu:~/code/lighttpd-1.4.71$ ./configure --prefix/usr/local/lighttpd编译 jwubuntu:~/code/lighttpd-1.4.71$ make安装 jwubuntu:~/code/lighttpd-1.4.71$ sudo make install安装的位置是 jwubuntu:~/code/lighttpd-1.4.71$ ls -al /usr/local/lighttpd/ total 20 drwxr-xr-x 5 root root 4096 Sep 3 07:03 . drwxr-xr-x 11 root root 4096 Sep 3 07:03 .. drwxr-xr-x 2 root root 4096 Sep 3 07:03 lib drwxr-xr-x 2 root root 4096 Sep 3 07:03 sbin drwxr-xr-x 3 root root 4096 Sep 3 07:03 share进入到lighttpd程序所在的目录后续以root进行操作 rootubuntu:/usr/local/lighttpd/sbin# ll total 1992 drwxr-xr-x 2 root root 4096 Sep 3 07:03 ./ drwxr-xr-x 5 root root 4096 Sep 3 07:03 ../ -rwxr-xr-x 1 root root 2004088 Sep 3 07:03 lighttpd* -rwxr-xr-x 1 root root 23048 Sep 3 07:03 lighttpd-angel*为了使用lighttpd需要有配置文件下面是一个最简单的例子test.conf server.document-root /home/jw/code/www/html server.port 80 mimetype.assign (.html text/html,.txt text/plain,.jpg image/jpeg,.png image/png ) index-file.names ( index.html )这些配置的意义如下 server.document-root指定了Web服务器目录我们需要在这里放浏览器可以访问的文件后续使用的jsnbd前端代码都会放到这里。server.port指定端口默认非安全的Web服务器端口就是80。mimetype.assign指定支持的文件。index-file.names指定入口文件就是浏览器输入IP之后首先看到的页面。 在server.document-root指定的目录中存放html文件下面是一个例子index.html htmlbodyHello Wolrd!/body /html当通过浏览器登录服务器时首先访问到的就是这个文件。 启动lighttpd的应用程序的命令如下 rootubuntu:/usr/local/lighttpd/sbin# ./lighttpd -D -f test.conf 2023-09-03 07:17:49: (server.c.1909) server started (lighttpd/1.4.71)启动之后该服务器会持续运行此时就可以通过浏览器访问输入的IP就是Linux系统的IP端口可以不写默认就是80。 测试结果如下图所示 到这里一个简单的lighttpd服务器就已经开启了。 当然这只是一个开始此时浏览器只能访问lighttpd中的简单html文件要想访问后续的nbd-proxy还需要修改配置文件这个将在本文后续说明。这里简单介绍lighttpd如何将内容转发到Linux下的程序这依赖于lighttpd的ws_tunnel插件。 lighttpd和WebSocket 为了使lighttpd支持WebSocket首先需要修改它的配置以下是修改之后的test.conf server.modules (mod_wstunnel )server.document-root /home/jw/code/www/html server.port 80 mimetype.assign (.html text/html,.txt text/plain,.jpg image/jpeg,.png image/png ) static-file.exclude-extensions ( .fcgi, .php, .rb, ~, .inc ) index-file.names ( index.html )$HTTP[url] ~ ^/websocket.test {wstunnel.server ( ((host 127.0.0.1,port 888)))wstunnel.frame-type text }这里的改动有以下的几个 通过server.modules引入lighttpd插件在lighttpd中通过插件的方式可以引入很多新的特性比如这里的WebSocket对应插件mod_wstunnel还有CGI代理等等。 配置wstunnel所有的参数可以在Docs ConfigurationOptions - Lighttpd - lighty labs找到这里的配置主要针对特定格式的WebSocket其配置有两个一个是转发的地址和端口指向了localhost127.0.0.1和888端口注意它们需要跟转发过去的程序有相同的配置否则该程序接收不到转发的内容另一个是WebSocket的数据格式这里指定的是文本格式。 配置修改之后重新打开lighttpd rootubuntu:/usr/local/lighttpd/sbin# ./lighttpd -D -f test.conf 2023-09-09 22:20:15: (server.c.1909) server started (lighttpd/1.4.71)此时可以查看到网络状态 rootubuntu:/usr/local/lighttpd/sbin# netstat -ntlv Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN tcp6 0 0 ::1:631 :::* LISTEN 这里显示的第一行就是lighttpd服务器它监听80端口IP没有限制。 然后是编写Linux端的转发处理程序下面是一个示例 #include stdio.h #include string.h #include unistd.h #include errno.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include stdlib.h// Should be same with the one in lihttpd.conf and index.html. #define DEFAULT_PORT 888 // Should be same with the one in lihttpd.conf. #define DEFAULT_IP 127.0.0.1int main(int argc, char **argv) {int server_socket -1;int client_socket -1;struct sockaddr_in server_addr;struct sockaddr_in client_addr;char received_buffer[1024]; // Buffer for received.int received_len -1;int sended_len -1;int res -1;socklen_t addr_len sizeof(struct sockaddr);int index;// Create a socket.server_socket socket(AF_INET, SOCK_STREAM, 0);if (server_socket 0){printf(Create socket failed: %s\n, strerror(errno));return -1;}// Bind the created socket on special IP and port.server_addr.sin_family AF_INET;server_addr.sin_port htons(DEFAULT_PORT);server_addr.sin_addr.s_addr inet_addr(DEFAULT_IP);if (bind(server_socket, (struct sockaddr *)server_addr, sizeof(server_addr)) 0){printf(Bind server failed: %s\n, strerror(errno));return -2;}printf(Socket[%d] has bond on port[%d] for IP address[%s]!\n,server_socket, DEFAULT_PORT, DEFAULT_IP);// Listen on the created socket.listen(server_socket, 10);while (1){printf(Waiting and accept new client connect...\n);client_socket accept(server_socket, (struct sockaddr *)client_addr, addr_len);if (client_socket 0){printf(Accept client socket failed: %s\n, strerror(errno));return -3;}printf(Accept new client[%d] socket[%s:%d]\n, client_socket,inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));while (1){memset(received_buffer, 0, sizeof(received_buffer));received_len read(client_socket, received_buffer, sizeof(received_buffer));if (received_len 0){printf(Read data from client [%d] failed: %s\n, client_socket, strerror(errno));close(client_socket);break;}else if (0 received_len){printf(Client [%d] disconnected!\n, client_socket);close(client_socket);break;}else{printf(Read %d bytes from client[%d] and the data is : %s\n,received_len, client_socket, received_buffer);// Send back the received buffer to client.sended_len write(client_socket, received_buffer, received_len);if (sended_len 0){printf(Write data back to client[%d] failed: %s \n, client_socket,strerror(errno));close(client_socket);break;}}}sleep(1);}if (client_socket){close(client_socket);}close(server_socket);return 1; }这里使用了socket编程注意socket和前面提到的WebSocket虽然都用来网络通信但是它们不是同一个东西关于它们的具体差别涉及到socket和WebSocket的基础这里不展开。 这个程序的实现很简单就是将服务器获取到的数据直接返回给发送端。编译然后使用该程序 rootubuntu:/home/jw/code/www/html# gcc websocket_server.c rootubuntu:/home/jw/code/www/html# ./a.out Socket[3] has bond on port[888] for IP address[127.0.0.1]! Waiting and accept new client connect...再次查看网络状态 rootubuntu:/usr/local/lighttpd/sbin# netstat -ntlv Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:888 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN tcp6 0 0 ::1:631 :::* LISTEN 可以看到又多监听了一个端口888IP是localhost127.0.0.1由于ligtttpd的配置前端连接过来的特定WebSocket满足^/websocket.test的格式就会被本程序处理。 发送端代码的编写。前面的两步都在Linux系统中本例使用了虚拟机中的Ubuntu系统而这里的操作可以在任意的系统中使用只要存在浏览器且跟Linux系统可以通过网络通信即可。不过这里编写的Web程序最后还是会在Linux系统中且在lighttpd指定的目录下其示例代码index.html h1Websocket Test/h1 pre idmessages styleheight: 400px; overflow: scroll/pre input typetext idmessageBox placeholderType your message herestyledisplay: block; width: 100%; margin-bottom: 10px; padding: 10px; / button idsend titleSend Message! stylewidth: 100%; height: 30px;Send Message/buttonscript(function () {const sendBtn document.querySelector(#send);const messages document.querySelector(#messages);const messageBox document.querySelector(#messageBox);let ws;function showMessage(message) {messages.textContent \nReceived: ${message};messages.scrollTop messages.scrollHeight;messageBox.value ;}function init() {if (ws) {ws.onerror ws.onopen ws.onclose null;ws.close();}ws new WebSocket(ws:// location.host /websocket.test);ws.onopen () {console.log(Connection opened!);}ws.onmessage ({ data }) showMessage(data);ws.onclose function () {console.log(Connectino closed!);ws null;}}sendBtn.onclick function () {if (!ws) {showMessage(No WebSocket connection :();return;}ws.send(messageBox.value);console.log(Sended: messageBox.value);}init();})(); /script注意这里的 ws new WebSocket(ws:// location.host /websocket.test);location.host对应的是Linux的IP整个URL满足lighttpd中ws_tunnel的转发要求所以会被第二步中的程序接收到。 通过浏览器访问location.host对应的地址也就是Linux系统的IP地址执行结果如下 图中的虚拟机安装有Ubuntu20.04开启两个进程上面的是lighttpd作为Web服务器下面是socket编写的服务器程序虚拟机外面是浏览器输入Ubuntu20.04系统的IP即可访问lighttpd并显示指定目录下的index.html文件在该界面下输入的内容会被lighttpd转发给服务器程序而后者打印传递过来的内容然后返回最后在浏览器显示出来。 nbd-proxy 代码 nbd-proxy的源代码可以通过https://github.com/openbmc/jsnbd.git下载注意这个开源代码的名称为jsnbd它包含了前端和后端的代码但是本节主要介绍的是后端nbd-proxy这个部分。还有一个gitee版https://gitee.com/jiangwei0512/nbdjs.git本文后续内容是按照这个版本为基础的。 代码如下 jwHOME:~/code/nbdjs$ ll total 92 drwxr-xr-x 5 jw jw 4096 Sep 1 22:17 ./ drwxr-xr-x 4 jw jw 4096 Sep 1 22:09 ../ -rw-r--r-- 1 jw jw 3693 Sep 1 22:17 .clang-format drwxr-xr-x 8 jw jw 4096 Sep 1 22:17 .git/ -rw-r--r-- 1 jw jw 368 Sep 1 22:17 .gitignore -rw-r--r-- 1 jw jw 11358 Sep 1 22:10 LICENCE -rw-r--r-- 1 jw jw 211 Sep 1 22:10 Makefile.am -rw-r--r-- 1 jw jw 1562 Sep 1 22:10 OWNERS -rw-r--r-- 1 jw jw 2307 Sep 1 22:10 README -rwxr-xr-x 1 jw jw 73 Sep 1 22:10 bootstrap.sh* -rw-r--r-- 1 jw jw 362 Sep 1 22:10 config.sample.json -rw-r--r-- 1 jw jw 446 Sep 1 22:10 configure.ac drwxr-xr-x 2 jw jw 4096 Sep 1 22:10 m4/ -rw-r--r-- 1 jw jw 1067 Sep 1 22:10 meson.build -rw-r--r-- 1 jw jw 103 Sep 1 22:10 meson_options.txt -rw-r--r-- 1 jw jw 20297 Sep 1 22:10 nbd-proxy.c drwxr-xr-x 3 jw jw 4096 Sep 1 22:17 web/这里其实包含两个部分 一部分是前端Web程序位于web目录下它们是NBD服务器的一部分可以在任意浏览器中使用。剩下的属于另外一部分主要就是nbd-proxy.c属于NBD客户端的一部分最终会生成一个nbd-proxy工具目前只有Linux版本。 编译 nbd-proxy在Linux环境下编译对应的环境 jwubuntu:~$ uname -a Linux ubuntu 5.15.0-82-generic #91~20.04.1-Ubuntu SMP Fri Aug 18 16:24:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux编译之前先安装依赖 jwubuntu:~$ sudo apt install libjson-c-dev libudev-dev autoconf然后是编译 jwubuntu:~/code/nbdjs$ autoscan configure.ac: warning: missing AC_CHECK_FUNCS([dup2]) wanted by: nbd-proxy.c:156 configure.ac: warning: missing AC_CHECK_FUNCS([memset]) wanted by: nbd-proxy.c:263 configure.ac: warning: missing AC_CHECK_FUNCS([socket]) wanted by: nbd-proxy.c:89 configure.ac: warning: missing AC_CHECK_FUNCS([strchr]) wanted by: nbd-proxy.c:412 configure.ac: warning: missing AC_CHECK_FUNCS([strdup]) wanted by: nbd-proxy.c:653 configure.ac: warning: missing AC_CHECK_HEADERS([fcntl.h]) wanted by: nbd-proxy.c:25 configure.ac: warning: missing AC_CHECK_HEADERS([limits.h]) wanted by: nbd-proxy.c:29 configure.ac: warning: missing AC_CHECK_HEADERS([stdint.h]) wanted by: nbd-proxy.c:32 configure.ac: warning: missing AC_CHECK_HEADERS([stdlib.h]) wanted by: nbd-proxy.c:34 configure.ac: warning: missing AC_CHECK_HEADERS([string.h]) wanted by: nbd-proxy.c:35 configure.ac: warning: missing AC_CHECK_HEADERS([sys/socket.h]) wanted by: nbd-proxy.c:37 configure.ac: warning: missing AC_CHECK_HEADERS([unistd.h]) wanted by: nbd-proxy.c:42 configure.ac: warning: missing AC_CHECK_HEADER_STDBOOL wanted by: nbd-proxy.c:47 configure.ac: warning: missing AC_CHECK_MEMBERS([struct stat.st_rdev]) wanted by: nbd-proxy.c:812 configure.ac: warning: missing AC_FUNC_FORK wanted by: nbd-proxy.c:137 configure.ac: warning: missing AC_FUNC_MALLOC wanted by: nbd-proxy.c:869 configure.ac: warning: missing AC_TYPE_PID_T wanted by: nbd-proxy.c:58 configure.ac: warning: missing AC_TYPE_SIZE_T wanted by: nbd-proxy.c:63 configure.ac: warning: missing AC_TYPE_SSIZE_T wanted by: nbd-proxy.c:196 configure.ac: warning: missing AC_TYPE_UINT8_T wanted by: nbd-proxy.c:62 jwubuntu:~/code/nbdjs$ aclocal jwubuntu:~/code/nbdjs$ autoreconf --install configure.ac:9: installing ./ar-lib configure.ac:8: installing ./compile configure.ac:5: installing ./install-sh configure.ac:5: installing ./missing Makefile.am: installing ./depcomp jwubuntu:~/code/nbdjs$ ./configure checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /usr/bin/mkdir -p checking for gawk... no checking for mawk... mawk checking whether make sets $(MAKE)... yes checking whether make supports nested variables... yes checking whether make supports nested variables... (cached) yes checking for gcc... gcc checking whether the C compiler works... yes checking for C compiler default output file name... a.out checking for suffix of executables... checking whether we are cross compiling... no checking for suffix of object files... o checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes checking for gcc option to accept ISO C89... none needed checking whether gcc understands -c and -o together... yes checking whether make supports the include directive... yes (GNU style) checking dependency style of gcc... gcc3 checking for ar... ar checking the archiver (ar) interface... ar checking whether make sets $(MAKE)... (cached) yes checking whether C compiler accepts -Wall... yes checking whether C compiler accepts -Werror... yes checking for splice... yes checking for pkg-config... /usr/bin/pkg-config checking pkg-config is at least version 0.9.0... yes checking for JSON... yes checking for UDEV... yes checking that generated files are newer than configure... done configure: creating ./config.status config.status: creating Makefile config.status: creating config.h config.status: executing depfiles commands jwubuntu:~/code/nbdjs$ make make all-am make[1]: Entering directory /home/jw/code/nbdjsCC nbd_proxy-nbd-proxy.oCCLD nbd-proxy make[1]: Leaving directory /home/jw/code/nbdjs最终生成的nbd-proxy就是我们需要的工具 jwubuntu:~/code/nbdjs$ ll nbd-proxy -rwxrwxr-x 1 jw jw 64184 Sep 1 09:45 nbd-proxy*使用 在使用之前还需要安装另外一个工具nbd-client rootubuntu:/home/jw/code/nbdjs# sudo apt install nbd-client不过光安装应用还不够还需要使用到Linux的NBD模块所以还需要执行如下的命令 rootubuntu:/home/jw/code/nbdjs# sudo modprobe nbd为了确定NBD模块是否加载可以使用如下的命令查看 jwubuntu:~/code/nbdjs$ ls /dev/nbd* /dev/nbd0 /dev/nbd10 /dev/nbd12 /dev/nbd14 /dev/nbd2 /dev/nbd4 /dev/nbd6 /dev/nbd8 /dev/nbd1 /dev/nbd11 /dev/nbd13 /dev/nbd15 /dev/nbd3 /dev/nbd5 /dev/nbd7 /dev/nbd9如果有上述的一系列/dev/nbd*设备表示NBD模块已经成功加载。 之后创建一个配置文件config.json内容如下 jwubuntu:~/code/nbdjs$ cat config.json {timeout: 30,configurations: {0: {nbd-device: /dev/nbd0,metadata: {description: Virtual media device}}} }将该文件放到/usr/local/etc/nbd-proxy目录下 jwubuntu:~/code/nbdjs$ sudo mkdir /usr/local/etc/nbd-proxy jwubuntu:~/code/nbdjs$ sudo cp config.json /usr/local/etc/nbd-proxy/之后还需要修改代码 // static const char* sockpath_tmpl RUNSTATEDIR /nbd.%d.sock; static const char* sockpath_tmpl /tmp/nbd.%d.sock;因为RUNSTATEDIR对应的/usr/local/var/run无法直接访问。 修改之后就可以执行nbd-proxy工具 rootubuntu:/home/jw/code/nbdjs# ./nbd-proxy 此时不会有什么输出内容也不能直接交互。这需要使用到Web前端内容而为了启动前端这里先使用websocketd工具它可以完成整个jsnbd的测试。 下载工具 rootubuntu:/home/jw/code/nbdjs# apt install websocketd启动该工具 rootubuntu:/home/jw/code/nbdjs# websocketd --port8000 --staticdirweb --binary ./nbd-proxy Sun, 12 Nov 2023 15:03:10 0800 | INFO | server | | Serving using application : ./nbd-proxy Sun, 12 Nov 2023 15:03:10 0800 | INFO | server | | Serving static content from : web Sun, 12 Nov 2023 15:03:10 0800 | INFO | server | | Starting WebSocket server : ws://ubuntu:8000/ Sun, 12 Nov 2023 15:03:10 0800 | INFO | server | | Serving CGI or static files : http://ubuntu:8000/参数说明 port指定端口浏览器打开IP时需要增加该端口。staticdir指定前端页面入口浏览器默认会去获取该目录下的index.html该文件也已经在开源代码中。binary指定二进制模式数据会被转到nbd-proxy程序中。 打开浏览器界面如下点击“Browse”可以选择文件“Serve Image”后开始服务 通过/dev/nbd0就可以查看到远端的文件 rootubuntu:/home/jw# fdisk -l /dev/nbd0 Disk /dev/nbd0: 1 MiB, 1048576 bytes, 2048 sectors Units: sectors of 1 * 512 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes而点击浏览器的“Stop”之后就无法再看到设备了 rootubuntu:/home/jw# fdisk -l /dev/nbd0 fdisk: cannot open /dev/nbd0: Inappropriate ioctl for device到这里jsnbd的前后端就联系起来了。 不过这里使用的是websocketd工具它将数据直接传递给了nbd-proxy对于lighttpd并不能直接使用这一套东西。为此需要修改相关的代码为此需要先了解nbd-proxy的实现。 代码说明 nbd-proxy的实现主要是以下的几个部分。 首先初始化这需要读取外部的文件这个文件在之前已经介绍过就是config.json。读取文件和初始化的流程大致如下 #mermaid-svg-6EtdrT7JjqrPWOf7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .error-icon{fill:#552222;}#mermaid-svg-6EtdrT7JjqrPWOf7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6EtdrT7JjqrPWOf7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .marker.cross{stroke:#333333;}#mermaid-svg-6EtdrT7JjqrPWOf7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .cluster-label text{fill:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .cluster-label span{color:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .label text,#mermaid-svg-6EtdrT7JjqrPWOf7 span{fill:#333;color:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .node rect,#mermaid-svg-6EtdrT7JjqrPWOf7 .node circle,#mermaid-svg-6EtdrT7JjqrPWOf7 .node ellipse,#mermaid-svg-6EtdrT7JjqrPWOf7 .node polygon,#mermaid-svg-6EtdrT7JjqrPWOf7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .node .label{text-align:center;}#mermaid-svg-6EtdrT7JjqrPWOf7 .node.clickable{cursor:pointer;}#mermaid-svg-6EtdrT7JjqrPWOf7 .arrowheadPath{fill:#333333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-6EtdrT7JjqrPWOf7 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-6EtdrT7JjqrPWOf7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6EtdrT7JjqrPWOf7 .cluster text{fill:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 .cluster span{color:#333;}#mermaid-svg-6EtdrT7JjqrPWOf7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6EtdrT7JjqrPWOf7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-6EtdrT7JjqrPWOf7 .default*{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;}#mermaid-svg-6EtdrT7JjqrPWOf7 .default span{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;} 获取参数 由参数确定配置项 初始化上下文 初始化数据缓存 初始化配置 指定配置项 关于配置项的初始化依赖于两个函数config_init()和config_select()后者通过入参来指定配置项。 配置项的一个示例如下 {timeout: 30,configurations: {0: {nbd-device: /dev/nbd0,metadata: {description: Virtual media device}},1: {nbd-device: /dev/nbd1,metadata: {description: Dump Offload}}} }整个初始化和选择配置结果是 由参数决定具体使用哪个配置。如果没有参数指定则使用配置中的默认项。如果连配置项也没有则第一项就是默认项。 后续简化了这个配置文件直接留一项这样就会只使用这项配置 {timeout: 30,configurations: {0: {nbd-device: /dev/nbd0,metadata: {description: Virtual media device}}} }创建用于nbd-proxy和nbd-client的socket对应的函数是open_nbd_socket()其主体的代码 static int open_nbd_socket(struct ctx* ctx) {rc asprintf(path, sockpath_tmpl, getpid());if (rc 0)return -1;// 创建socket参数说明如下// SOCK_STREAM:// Provides sequenced, reliable, two-way, connection-based// byte streams. An out-of-band data transmission mechanism// may be supported.// SOCK_CLOEXEC:// Set the close-on-exec (FD_CLOEXEC) flag on the new file// descriptor. See the description of the O_CLOEXEC flag in// open(2) for reasons why this may be useful.sd socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);// S_IRUSR: 00400 user has read permission// S_IWUSR: 00200 user has write permissionrc fchmod(sd, S_IRUSR | S_IWUSR);addr.sun_family AF_UNIX; // 表示是PF_UNIX类型的socketstrncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);// addr表示的是本地地址rc bind(sd, (struct sockaddr*)addr, sizeof(addr));rc listen(sd, 1);ctx-sock sd;ctx-sock_path path; }socket有两种类型这里使用的是AF_UNIX类型的重点就是这里的ctx-sock_path它会被nbd-client使用到这样socket就与nbd-client联系起来了该操作在之后的代码中会进一步说明。 开启nbd-client对应的函数是start_nbd_client() static int start_nbd_client(struct ctx* ctx) {pid fork();// 子进程开始执行nbd-client命令if (pid 0){// 执行的命令大致是// nbd-client -u /usr/local/var/run/nbd.xxx.sock -n -L -t 30 /dev/nbd0// 关于参数的说明// -u指定UNIX域的socket这个socket就是nbd-client拿数据的地方// -n: 表示nofor// -L表示nonetlink// -t指定超时时间// 最后是NDB设备execlp(nbd-client, nbd-client, -u, ctx-sock_path, -n, -L,-t, timeout_str, ctx-config-nbd_device, NULL);err(EXIT_FAILURE, cant start ndb client);}ctx-nbd_client_pid pid; }这里操作的命令就是 nbd-client -u /usr/local/var/run/nbd.xxx.sock -n -L -t 30 /dev/nbd0-u之后的就是前面提到的AF_UNIX类型的sockekt最后的是nbd设备也是我们最终访问的设备。 最重要的数据处理位于run_proxy()函数其实现基础就是数据的搬运通过copy_fd()函数 static int run_proxy(struct ctx* ctx) {/* main proxy: forward data between stdio socket */pollfds[0].fd ctx-sock_client;pollfds[0].events POLLIN;pollfds[1].fd STDIN_FILENO;pollfds[1].events POLLIN;// 数据处理的主题还有其它信号和设备操作的处理for (;;){errno 0;rc poll(pollfds, n_fd, -1);if (rc 0){if (errno EINTR)continue;warn(poll failed);break;}if (pollfds[0].revents){rc copy_fd(ctx, ctx-sock_client, STDOUT_FILENO);if (rc 0)break;}if (pollfds[1].revents){rc copy_fd(ctx, STDIN_FILENO, ctx-sock_client);if (rc 0)break;}} }这里的数据搬运涉及到的两端一端是前面的socket对应的文件描述符另一端是标准输入输出由于websocketd接管了nbd-proxy程序的标准输入输出所以实际上就是websocketd与socket的通信。 最终数据的传输如下图所示 #mermaid-svg-0LKbf4TITcqxcjaz {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .error-icon{fill:#552222;}#mermaid-svg-0LKbf4TITcqxcjaz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0LKbf4TITcqxcjaz .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-0LKbf4TITcqxcjaz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0LKbf4TITcqxcjaz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0LKbf4TITcqxcjaz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0LKbf4TITcqxcjaz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0LKbf4TITcqxcjaz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0LKbf4TITcqxcjaz .marker.cross{stroke:#333333;}#mermaid-svg-0LKbf4TITcqxcjaz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0LKbf4TITcqxcjaz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .cluster-label text{fill:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .cluster-label span{color:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .label text,#mermaid-svg-0LKbf4TITcqxcjaz span{fill:#333;color:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .node rect,#mermaid-svg-0LKbf4TITcqxcjaz .node circle,#mermaid-svg-0LKbf4TITcqxcjaz .node ellipse,#mermaid-svg-0LKbf4TITcqxcjaz .node polygon,#mermaid-svg-0LKbf4TITcqxcjaz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0LKbf4TITcqxcjaz .node .label{text-align:center;}#mermaid-svg-0LKbf4TITcqxcjaz .node.clickable{cursor:pointer;}#mermaid-svg-0LKbf4TITcqxcjaz .arrowheadPath{fill:#333333;}#mermaid-svg-0LKbf4TITcqxcjaz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0LKbf4TITcqxcjaz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0LKbf4TITcqxcjaz .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-0LKbf4TITcqxcjaz .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-0LKbf4TITcqxcjaz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0LKbf4TITcqxcjaz .cluster text{fill:#333;}#mermaid-svg-0LKbf4TITcqxcjaz .cluster span{color:#333;}#mermaid-svg-0LKbf4TITcqxcjaz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0LKbf4TITcqxcjaz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-0LKbf4TITcqxcjaz .default*{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;}#mermaid-svg-0LKbf4TITcqxcjaz .default span{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;} /dev/nbd0 nbd-client socket websocketd 当然nbd-client中还有一些其它的处理代码它们跟信号、udev等有关不过这些部分不影响代码修改所以暂时不关注。 代码修改 为了使nbd-proxy能够与lighttpd连接只需要根据lighttpd和WebSocket的示例代码进行修改即可修改的重点在于下图 #mermaid-svg-d278KlWIiMKhiGae {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-d278KlWIiMKhiGae .error-icon{fill:#552222;}#mermaid-svg-d278KlWIiMKhiGae .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d278KlWIiMKhiGae .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-d278KlWIiMKhiGae .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d278KlWIiMKhiGae .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d278KlWIiMKhiGae .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d278KlWIiMKhiGae .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d278KlWIiMKhiGae .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d278KlWIiMKhiGae .marker.cross{stroke:#333333;}#mermaid-svg-d278KlWIiMKhiGae svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d278KlWIiMKhiGae .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d278KlWIiMKhiGae .cluster-label text{fill:#333;}#mermaid-svg-d278KlWIiMKhiGae .cluster-label span{color:#333;}#mermaid-svg-d278KlWIiMKhiGae .label text,#mermaid-svg-d278KlWIiMKhiGae span{fill:#333;color:#333;}#mermaid-svg-d278KlWIiMKhiGae .node rect,#mermaid-svg-d278KlWIiMKhiGae .node circle,#mermaid-svg-d278KlWIiMKhiGae .node ellipse,#mermaid-svg-d278KlWIiMKhiGae .node polygon,#mermaid-svg-d278KlWIiMKhiGae .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d278KlWIiMKhiGae .node .label{text-align:center;}#mermaid-svg-d278KlWIiMKhiGae .node.clickable{cursor:pointer;}#mermaid-svg-d278KlWIiMKhiGae .arrowheadPath{fill:#333333;}#mermaid-svg-d278KlWIiMKhiGae .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d278KlWIiMKhiGae .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d278KlWIiMKhiGae .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-d278KlWIiMKhiGae .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-d278KlWIiMKhiGae .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d278KlWIiMKhiGae .cluster text{fill:#333;}#mermaid-svg-d278KlWIiMKhiGae .cluster span{color:#333;}#mermaid-svg-d278KlWIiMKhiGae div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d278KlWIiMKhiGae :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-d278KlWIiMKhiGae .default*{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;}#mermaid-svg-d278KlWIiMKhiGae .default span{fill:#bbb!important;stroke:#333!important;stroke-width:2px!important;} /dev/nbd0 nbd-client socket 新增socket lighttpd 下面是具体的操作 首先是新增socket用于在lighttpd和原有的socket之间的数据通信 static int open_web_socket(struct ctx* ctx) {sd socket(AF_INET, SOCK_STREAM, 0);rc fchmod(sd, S_IRUSR | S_IWUSR);server_addr.sin_family AF_INET;server_addr.sin_port htons(DEFAULT_PORT);server_addr.sin_addr.s_addr inet_addr(DEFAULT_IP);rc bind(sd, (struct sockaddr *)server_addr, sizeof(server_addr));rc listen(sd, 1);ctx-web_sock sd; }与nbd-proxy原始代码中的socket类型不同这个是AF_INET类型的因为它需要与lighttpd通信这里的DEFAULT_PORT和DEFAULT_IP需要跟lighttpd中的配置一致 $HTTP[url] ~ ^/websocket.test {wstunnel.server ( ((host 127.0.0.1,port 888)))wstunnel.frame-type binary }该配置在前面也出现过不同的是frame-type不再是text而是binary。 然后是建立lighttpd和socket的连接 static int wait_for_web_socket(struct ctx* ctx) {pollfds[0].fd ctx-web_sock;pollfds[0].events POLLIN;for (;;){rc poll(pollfds, 1, -1);if (pollfds[0].revents){rc accept4(ctx-web_sock, NULL, NULL, SOCK_CLOEXEC);ctx-web_sock_fd rc;break;}}return 0; }连接成功之后得到文件描述符ctx-web_sock_fd后续数据通信需要依赖于它。 修改原始代码中的数据传输代码 static int run_proxy(struct ctx* ctx) {if (pollfds[0].revents){rc copy_fd(ctx, ctx-sock_client, ctx-web_sock_fd);if (rc 0)break;}if (pollfds[1].revents){rc copy_fd(ctx, ctx-web_sock_fd, ctx-sock_client);if (rc 0)break;}} }该代码在前面已经出现过不过copy_fd()的参数有做修改不再是标准输入输出而是前面代码中得到的文件描述符。 前端的代码也需要稍微修改主要是WebSocket需要修改 function start_server() {server new NBDServer(ws:// location.host /websocket.test, file); }这里增加了websocket.test标识符这样lighttpd才能够转发。 再次使用 之后就可以使用lighttpd服务器来使用nbd-proxy了操作如下 打开lighttpd注意test.conf已经跟lighttpd和WebSocket中的不同 rootubuntu:/usr/local/lighttpd/sbin# ./lighttpd -D -f test.conf 重新编译nbd-proxy并开启服务 rootubuntu:/home/jw/code/nbdjs# ./nbd-proxy 打开浏览器上传文件 指定文件并点击“Server Image”后就开启了服务器之后就可以正常查看/dev/nbd0了表示已经连接上。 注意目前的代码修改非常的原始还存在不少的问题只能作为示例使用。 前端 前端代码没有编译之类的部分只是包含一个html和一个js文件把它们放在服务器配置指定的位置能够让服务器找到即可这样打开浏览器输入正确的网址之后就会直接访问到html文件。html的实现没有多少可以介绍的这里详细说明js文件。 代码 js代码中包含了一个状态机来处理数据其初始化和状态机处理流程如下 代码说明已经包含在注释中 /* handshake flags */ const NBD_FLAG_FIXED_NEWSTYLE 0x1; const NBD_FLAG_NO_ZEROES 0x2;/* transmission flags */ const NBD_FLAG_HAS_FLAGS 0x1; const NBD_FLAG_READ_ONLY 0x2;/* option negotiation */ const NBD_OPT_EXPORT_NAME 0x1; const NBD_REP_FLAG_ERROR 0x1 31; const NBD_REP_ERR_UNSUP NBD_REP_FLAG_ERROR | 1;/* command definitions */ const NBD_CMD_READ 0; const NBD_CMD_WRITE 1; const NBD_CMD_DISC 2; const NBD_CMD_FLUSH 3; const NBD_CMD_TRIM 4;/* errno */ const EPERM 1; const EIO 5; const ENOMEM 12; const EINVAL 22; const ENOSPC 28; const EOVERFLOW 75; const ESHUTDOWN 108;/* internal object state */ const NBD_STATE_UNKNOWN 1; const NBD_STATE_OPEN 2; const NBD_STATE_WAIT_CFLAGS 3; const NBD_STATE_WAIT_OPTION 4; const NBD_STATE_TRANSMISSION 5;// 定义对象构造器后续会创建该构造类型的对象为了web\index.html function NBDServer(endpoint, file) {// index.html中得到的文件this.file file;this.endpoint endpoint;this.ws null;// NBD的状态不同的状态下nbd有不同的操作该操作在recv_handlers中定义// 默认初始化的是无效状态该状态下什么也不会做因为recv_handlers中没有它的操作函数this.state NBD_STATE_UNKNOWN;// 存放获取到的数据this.msgbuf null;// start函数用于创建WebSocketthis.start function () {// 当NBD开始执行时的状态该状态本身也没有多大的意义this.state NBD_STATE_OPEN;// WebSocket的构造函数WebSocket(url[, protocols])这里没有使用protocols所以就只使用了urlthis.ws new WebSocket(this.endpoint);this._log(WebSocket created);// 传输的数据格式按ArrayBuffer对象来表示对固定长度的连续内存的引用// The ArrayBuffer object is used to represent a generic raw binary data buffer// 构造函数ArrayBuffer()// 具体参考https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBufferthis.ws.binaryType arraybuffer;// WebSocket通过onmessage事件来接收服务器返回的数据// 注意这里使用了bind函数// bind()方法创建一个新的函数在bind()被调用时这个新函数的this被指定为bind()的第一个参数而其余参数将作为新函数的参数供调用时使用// 简单说就是onmessage回调函数对应到_on_ws_messagethis.ws.onmessage this._on_ws_message.bind(this);// WebSocket连接建立时触发this.ws.onopen this._on_ws_open.bind(this);}// stop函数主要就是关闭WebSocketthis.stop function () {this.ws.close();this.state NBD_STATE_UNKNOWN;}this._log function (msg) {// 在NBDServer创建成功之前先用console.log后续会在index.html的代码中设置this.onlog// 这样就会在页面直接显示出来结果if (this.onlog) {this.onlog(msg);} else {console.log(msg);}}/* websocket event handlers */// ws.onopen的实现会在建立WebSocket连接时执行// 本函数跟后端nbd-server进行协商不过似乎也不是协商因为就是前端发过去数据并没有其它的交互// 具体的实现主要在_negotiate函数this._on_ws_open function (ev) {this.client {flags: 0,};this._negotiate();}// 入参是MessageEvent它包含属性// data: 返回DOMString, Blob或者ArrayBuffer包含来自发送者的数据。对于本例疾就是ArrayBufferthis._on_ws_message function (ev) {// 获取到ArrayBuffervar data ev.data;if (this.msgbuf null) {// 最开始msgbuf中是没有数据的所以直接就可以拿来用this.msgbuf data;} else {// 之后有数据了就要将原本的数据放到最开始然后往后面添加数据// Uint8Array数组类型表示一个8位无符号整型数组创建时内容被初始化为0// 创建完后可以以对象的方式或使用数组下标索引的方式引用数组中的元素// 这里创建一个足够大的内存空间包括原有的数据和新增的数据所以长度是两者相加var tmp new Uint8Array(this.msgbuf.byteLength data.byteLength);// 从源数据比如this.msgbuf和data拷贝数据到新的内存空间tmp.set(new Uint8Array(this.msgbuf), 0);tmp.set(new Uint8Array(data), this.msgbuf.byteLength);// 新的空间被赋值该this.msgbuf因为后续处理数据的对象就是它this.msgbuf tmp.buffer;}for (; ;) {// 根据不同的状态来获取处理函数// 当NBD开始工作的时候初始的状态是_negotiate函数中设置的NBD_STATE_WAIT_CFLAGS// 所以最开始执行的操作是_handle_cflags// 当一次_handle_cflags函数处理之后值又被设置成了NBD_STATE_WAIT_OPTION// 之后就是执行_handle_option// 当一次_handle_option函数处理之后值可能变成NBD_STATE_TRANSMISSION那么就会执行_handle_cmd// 或者不变那么还是执行_handle_option// 而这个变或者不变的条件是本次的数据msgbuf// 如果是_handle_cmd则还要根据msgbuf中的请求类型执行不同的操作// NBD_CMD_READ对应_handle_cmd_read操作它是当前代码唯一支持的操作就是后台读数据这也是我们需要的// NBD_CMD_DISC表示nbd-server想要端口所以最终就是端口WebSocket// NBD_CMD_WRITE并没事实际支持// NBD_CMD_TRIM不支持var handler this.recv_handlers[this.state];if (!handler) {this._log(no handler for state this.state);this.stop();break;}var consumed handler(this.msgbuf);if (consumed 0) {this._log(handler[state this.state ] returned error consumed);this.stop();break;}if (consumed 0)break;if (consumed 0) {if (consumed this.msgbuf.byteLength) {this.msgbuf null;break;}this.msgbuf this.msgbuf.slice(consumed);}}}this._negotiate function () {// 相当与一段内存var buf new ArrayBuffer(18);// 但是这段内存不能直接使用必须要通过View Object来操作// DataView就是一种View Object指的是自定义的解析器var data new DataView(buf, 0, 18);// 后面就是创建数据并通过WebSocket发送数据总共18个字节// 首先是魔术字/* NBD magic: NBDMAGIC */data.setUint32(0, 0x4e42444d);data.setUint32(4, 0x41474943);/* newstyle negotiation: IHAVEOPT */data.setUint32(8, 0x49484156);data.setUint32(12, 0x454F5054);/* flags: fixed newstyle negotiation, no padding */// 这里的值需要从nbd-server/nbd-client代码中去找为了cliserv.h// #define NBD_FLAG_FIXED_NEWSTYLE (1 0) /** new-style export that actually supports extending */// #define NBD_FLAG_NO_ZEROES (1 1) /** we wont send the 128 bits of zeroes if the client sends NBD_FLAG_C_NO_ZEROES */// 实际上这些数据最终都会送到后台的nbd-server/nbd-client中而本源码中的nbd-proxy只是一个中转站data.setUint16(16, NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES);// 当与后台的nbd-server建立连接之后的状态// 该状态下涉及处理函数_handle_cflagsthis.state NBD_STATE_WAIT_CFLAGS;this.ws.send(buf);}/* handlers */this._handle_cflags function (buf) {if (buf.byteLength 4)return 0;var data new DataView(buf, 0, 4);this.client.flags data.getUint32(0);this._log(client flags received: 0x this.client.flags.toString(16));this.state NBD_STATE_WAIT_OPTION;return 4;}this._handle_option function (buf) {if (buf.byteLength 16)return 0;var data new DataView(buf, 0, 16);if (data.getUint32(0) ! 0x49484156 ||data.getUint32(4) ! 0x454F5054) {this._log(invalid option magic);return -1;}var opt data.getUint32(8);var len data.getUint32(12);this._log(client option received: 0x opt.toString(16));if (buf.byteLength 16 len)return 0;switch (opt) {case NBD_OPT_EXPORT_NAME:this._log(negotiation complete, starting transmission mode);var n 10;if (!(this.client.flags NBD_FLAG_NO_ZEROES))n 124;var resp new ArrayBuffer(n);var view new DataView(resp, 0, 10);/* export size. */var size this.file.size;view.setUint32(0, Math.floor(size / (2 ** 32)));view.setUint32(4, size 0xffffffff);/* transmission flags: read-only */view.setUint16(8, NBD_FLAG_HAS_FLAGS | NBD_FLAG_READ_ONLY);this.ws.send(resp);this.state NBD_STATE_TRANSMISSION;break;default:/* reject other options */var resp new ArrayBuffer(20);var view new DataView(resp, 0, 20);view.setUint32(0, 0x0003e889);view.setUint32(4, 0x045565a9);view.setUint32(8, opt);view.setUint32(12, NBD_REP_ERR_UNSUP);view.setUint32(16, 0);this.ws.send(resp);}return 16 len;}this._create_cmd_response function (req, rc, data null) {var len 16;if (data)len data.byteLength;var resp new ArrayBuffer(len);var view new DataView(resp, 0, 16);view.setUint32(0, 0x67446698);view.setUint32(4, rc);view.setUint32(8, req.handle_msB);view.setUint32(12, req.handle_lsB);if (data)new Uint8Array(resp, 16).set(new Uint8Array(data));return resp;}this._handle_cmd function (buf) {if (buf.byteLength 28)return 0;var view new DataView(buf, 0, 28);if (view.getUint32(0) ! 0x25609513) {this._log(invalid request magic);return -1;}var req {flags: view.getUint16(4),type: view.getUint16(6),handle_msB: view.getUint32(8),handle_lsB: view.getUint32(12),offset_msB: view.getUint32(16),offset_lsB: view.getUint32(20),length: view.getUint32(24),};/* we dont support writes, so nothing needs the data at present *//* req.data buf.slice(28); */var err 0;var consumed 28;/* the command handlers return 0 on success, and send their* own response. Otherwise, a non-zero error code will be* used as a simple error response*/switch (req.type) {case NBD_CMD_READ:err this._handle_cmd_read(req);break;case NBD_CMD_DISC:err this._handle_cmd_disconnect(req);break;case NBD_CMD_WRITE:/* we also need length bytes of data to consume a write* request */if (buf.byteLength 28 req.length)return 0;consumed req.length;err EPERM;break;case NBD_CMD_TRIM:err EPERM;break;default:this._log(invalid command 0x req.type.toString(16));err EINVAL;}if (err) {var resp this._create_cmd_response(req, err);this.ws.send(resp);}return consumed;}this._handle_cmd_read function (req) {var offset;offset (req.offset_msB * 2 ** 32) req.offset_lsB;if (offset Number.MAX_SAFE_INTEGER)return ENOSPC;if (offset req.length Number.MAX_SAFE_INTEGER)return ENOSPC;if (offset req.length file.size)return ENOSPC;this._log(read: 0x req.length.toString(16) bytes, offset 0x offset.toString(16));var blob this.file.slice(offset, offset req.length);var reader new FileReader();// 发送获取到的文件内容reader.onload (function (ev) {var reader ev.target;if (reader.readyState ! FileReader.DONE)return;var resp this._create_cmd_response(req, 0, reader.result);this.ws.send(resp);}).bind(this);reader.onerror (function (ev) {var reader ev.target;this._log(error reading file: reader.error);var resp this._create_cmd_response(req, EIO);this.ws.send(resp);}).bind(this);// 前面的blob加上这个函数用来读取文件的内容reader.readAsArrayBuffer(blob);return 0;}this._handle_cmd_disconnect function (req) {this._log(disconnect received);this.stop();return 0;}// NBD处于不同状态下时对应的处理函数// 不同的状态由state控制this.recv_handlers Object.freeze({[NBD_STATE_WAIT_CFLAGS]: this._handle_cflags.bind(this),[NBD_STATE_WAIT_OPTION]: this._handle_option.bind(this),[NBD_STATE_TRANSMISSION]: this._handle_cmd.bind(this),}); }
http://www.hkea.cn/news/14538990/

相关文章:

  • 网站域名不合法wordpress批量替换代码
  • 网站建设开发服务费品牌创建的六个步骤
  • 关于加强门户网站建设单页面网站
  • 外贸多语种网站推广成都建设网站首页
  • 网站如何添加二维码家具定制东莞网站建设
  • 在哪找专业做淘宝网站网站开发这个专业前景怎样
  • 专业的网站建设哪家好公众平台微信登录
  • 德州市建设工程质监站网站济南住房和房产信息网
  • 淮北矿业 集团 工程建设有限责任公司网站WordPress画表格
  • wordpress站群管理破解版网页qq音乐在线听
  • 购物网站模板 php中信建设公司好进去吗
  • wordpress自定义简码百度上做优化
  • 如何制作淘客导购网站网站建站服务的公司
  • 内蒙古建设项目环保备案网站网页设计风格分类
  • 下载asp网站网页设计用什么尺寸的画布
  • 公众号的微网站怎么做的什么网站内链建设
  • 住房和城建设网站首页高校网站如何建设论文
  • 大理做网站哪家好建设公司哪家好
  • 网站建设 肥城网页设计尺寸多大
  • 太原网站开发定制成都大邑网站建设
  • 财务公司网站源码宁波网站设计价格
  • 资源付费网站制作怎么做自己的卡盟网站
  • 淘宝购物券网站怎么做做技能培训和那个网站合作好
  • 互联网站备案知名网站建设公司好吗
  • 邢台网站设计哪家专业建设网站的企业专业服务
  • 全企网建站怎么样专业的网站建设费用
  • 营销网站建设文章单页网站seo怎么做
  • 有关商业网站的风格特征温州高端品牌网站建设
  • 郑州网络营销网站网站备案ip查询网站
  • 指定图片做logo网站商城网站制作