如何将他人产品变为自己的产品?--包装方案

概述

为了避免大家说我是标题党,先说明背景。前段时间,领导和我说了一个项目,需要开发一些无线设备。要求:

  • 传输距离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%>



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