`
裴小星
  • 浏览: 261313 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
8ccf5db2-0d60-335f-a337-3c30d2feabdb
Java NIO翻译
浏览量:27603
F3e939f0-dc16-3d6e-8c0b-3315c810fb91
PureJS开发过程详解
浏览量:72056
07a6d496-dc19-3c71-92cf-92edb5203cef
MongoDB Java ...
浏览量:62047
社区版块
存档分类
最新评论

OMToolkit: Web Server,Web Framework 及 Object-Orinted Database 的简单实现

阅读更多

概述

 

  最近在看一些NIO和concurrent的资料,写了一个小项目作为练习,看起来涉及面很广,但上实现方式很简单,整个项目共2179行。
  项目已经上传到Google Code,地址:http://code.google.com/p/oh-my-toolkit/。可以在Downloads页面下载已经发布的0.0.1版本的OMToolikit以及OMSimpleBlog(一个示例,仅254行),也可以通过SVN下载源码。

  OMToolkit除JDK 1.6之外不依赖于任何第三方包,本身包含了Web Server,Web Framework 和 Object-Orinted Database 三个部分, 以“Entity”概念作为提供给Web应用开发者的API。
  最近将连续发布一系列文章,重点说明实现思路,同时也总结学习所得。


使用示例

  首先以OMSimpleBlog为例,简要说明OMToolkit的使用方式。后续的系列文章将对这个例子进行更详细的说明。

 

 

  1.Entity建模
  Entity的属性分为两类,一类继承自OMField,包括StringField,DateField,Children等,这些属性将被持久化。
另一类为基本类型,如String,int等,这些属性实际上是视图属性,不会被持久化,但可以通过URL进行赋值,并在渲染对象时获取。
基于OMToolkit的Web应用必须有一个名为Database的Entity,OMSimpleBlog中,Database的建模方式如下:

public class Database extends Entity {
	private Children<User> userList;
	private References<Article> articleList;

	// Methods ...
}

 

  可以看到,Database包含了一个User列表和一个Article列表。Childern/Child与References/Reference的区别在于:
  1. 删除一个对象时将会级联地删除Child和Children(通过调用Entity.doDelete()),而以References/Reference方式引用的对象不会被删除。
  2. 从Children/Child中添加或移除移除一个对象时,这个对象将被持久化或删除,而References/Reference与持久化无关。

  或者说,References/Reference只是简单地表示引用关系,与对象持久化无关;而Childern/Child表示严格的父子关系,与对象持久化密切相关。

  接着看User的建模:

public class User extends Entity {
	private StringField name;
	private StringField password;
	private StringField blogName;
	private Children<Article> articleList;

	// View Attributes ...

	// Methods ...
}

  可以看到,user包含了name,password,blogName三个字符串型的Field,以及一个Article的列表。并且,User与Article之间是严格的父子关系。
  在这个简单的示例中,由于Blog的属性比较简单,只有blogName,因此直接合并到了User中;并且Blog中没有分类(Category),因此User直接与Article关联。
  如果Blog的属性比较复杂,并且带有分类,则User的建模方式可能是这样的:

public class User extends Entity {
	private StringField name;
	private StringField password;
	private Child<Blog> blog;
	private Children<Category> categoryList;

	// View Attributes ...

	// Methods ...
}

 
  这里需要说明的是*List这种命名方式,如果调用Entity的内置的doDelete(),doAdd()等方法,将会以这种默认的命名方式中父类中寻找相关的集合,然后将自己从这个集合中移除(或增加到集合中)。如果不以这种方式命名,那么就需要自己编写这些方法了。
  关于Child/Reference的延迟加载:当调用Child/Reference的get()方法时,所引用的对象才会真正地被加载,这也是一种性能上的优化。

  最后是Article的建模:

public class Article extends Entity {
	private DateField published;
	private StringField title;
	private TextField summary;
	private TextField content;
	private Reference<User> user;

	// View Attributes ...

	// Methods ...
}

  这里需要特别说明的是TextField类型:TextField类型实际上继承了Child<Text>,Text是特殊的Entity,这是为了利用延迟加载的特性;在参数解析和视图解析中对TextField进行了特殊处理,以保证使用上与StringField相似,同时还可以在视图中使用类似"${content.html}"的方法,将内容转为Html进行渲染(不过这个方法还有待完善)。

 

  2.Rest风格URL与参数传递

 

  举个例子:URL:http://localhost/User/save/name/aaa/password/bbb/blogName/ccc 将会创建一个User对象,将name赋值为aaa,将password赋值为bbb,将blogName赋值为ccc,并调用该对象的save()方法。当然,这种通过URL进行赋值的方式也可能带来安全隐患,尤其是当需要对对象进行更新并持久化时。因此,OMToolkit引入了更新选项(UpdateOptions)对这种赋值进行限制,这将在后面谈到。

  另外,如果URL中包含id,则对象会被自动获取(通常是从cache中clone一个对象);如果URL中包含parent的id,则parent也会被自动获取(否则认定parent为database),这在增删对象和显示特定parent的列表时非常有用。

 

  3.Session,Cookie,Request

 

  可以随意地在Entity中声明session和cookies并使用,方法如下:

public class User extends Entity {
	// Fields ...

	private Session session;
	private List<Cookie> cookies;

	// Other view attributes ...

	// Other methods ...	

	public String checkLogin() throws Exception {
		name.set(Cookie.get(cookies, "name"));
		password.set(Cookie.get(cookies, "password"));

		// Other codes ..
	}

	public String login() throws Exception {
		// Other codes ...

		if (remember) {
			cookies.add(new Cookie("name", name.get()));
			cookies.add(new Cookie("password", password.get()));
		}

		// Other codes ...
	}

	public String logout() throws Exception {
		session.set("userId", null);
		session.set("role", null);
		cookies.add(new Cookie("name", ""));
		cookies.add(new Cookie("password", ""));
		return toView("/Home/login");
	}

	// Annotations ...
	public String save() throws Exception {
		// Other codes ...

		session.set("role", "user");
		session.set("userId", getId());

		// Other codes ..
	}
	
	// Other methods ...
}

 

  事实上,在生成Entity时,OMToolkit将会自动检查Entity的属性,并对其中的session和cookies进行赋值(如果有的话)。

  Request对象可以通过Entity.getRequest()获取,但不推荐,因为实际上通过Fields和View Attributes就可以接收和设置参数了。


  4.权限控制与更新选项

 

  使用注解进行权限控制,并标明更新选项,方法如下:

public class User extends Entity {
	@Role("user")
	@UpdateOptions(toUpdate = "blogName[20]")
	public String update() throws Exception {
		// Codes ...
	}
}

   其中,Role注解可以放置在类或方法上,而UpdateOptions只能放置在方法上。

  Role的value为数组,@Role({"user", "admin"}) 会从session中取出key为"role"的对象(String类型,目前还不支持数组类型),看是否包含于允许列表中,若不包含,则转向登录页面。登录页面可以在配置文件(Cfg.cfg)中进行配置。

  UpdateOptions实际上是对输入URL进行检查,避免不合法的赋值,有toUpdate和allowEmpty两个属性。不合法的URL将被重定向到登陆页面。另外,在带有UpdateOptions的方法中获取对象时会申请互斥的更新锁,这是出于数据一致性的考虑,但实际上有潜在的性能问题,也容易导致死锁,是后续版本中需要优化的一个重点。


  5.事务控制与自动提交

 

  可以使用getTransaction()获取对事务的控制,OMToolkit在正常执行完一次操作时也会自动提交(有异常则rollback),对于没有添加@UpdateOptions注解的方法,将不会进行持久化或删除数据。这时transaction实际上只用于cache本次事务中获取的对象。

  示例如下:

@Role("user")
@UpdateOptions(toUpdate = "blogName[20]")
public String update() throws Exception {
	// Other Codes ...

	if (userId == null || (Long) userId != getId()) {
		getTransaction().rollback();
		return toView("/Home/login");
	}

	// Other Codes ...
}

 
  6.分页与排序


  Children/References的toList()方法将会按照id降序排列(即新创建的对象会排在前面),PageUtil.run(References<T>,Request)方法提供分页功能,写法如下:

pagedArticles = PageUtil.run(articleList, getRequest());

  该方法将自动按照URL中的pageIndex和pageSize进行分页和排序,排序的依据仍然是id降序。
  如果需要自定义的排序,可以通过Children/References的values()方法获取Child/Reference的集合,并将一个自定义的Comparator、pageIndex、pageSize一同传入ArrayUtil的toList(...)方法中。当然这种方式比较繁琐,在后续版本(varsion 0.0.3)中计划加入更便捷的排序和过滤API,实现类似下面的写法:

 

articleList.where("title like 'OMTookit%'")
	.orderBy("published desc")
	.toList(pageIndex, pageSize);  

 

  7.CRUD

 

  下面快速地浏览一下OMTookit中的CRUD(User类中的方法):

 

  列出用户

@Role("admin")
public String list() throws Exception {
	return super.list();
}

 

  这里直接使用了Entity的list()方法,Entity.list()实现如下:

public String list() throws Exception {
	return toView(PageUtil.run(doList(), request));
}

 

  获取用户(同时显示该用户的blog中的所有文章):

public String get() throws Exception {
	pagedArticles = PageUtil.run(articleList, getRequest());
	return toView();
}

   请求URL中包含了id,该对象将自动被获取,所以不需要编写获取对象的语句。


  注册新用户

@UpdateOptions(toUpdate = { "name[20]", "password[20]", "blogName[20]" })
public String save() throws Exception {
	if (exists()) {
		error = cfg("exists") + '\n';
		return toView("/Home/register");
	}

	doSave();
	session.set("role", "user");
	session.set("userId", getId());

	return toView("/Home/index");
}

  首先检查用户是否存在,若存在则设置错误信息并回到注册页面;doSave()方法会先在parent中找到"userList",再将自身添加到到列表中。之后在提交事务时将自动保存被创建的用户,并更新parent(即Database)。之后是session的设置,表示注册后自动登录。
  除去检查用户是否存在和session设置的代码,实际实现保存用户的只有doSave()这一句。


  更新用户(只更新博客名称):

@Role("user")
@UpdateOptions(toUpdate = "blogName[20]")
public String update() throws Exception {
	Object userId = session.get("userId");
	if (userId == null || (Long) userId != getId()) {
		getTransaction().rollback();
		return toView("/Home/login");
	}

	return action("edit");
}

  先检查用户的id是否与被修改的用户的id相同(防止非法修改他人的博客名称),然后返回到编辑页面。
  看起来好像没有更新语句,这是因为OMTookit将自动保存被更改过的Entity(对于带有@updateOptions注解的方法)。需要注意的是getTransaction().rollback()是必不可少的,否则对象仍然会被更新(因为对象被更改过了)。这是少数需要开发者手动控制事务的地方。看起来好像这种自动保存机制带来了麻烦,但在大多数情况下应该还是会方便开发者的。

 

  删除用户

 

	@Role("admin")
	@UpdateOptions
	public String delete() throws Exception {
		return super.delete();
	}

   删除一个Entity的同时会删除它的child和children。

 

  Entity本身有默认的CRUD实现,当然,大多数情况下还是需要开发者覆盖这些实现。

  需要注意的有三点:

  1. 如果在URL中指定id,则相应的对象将被自动获取
  2. 如果在URL中指定parent的id,则相应的parrent将被自动加载,否则认定parent为Database。
  3. 对于带有UpdateOptions注解的方法,OMTookit将自动保存被更新过的对象。

 

  如果所请求的方法不存在,将自动调用toView()方法。所以Article没有实现get()方法(Entity中也没有),因为实际上不需要。只要在URL中指定了id,对象就会被获取,然后调用toView()方法,渲染该Entity。


  8.视图渲染

 

  继承与包含

  以views/Home/index.html为例:

#extends /Master/home

#override header
<a href="/User/get/id/${userId}/pageSize/10">我的博客</a>
<a href="/User/edit/id/${userId}/pageSize/10">博客设置</a>
<a href="/User/logout">注销登录</a>

#override content
#include /Article/list/action/index/pageIndex/${pageIndex}/pageSize/10

   被继承的views/Master/home.html如下:

#extends /Master/master

#override title
博客首页

#override content
<div id="header">
	#abstract header
</div>
<h1>Oh, My Simple Blog!</h1>
#abstract content

  可以看到,#extends表示继承,views/Home/index.html继承了views/Master/home.html,而views/Master/home.html又继承了views/Master/master.html。#override表示覆盖,将会替换被继承视图的#abstract部分。
  #include表示包含,后面接上被包含的URL。
  需要注意的是,继承是静态的,而包含是动态的;即继承只是简单的替换,而包含则会在内部重新模拟一次请求。


  循环与分支

#loop var
...(block) 
#end

  有两种形式:不带参数和带一个参数,不带参数指对当前Model进行遍历,带参数则表示先获取该属性,再进行遍历。

#if var
...(block) 
#end 

  带一个参数,该参数的toString()方法返回"true"则解析语法块中的内容,否则不解析。

 

  变量解析

  解析的顺序是Model的get方法 -> Model属性 -> request -> session。

  如"${pageIndex}",会先查找当前Model的getPageIndex()方法,然后是pageIndex属性,接着是request.params().get("pageIndex"),最后是session.get("pageIndex") 。都为null则作为空字符串进行渲染。


  点操作符
  "." 解析顺序是 Model的get方法 -> Model属性
  如"content.html" 会先找content对象的getHtml()方法,然后找名为“html”的属性。


  9.配置文件


  有两类配置文件,一类是Cfg.cfg,实际上主要配置的是Web Server的参数,包括端口、缓冲区大小、线程池的线程数等。

  另一类是cfg文件夹下的文件,以Entity类的名称命名,如User.cfg,主要用于配置一些提示信息等;可以用Entity.cfg(String name, String... args)获取配置文件中的值。


  10.打包发布


  将Web应用打包为jar(eclipse导出时只需勾选src文件夹),放在空模板中(即OMToolkit_0_0_1_bin.rar,可以在Google Code项目主页下载), 注意根据打包文件的名称,修改Cfg.cfg中的jar属性,并将cfg、data、resources、views目录复制到空模板中,覆盖原来的文件夹。(0.0.3 版本中会增加Ant脚本进行打包)


实现思路

 

  1.Web Server的实现

  Server的实现主要位于包com.omc.server。主要借助了Java NIO和concurrent包中的线程池。
  网络信息的读写是单线程的,即每次从每个连接中读写一部分数据,而请求的处理则借助线程池。每次读写的数据量和线程池的线程数可以在Cfg.cfg中进行配置。
  Server类的核心代码:

public class Server {
	public void run() throws Exception {
		init();
		Selector selector = openSelector();
		while (true) {
			doSelect(selector);
		}
	}

	private Selector openSelector() throws Exception {
		Selector selector = Selector.open();
		ServerSocketChannel server = ServerSocketChannel.open();

		server.configureBlocking(false);
		SelectionKey key = server.register(selector, SelectionKey.OP_ACCEPT);
		key.attach(new Accepter(key));

		server.socket().bind(new InetSocketAddress(Cfg.port()));

		return selector;
	}

	private void doSelect(Selector selector) throws Exception {
		selector.select();
		Set<SelectionKey> selected = selector.selectedKeys();

		Iterator<SelectionKey> it = selected.iterator();
		while (it.hasNext()) {
			((OMRunnable) it.next().attachment()).run();
		}

		selected.clear();
	}
	
	// Other methods ...
}

 

  在openSelector()方法中,将一个Accepter类的对象附加到了ServerSocketChannel的key上,当key的状态为acceptable时,将被selector检索到,从而调用Accept对象的run()方法,以处理接收到的SocketChannel,Accepter的核心代码如下:

public class Accepter implements OMRunnable {
	// Other codes ...

	public void run() throws Exception {
		SocketChannel socket = accept();
		socket.configureBlocking(false);

		SelectionKey k = register(socket);
		k.attach(new Worker(k));
	}

	private SocketChannel accept() throws Exception {
		return ((ServerSocketChannel) key.channel()).accept();
	}

	private SelectionKey register(SocketChannel socket) throws Exception {
		return socket.register(key.selector(), SelectionKey.OP_READ);
	}
}

 

  将被接收到的SocketChannel也注册到selector上,这样一来,当SocketChannel的key的状态为readable时,也会被selector检索到,从而调用Worker的run()方法。
  Woker的实现较复杂,包含了Reading、Processing、Writing三个内部类,分别表示读取、处理、写回三种状态。Reading类每次读取一部分数据,读取完毕后创建一个Processing对象,并提交到线程池中(同时key的interest被设置为0,表示不关心任何操作);线程池在有线程空闲的情况下启动Processing对象的处理过程,处理完成后,将状态改为Writing(同时key的interest被设置为SelectionKey.OP_WRITE,表示只关心写操作)。


  2.Web Framework的实现
  Web Framework的部分主要位于com.omc.web包,实现了分发任务的控制器(Controller)、事务控制类(Transaction)、视图类(View)等。
  Controller类主要通过从URL中解析得到的信息,创建Entity对象,对该对象的属性进行赋值,并调用相应的方法处理请求,最后进行视图渲染。主要运用了Java的反射机制。

  Controller核心代码如下:

public class Controller {
	// Other codes ...

	public byte[] run() throws Exception {
		if (!AccessChecker.isAllow(req)) {
			return toLoin();
		}

		String res = doRun();
		List<Cookie> cookies = getCookies(entity);
		return response(res, cookies).getBytes();
	}

	public String doRun() throws Exception {
		Method method = req.method();
		UpdateOptions op = method.getAnnotation(UpdateOptions.class);
		Transaction transaction = loadEntity(op);
		try {
			Object res = method.invoke(entity);
			transaction.commit();
			return (String) res;
		} catch (Exception e) {
			transaction.rollback();
			e.printStackTrace();
			return "";
		}
	}

	// Other codes ...
}

  先检查权限、更新选项等,一旦发现非法访问,则重定向至登录页面;如果通过检查,则先加载Entity,然后用反射调用相应的方法,执行过程中没有错误则提交事务,否则回滚事务。

  View的核心代码如下:

public class View {
	// Other codes ...

	public static String render(Object model, String view, Request request)
			throws Exception {
		String content = read("views/" + view + ".html").replace("\r", "");
		return new View(model, content, request).render();
	}

	private View(Object model, String content, Request request) {
		this.model = model;
		this.content = content;
		this.req = request;
	}

	public String render() throws Exception {
		if (content.startsWith("#extends")) {
			content = doExtends(content);
			return new View(model, content, req).render();
		}

		lines = StringUtil.split(content, '\n');
		while (index < lines.size()) {
			readLine();
			if (s.startsWith("#if")) {
				branch();
			} else if (s.startsWith("#loop")) {
				loop();
			} else {
				parseLine();
			}
		}

		return result.toString();
	}

	// Other codes ...
}

  可以看到,View的构造函数是私有的,只能通过调用静态方法View.render(...)来渲染视图。
  首先检查"#extends",如果存在则对内容进行替换,然后重新构造View并调用render()方法。然后逐行解析,遇到"#if"则进行分支处理,遇到"#loop"则进行循环处理,否则进行一般的解析。

 

  3.Object-Orinted Database的实现

 

  OODB的部分主要位于com.omc.data包,主要实现类包括DataUtil和FieldUtil。例如,DataUtil.get(long id, boolean forUpdate)方法用于获取一个对象:

	public static Entity get(long id, boolean forUpdate) throws Exception {
		if (forUpdate) {
			while (locks.contains(id)) {}
			locks.add(id);
		}

		Entity entity = cache.get(id);
		return entity == null ? loadEntity(id) : entity;
	}

 

  loadEntity(long id)方法如下:

	private static Entity loadEntity(long id) throws Exception {
		Meta meta = Meta.get(id);
		Entity entity = meta.getEntity();

		loadFields(entity, meta);

		entity.setId(id);
		cache.add(entity);

		return FieldUtil.clone(entity);
	}

 

     loadFields(Entity entity, Meta meta)方法如下:

	private static void loadFields(Entity entity, Meta meta) throws Exception {
		long position = meta.getPosition();
		int size = meta.getSize();
		String content = FileUtil.read(DATA_FILE, position, size);
		parseFields(entity, content);
	}

 

  下面是对一些要点的说明:

 

  meta与data
  数据文件包括data/meta和data/data两个部分,分别包含了元数据(id,对象数据在data文件中的位置,Entity的类型)和对象数据。
  OMTookit启动时,加载所有的meta(这可能会导致内存占用过高,是一个需要优化的地方)。
  读取对象时,先从meta中读取对象数据所在位置,然后解析对象数据,生成对象。
  保存对象时,将对象按一个的规则序列化并保存到data文件中,同时在meta文件中记录数据。

 

  cache

  对于已经加载的数据,在一定时间(可以在Cfg.cfg中配置)内将存在于cache中,再次读取时只需要clone一份,而不需要从数据文件中读取。

 

  更新锁
  当读取的对象的目的是更新("forUpdate")时,将申请该对象的更新锁,这意味着将阻塞其他"forUpdate"方式的读取。这可能是一个潜在的性能瓶颈,是后续版本需要进行优化的一个地方。

 

 


版本计划

  偶数版本(0.0.2,0.0.4...)均为Bug修复与重构,奇数版本增加新功能。

 

  0.0.3 计划
  1. API改进,力求实现更为便捷的API
  2. 文件上传
  3. 404、500、502等错误页面
  4. 排序、筛选、全文搜索等功能
  5. 日志、性能、异常: 可以使用动态代理。OMField 和 Entity 也可以用动态代理加以改造。
  6. 视图引擎增强。

  7. XML、JSON支持。

  8. Ant打包脚本。

 

  0.0.5计划
  1. 数据存储性能优化(更新锁机制,多级Meta)

 

  更新锁需要改进,避免过多的互斥。
  meta全量加载可能导致性能问题。如果将meta本身也视为数据的话,实际上可以建立meta的meta,进而建立n层的meta,从而实现更小的加载量。

 

  2. 数据导入、导出、迁移工具。


  0.0.7计划
  1. Server性能优化。
  2. 多应用支持。目前一个Server只支持运行一个应用。

 

  每个小版本的周期约为1.5个月,大版本的周期约为1年。


系列文章发布计划

 

  1. OMSimpleBlog详细说明(3月17日)
  从零开始搭建OMSimpleBlog,主要分为用户注册与登录,文章发布与编辑,后台数据管理三个步骤。

 

  2. OMTookit Web Server 详细说明(3月19日)
  从零开始搭建Server,分为Server、Acceptpr、Worker的实现;Session、Cookie的处理。

 

  3. OMToolkit Web Framework 详细说明(3月21日)
  在前一部分的基础上,开发Web Framework,包括Controller的实现,Transaction的实现,以及View的实现。

 

  4. OMToolkit Object-Orinted Database 详细说明(3月24日)
  在前一部分的基础上,开发数据存储部分,包括meta data、对象的持久化、对象的读取、cache、更新锁等。

 

  5. 总结(3月26日)

12
10
分享到:
评论
4 楼 裴小星 2011-05-14  
key232323 写道
douyu2.0??

楼主真够有精力了,不过很支持下!

我下载下看看web server性能如何?

oodb这个,我觉得实用性太低。


现在只是玩具而已,确实不实用。
3 楼 key232323 2011-05-13  
douyu2.0??

楼主真够有精力了,不过很支持下!

我下载下看看web server性能如何?

oodb这个,我觉得实用性太低。
2 楼 cloud21 2011-03-16  
很不错呢,加油。
1 楼 裴小星 2011-03-15  
OMSimpleBlog的代码有点小问题,Home类忘记加@Ignore注解了。
这个注解的作用是说明该Entity不会被持久化,因此不需要加载parent等。
不会导致错误,但会稍稍影响性能。

Master/user.html中
${name}

最好写作
<p>${name}</p>

以免没有文章时和提示信息混在一行中。


现在更新锁是直接用
if (forUpdate) {
    while (locks.contains(id)) {}
    locks.add(id);
}

进行检查的,jdk concurrent包中有一些现成的锁,
但更理想的是不使用锁,而对不同的更新进行merge。

相关推荐

    CSharpGL:C#中的面向对象的OpenGL

    :green_apple: CSharpGL是纯Objective-Orinted OpenGL包装器,没有任何第三方支持。 它从OpenGL API和通用要求中抽象出概念(缓冲区,着色器,状态,矩阵,矢量,纹理,画布,场景,相机,光源,拾取,文本,GUI .....

    【图像压缩】 GUI矩阵的奇异值分解SVD灰色图像压缩【含Matlab源码 4359期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    node-v0.9.2-x86.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【尺寸检测】机器视觉图像目标尺寸测量【含Matlab源码 4087期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    【图像加密】双随机相位图像加密解密【含Matlab源码 4118期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    金融支付:浅析如何用定期资产设计活期产品.docx

    金融支付:浅析如何用定期资产设计活期产品.docx

    Excel模板个人简历文艺清新单页06.docx

    Excel模板个人简历文艺清新单页06.docx

    【图像重建】 POCS算法超分辨率图像重建(含PSNR)【含Matlab源码 4404期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    吹瓶转线清线(清场)记录表.xls

    吹瓶转线清线(清场)记录表.xls

    odis14.1安装包(专检5054B专用含驱动)

    支持win10,win11(21H2及以前版本)系统

    Modbus Slave version 9.3.2 Build 2156

    Modbus Slave version 9.3.2 Build 2156, modbus 协议从机,非常好用,包括32位与64位

    Excel模板个人简历稳重大气单页03.docx

    Excel模板个人简历稳重大气单页03.docx

    11记录控制程序.doc

    11记录控制程序.doc

    【图像边缘检测】自适应阈值的八方向和四方向sobel图像边缘检测【含Matlab源码 2058期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    第一步安装.zip

    第一步安装.zip

    node-v0.9.0-x64.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【图像融合】红外与可见光图像融合与配准【含Matlab源码 4214期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    【图像去噪】 GUI中值+均值+维纳+小波滤波图像去噪(含PSNR)【含Matlab源码 753期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描视频QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    C风险心理承受能力测试20210603.docx

    C风险心理承受能力测试20210603.docx

Global site tag (gtag.js) - Google Analytics