如何將他人產品變爲自己的產品?--包裝方案

概述

爲了避免大家說我是標題黨,先說明背景。前段時間,領導和我說了一個項目,需要開發一些無線設備。要求:

  • 傳輸距離1500m左右
  • 帶寬1000M

問題就來了,在公司已有的資源下,是沒有哪一款能夠達到以上的性能。我很肯定的和領導說:“我們的設備性能達不到。如果要做的話,短期內做不出來的”。但是領導可不管,就說:“小謝,你想想辦法,功能簡單點也沒關係。這個項目利潤不錯,你好好幹,年底少不了獎金的”。聽到獎金二字,我就有點激動,回覆道:好好,我試試。

時間短,重新畫板子開發肯定不行了。於是我想到了包裝方案。所謂的包裝方案,就是將別的公司的產品改爲自己的產品,進行出售。ODM就是這樣的生產方式。但是ODM的需要投入的資金也比較多,我們自己有軟件團隊,爲什麼將軟件開發費給別人呢?於是我想到了一個類似ODM的方式:

選擇能夠滿足性能要求的設備M,再找一個千兆帶寬的ARM板 L,利用L設備 telent到M設備上,進行參數設置:SSID,PASSWORD,Mode等基本參數。通過訪問L設備進行參數設置,當然,L設備上的web頁面都是本公司的頁面。

有了想法之後,就在網上找了一些設備,驗證了能夠telnet登陸並修改參數。並將這個方案和領導說了一下,領導聽了之後,感覺能夠滿足客戶需求,並且能夠節約成本,並且能夠在規定時間內完成,便愉快的答應了。通過一個多月的開發和調試,最終項目得以完美交付。

ps:可能有人會有疑惑,爲什麼客戶不找其它廠家,非要找你們,並且還貴。那是因爲我們公司銷售厲害啊-。-

其實在我們工作中,我相信遇到相似的情況並不少。希望能夠給遇到朋友一些思路和建議。說不定就會有柳暗花明又一村的醒悟。

開發過程中遇到的問題

以下內容主要分享一下,在開發過程中遇到的一些問題或思路。希望給有相同想法的小夥伴一些幫助。

telnet的輸入和輸出

在這個方案中,L設備和M設備的通信都是通過telent作爲橋樑,因此,它的輸入輸出極爲重要。

  • 輸入:就是設置L設備參數的命令
  • 輸出:就是L設備返回的狀態

方案一:剛開始我通過shell的輸入輸出重定向到文件中
通過調試一段時間後,發現不可取。總會遇到這樣或那樣的問題,可能是因爲我對shell的標準輸出和標準輸入理解不夠充分。

方案二:修改telnet.c源碼
我們知道telent的輸入輸出都是終端。因此我修改了源碼,將輸入和輸出分別通過read /tmp/input和write /tmp/output文件實現。在功能實現上,我們只需要將設置參數的命令寫入 tmp/input,獲取設備參數從tmp/output中獲取即可。這裏發一下初版修改內容:

int telnet_main(int argc UNUSED_PARAM, char **argv)
{
	char *host;
	int port;
	int len;
	struct pollfd ufds[2];

	INIT_G();

#if ENABLE_FEATURE_TELNET_WIDTH
	get_terminal_width_height(0, &G.win_width, &G.win_height);
#endif

#if ENABLE_FEATURE_TELNET_TTYPE
	G.ttype = getenv("TERM");
#endif

	if (tcgetattr(0, &G.termios_def) >= 0) {
		G.do_termios = 1;
		G.termios_raw = G.termios_def;
		cfmakeraw(&G.termios_raw);
	}

#if ENABLE_FEATURE_TELNET_AUTOLOGIN
	if (1 == getopt32(argv, "al:", &G.autologin)) {
		/* Only -a without -l USER picks $USER from envvar */
		G.autologin = getenv("USER");
	}
	argv += optind;
#else
	argv++;
#endif
	if (!*argv)
		bb_show_usage();
	host = *argv++;
	port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
	if (*argv) /* extra params?? */
		bb_show_usage();

	xmove_fd(create_and_connect_stream_or_die(host, port), netfd);

	setsockopt_keepalive(netfd);

	signal(SIGINT, record_signo);
#if 0
	ufds[0].fd = STDIN_FILENO;
	ufds[0].events = POLLIN;
	ufds[1].fd = netfd;
	ufds[1].events = POLLIN;
#else 
	ufds[1].fd = STDIN_FILENO;
	ufds[1].events = POLLIN;
	ufds[0].fd = netfd;
	ufds[0].events = POLLIN;
        int fdin = open("/tmp/input",O_RDWR|O_CREAT);
        int fdout = open("/tmp/output",O_RDWR|O_CREAT);

#endif
	while (1) {
		if (poll(ufds, 1, 500) < 0) {
			/* error, ignore and/or log something, bay go to loop */
			if (bb_got_signal)
				con_escape();
			else
				sleep(1);
			continue;
		}

// FIXME: reads can block. Need full bidirectional buffering.
#if 0
		if (ufds[0].revents) {
			len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE);
			if (len <= 0)
				doexit(EXIT_SUCCESS);
			TRACE(0, ("Read con: %d\n", len));
			handle_net_output(len);
		}
#else
		len = safe_read(fdin,G.buf,DATABUFSIZE);
		if(len < 0)
			doexit(EXIT_SUCCESS);
		else if(len == 0)
		{
			//sleep(1);
		}
		else
		{
			handle_net_output(len);
		}


#endif
		if (ufds[0].revents) {
			len = safe_read(netfd, G.buf, DATABUFSIZE);
			if (len <= 0) {
				full_write1_str("Connection closed by foreign host\r\n");
				doexit(EXIT_FAILURE);
			}
			TRACE(0, ("Read netfd (%d): %d\n", netfd, len));
			handle_net_input(fdout,len);
		}
	} /* while (1) */
}
linux 轉義序列ansi

對於轉義序列大家可能不太清楚,可參考下面兩圖 ,比較直觀

同一文件在終端的顯示效果

在這裏插入圖片描述

同一文件的文本顯示效果

很明顯文本的內容對於我們而言就是亂碼,但是終端能夠實現對這些ansi轉義序列進行解釋,故能過優美的解釋。但是對於我們web端,它的屬性時text,是不能進行解釋的,顯示出來的就是亂碼。

方案一: 讓web也能夠識別轉義序列
這種解決方案是最好的,我嘗試使用比較火的xterm.js插件,的確可以識別部分的轉義序列,比如可以識別字體顏色。但是還是有部分不能識別,會造成部分亂碼。由於我對前端也不是很瞭解,所以我也沒有深究

方案二:過濾文本中的轉義序列
這個解決方案也是無奈之舉,經過好幾天的糾結還是選擇了這個方式。通過二進制查看文本內容,發現了一些規律,就寫了一個過濾demo。沒想到效果還不錯,就選擇的這一方案。
下面爲過濾demo:

#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>

#define MAXLEN 1024*8
int main()
{
	char *input = "./output";
	char buff[MAXLEN] = {0};
        char out[MAXLEN] = {0};
	
	int fd = open(input,O_RDWR);
	if(fd < 0 )
		printf("open output is error");

	int len = read(fd,buff,MAXLEN);
	char* p = buff;
	int i = 0;
	while (len--)
	{
		if(*p == 0x00 || *p == 0x1b)
		{
			p++;
		}
		else if(*p == '[' && *(p-1) == 0x1b)
		{
			while(1)
			{
				if(*p == 'm' || *p=='K')
				{
					p++;
					break;
				}
				else
					p++;
			}
		}
		else if(*p == 0x0d && *(p+1) == '[')
		{
			p++;
			while(1)
			{
				if(*p == 0x0d)
				{
					p++;
					break;
				}
				else
					p++;
			}
		}
		else if(*p == 0x0d)
		{
			p++;
		}
		else
		{
			out[i++]=*p;
			p++;
		}
		
	}
	write(fd,out,i);
	close(fd);
	return 0;

以上便是項目中可能比較麻煩的地方,其它的工作基本沒有什麼難度。也正因爲難度不大,所以不失爲一個好方案啊,希望能夠給你帶來幫助。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

分割線
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
補充:2020-03-12
優化telent頁面

優化web頁面

在之前的web顯示中,我曾嘗試使用xterm.js來實現,但由於瞭解不深,遇到問題就沒有繼續。採用了textarea,但事實證明偷懶的效果就是醜,並且效果使用也不方便。咱也不多說,直接上圖。
在這裏插入圖片描述
這樣看起是不是覺得很low?並且使用起來很麻煩,你需要在上面的input框中輸入命令,再點擊submit才能正常交互。要是一個前端工作者看到,肯定要把我凌遲。(太不人性化了)。

這兩天沒啥事(主要是因爲我的效率比較高,哈哈),再搗鼓搗鼓一下xterm.js,通過查看官網的文檔,進行修改,也就能正常使用了,並且使用效果還不錯。(遇到困難還是要堅持一下呢)
上圖:

看起來肯定比上面的要簡潔,並且使用起來更加人性化。主要修改的代碼就是htm文件,後臺沒有修改。僅供參考:

<%#
 Copyright 2008 Steven Barth <[email protected]>
 Copyright 2008-2015 Jo-Philipp Wich <[email protected]>
 Licensed to the public under the Apache License 2.0.
-%>
 
<%+header%>

<div class="cbi-section">

<link rel="stylesheet" href="/luci-static/xterm/xterm.css"/>
<script src="/luci-static/xterm/xterm.js"></script>	

	<h2 name="content">Telnet</h2>
	<textarea style="display:none;"  id="showlog"><%=showlog:pcdata()%></textarea>
	
	<form class="inline" id="inputcommand" method="post" action="<%=url('admin/telnet/submit')%>" enctype="multipart/form-data">
		<textarea  style="display:none;" class="command-textarea" style="width: 60%" name="command" id="cbid.luci.command" rows="1"></textarea>	
		<input type="hidden" name="token" value="<%=token%>" />
		<input type="hidden" class="cbi-button cbi-button-action important" name="restore" value="<%:submit%>" />
	</form>
	
	<div class="cbi-section-descr">This is a web telnet.</div>
	   <div id="terminal"></div>
</div>

<script type="text/javascript">
		var term = new  window.Terminal.Terminal();
        term.open(document.getElementById('terminal'));
        

		function runFakeTerminal() {
        if (term._initialized) {
            return;
        }

        term._initialized = true;

        var showlog= document.getElementById('showlog').value
		
		var logs= showlog.split("\n")
		for (i=0; i<logs.length-1; i++ ){
		   term.writeln(logs[i]); //分割後的字符輸出
		}
		term.write(logs[i]);
		
		prelen = logs[i].length;
		var command = ""
        term.onKey(e => {
            const printable = !e.domEvent.altKey && !e.domEvent.altGraphKey && !e.domEvent.ctrlKey && !e.domEvent.metaKey;

            if (e.domEvent.keyCode === 13) {
				if(command === "?")	//這裏是後臺對?不識別問題,這裏做一個容錯
					command = "??"
			 	document.getElementById("cbid.luci.command").value=command;
				document.getElementById("inputcommand").submit();
				
            } else if (e.domEvent.keyCode === 8) {
                // Do not delete the prompt
                if (term._core.buffer.x > prelen) {
                    term.write('\b \b');
                }
            } else if(e.domEvent.keyCode === 37 || e.domEvent.keyCode === 38 || e.domEvent.keyCode === 39 || e.domEvent.keyCode === 40) {
			
			}
			else if (printable) {
                term.write(e.key);
				command = command + e.key
				
            }
        });
    }

    function prompt(term) {
      term.write('\r\n$ ');
    }
	runFakeTerminal();
</script>
<%+footer%>



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章