OldJiang.com

浩毛的博客

OldJiang.com
posts - 14, comments - 81, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

置顶随笔

Stool - Easily switch VPN server for  Mac 发布

软件主页:www.oldjiang.com

主要功能:
1.方便管理大量VPN服务器和账户
2.批量检测VPN服务器速度
3.方便的切换VPN连接,并自动监测VPN连接状态.
4.支持L2TP和PPTP.
完全免费,欢迎大家使用.
下一步:计划加入状态栏支持.

版本 1.0
下载地址: Download

截图:







posted @ 2014-12-10 12:34 浩毛 阅读(1629) | 评论 (0)编辑 收藏

2014年12月10日

Stool - Easily switch VPN server for  Mac 发布

软件主页:www.oldjiang.com

主要功能:
1.方便管理大量VPN服务器和账户
2.批量检测VPN服务器速度
3.方便的切换VPN连接,并自动监测VPN连接状态.
4.支持L2TP和PPTP.
完全免费,欢迎大家使用.
下一步:计划加入状态栏支持.

版本 1.0
下载地址: Download

截图:







posted @ 2014-12-10 12:34 浩毛 阅读(1629) | 评论 (0)编辑 收藏

2011年8月11日

weak table是什么意思,建议不要看中文翻译的,我以前看了半天还是没懂啥子意思.

lua 手册原文是这样解释的:
A weak table is a table whose elements are weak references. A weak reference is ignored by the garbage collector. In other words, if the only references to an object are weak references, then the garbage collector will collect this object.
A weak table can have weak keys, weak values, or both. A table with weak keys allows the collection of its keys, but prevents the collection of its values. A table with both weak keys and weak values allows the collection of both keys and values. In any case, if either the key or the value is collected, the whole pair is removed from the table. The weakness of a table is controlled by the __mode field of its metatable. If the __mode field is a string containing the character 'k', the keys in the table are weak. If __mode contains 'v', the values in the table are weak.
After you use a table as a metatable, you should not change the value of its __mode field. Otherwise, the weak behavior of the tables controlled by this metatable is undefined.

在lua中,像table,userdata,function这些类型的值都是引用传递,通过引用计数来判断是否收掉对象,而弱引用(weak reference)会被垃圾回收器忽略.weak table就是它的元素是弱引用的,一个元素(键值对)可能键是弱引用,也可能值是弱引用的,也可能都是弱引用, 这个特性是通过弱表的metatable的__mode的值来设置的,特别有意思的是,当弱表中一个键值对,它的键或值关联(引用/指向)的那个对象被垃圾回收器回收的时候,这个键值对会从弱表中被自动删除掉.这是个很重要的特点.

那么弱表到底有什么用呢? 在lua的wiki中有一篇使用userdata的例子 ,其中就很巧妙的用到了弱表,原文地址 http://lua-users.org/wiki/CppBindingWithLunar
这篇文章介绍了如何通过userdata绑定c++对象到脚本中
fulluserdata能够设置metatable,也就能模拟出对象的效果出来,对一个C++的类的对象实例来说,push到脚本中,一般是创建了一个userdata,文章中用弱表避免了同一个对象实例(指针) push到脚本中,多次创建userdata的问题.
换句话来说,如果C++对象的生存周期是靠lua的垃圾回收来控制的话(userdata被回收时,调用元表的__gc方法,__gc方法中析构c++对象),一个C++对象只能有一个唯一的userdata. 在userdata的metatable中创建一个值是弱引用的弱表,用C++对象指针做键,每次push c++对象的时候,就去用指针值查弱表,如果有,就push那个userdata,没有就创建,  同时,当userdata是被弱引用的,当被垃圾回收掉的时候,弱表中它所在的键值对自动被销毁了. 



posted @ 2011-08-11 14:33 浩毛 阅读(7013) | 评论 (0)编辑 收藏

最近做了个DEMO,飞机追飞机的.
利用AOI模块产生的enterAOI,LeaveAOI事件 触发NPC追随玩家

顺便也测试下服务器实体的欧拉角计算


先运行RegionApp.exe
然后再运行ClientDemo

WSAD 前后左右 J上K下

客户端做的很简单..., 支持多开...

下载DEMO

posted @ 2011-08-11 11:22 浩毛 阅读(2940) | 评论 (0)编辑 收藏

2011年6月20日

最近在优化游戏服务器的AOI(area of interest)部分,位置有关的游戏实体一般都有一个视野或关心的范围,
当其他实体进出某个实体的这个范围的时候,就会触发leaveAOI或enterAOI事件,并维护一份AOI 实体列表。
我们来考虑最简单的实现,假设区域R中有1000个Entity,当某个entity位置发生变化时,需要计算entity的AOI事件和列表,伪代码如下:

function onEntityMove(who)   
  for entity in entities do
    if who <> entity then
        计算who和entity之间的距离
        如果who移动前entity在who的AOI范围内,且现在在范围外
           触发who.onLeaveAOI(entity)
        如果who移动前entity在who的AOI范围外,且现在在范围内
           触发who.onEnterAOI(entity) 
         如果who移动前在 entity的AOI范围内,且现在在范围外  
          触发entity.onLeaveAOI(who)
          如果who移动前在 entity的AOI范围外,且现在在 范围内
           触发entity.onEntityAOI(who)
      end
  end
end
每次一个实体移动一次位置就要遍历1000个实体来计算,这 样做显然不行,效率太低了,
那么就需要引入场景管理,将区域R分成n个格子,每个格子维护一个实体链表,entity移动时,只遍历它所在的格子和周围的8个格子的实体链表,
再优化下,可以加入AOI圆和格子的碰撞检查,9个格子中再去掉没有相交的格子。。。等等
也有用四叉树来进行场景管理的。

还有些方案更简单,直接是画格子,按以实体为中心的九个格子进行位置广播, 实体从一个格子移动到另外的格子时触发事体。。好处是计算量简单,缺点是带宽占用大

我上面的方案都试过了,效率和带宽占用都不理想,最近终于弄出一个新的方案,现在的AOI计算量是我们服务器以前计算量的1/40-1/80,由于涉及到公司的保密制度,不便细说,上几个测试的抓图:

机器配置:win7 ,T5870 inter双核2G,2G内存
20个entity 随机运动计算一次所有entity AOI的时间在0.02毫秒左右:

220个实体,选择的实体AOI范围里有68个实体:

4000个实体,选择的实体的AOI区域里有465个实体

AOIDemo.exe 下载

posted @ 2011-06-20 01:25 浩毛 阅读(17932) | 评论 (23)编辑 收藏

2010年12月29日

SOA:Service-oriented architecture
最近在做游戏服务器引擎,查了不少有关SOA的设计方面的资料。发现JAVA世界有相当多的技术标准。
其中比较好的是OSGi (http://www.osgi.org/About/WhatIsOSGi),OSGi技术是面向Java的动态模型系统。OSGi服务平台向Java提供服务,这些服务使Java成为软件集成和软件开发的首选环境。Java提供在多个平台支持产品的可移植性。OSGi技术提供允许应用程序使用精炼、可重用和可协作的组件构建的标准化原语。这些组件能够组装进一个应用和部署中。
参考它的思路,结合C++语言的特点,我将一个服务器基础框架设计成了这样一种:

一个服务器应用,由多个组件组成,组件物理上可以是动态库,也可以是EXE里一段实际的代码,每个组件可以向ApplicationFramework注册多个服务(比如日志服务,网络服务等)。
ApplicationFramework启动时,通过配置文件,动态加载和启动组件,读取服务的可配置属性。Framework触发IComponentActivator的OnStart() 和OnStop()事件,在Start事件中,可以注册组件自己的服务,也可以加入一个服务监听器来监听关心的服务的注册,注销等事件。
我随便画了几个大概的接口:
  

posted @ 2010-12-29 00:31 浩毛 阅读(2724) | 评论 (1)编辑 收藏

2010年10月15日

替换^M字符
在Linux下使用vi来查看一些在Windows下创建的文本文件,有时会发现在行尾有一些“^M”。有几种方法可以处理。

1.使用dos2unix命令。一般的分发版本中都带有这个小工具(如果没有可以根据下面的连接去下载),使用起来很方便:
$ dos2unix myfile.txt
上面的命令会去掉行尾的^M。

2.使用vi的替换功能。启动vi,进入命令模式,输入以下命令:
:%s/^M$//g # 去掉行尾的^M。

:%s/^M//g # 去掉所有的^M。

:%s/^M/[ctrl-v]+[enter]/g # 将^M替换成回车。

:%s/^M/\r/g # 将^M替换成回车。

3.使用sed命令。和vi的用法相似:
$ sed -e ‘s/^M/\n/g’ myfile.txt

注意:这里的“^M”要使用“CTRL-V CTRL-M”生成,而不是直接键入“^M”。

转自:http://hi.baidu.com/mofeis/blog/item/23c7b2fb92dc97234e4aea6d.html

posted @ 2010-10-15 11:28 浩毛 阅读(2437) | 评论 (0)编辑 收藏

2010年8月30日

    在游戏服务器中,处理玩家登陆需要向数据库查询玩家的账号和密码,玩家上线和下线需要对玩家的角色数据从数据库中读取和保存。可以说,相对于游戏逻辑处理来说,数据库操作是一种相对很慢的操作,即便你通过使用多个线程多个数据库连接来提高数据库操作的处理能力,但是,在高并发高负载的服务器应用中,这样仍然会是相当的负载瓶颈。设想这样一种设计方案,见下图:

    在大量玩家登陆游戏服务器时,由于有大量的数据库访问请求,即便是有自己实现的CACHE机制,还是会导致服务器耗尽所有的逻辑线程资源,服务器的处理能力将降低成DBMS的处理能力。
    
     为了不阻塞逻辑线程,可以采用异步数据库访问的方式,即数据库操作请求提交给专门的数据库处理线程池,然后逻辑线程不再等待数据库处理结果,继续处理其他,不再阻塞在这里。
     抽象的来看,对于一个需要持久化的游戏对象来说,可以考虑它有2个方法,读取和保存。那么我们抽象一个DBO接口:
   

struct IDbo
{
    
virtual bool SaveToDB(DB*)=0;
    
virtual bool LoadFromDB(DB*)=0;
}
;
   
     然后把设计方案改成下面这种:

 

     改成数据库异步处理后,在想想现在的游戏数据的保存机制应该是怎样改进的,为了保障数据安全,我们希望不只是玩家下线的时候才会保存玩家数据,而是希望每隔一段时间统一保存所有在线玩家的数据,那么,可以考虑这样的思路:假设我们有一个GAMEDB服务器,GAMEDB缓存了所有在线玩家的角色数据,每到保存时间,GAMEDB就将所有在线玩家的数据(DBO)的副本都统一提交给DB线程池,让它保存数据,提交的过程很快,提交完后,GAMEDB的逻辑线程仍能继续处理游戏服务器的更新和读取CACHE的请求。为什么要保存副本呢,DB线程的执行保存队列的过程也许很耗时,但是队列中的数据都是GAMEDB提交DBO那个时刻的数据,这样就能保证玩家的游戏数据的完整性。
      当然,我这里提的这只是个思路,这里面还有很多细节没有讨论,例如如果DB线程池正在保存九点钟时刻保存的数据,到了十点钟新的保存时刻时,DB线程池还没保存完九点钟时刻的DBO副本队列,这时应该怎么处理;DBO对象的划分粒度的问题;DBO队列的优先级的问题等等。

     PS:这篇文章里的架构其实就是一个GAMEDB服务器,里面的逻辑处理就是GAMEDB的逻辑处理。你可以把这篇文章理解成:一个GAMEDB服务器 的实现思路。。。

posted @ 2010-08-30 11:35 浩毛 阅读(8444) | 评论 (12)编辑 收藏

2010年7月29日

编辑~/.vimrc 加入以下代码

 1 autocmd BufNewFile *.[ch],*.hpp,*.cpp exec ":call SetTitle()" 
 2 
 3 "加入注释
 4 func SetComment()
 5     call setline(1,"/*==============================================================="
 6     call append(line("."),   "*   Copyright (C) ".strftime("%Y")." All rights reserved.")
 7     call append(line(".")+1"*   "
 8     call append(line(".")+2"*   文件名称:".expand("%:t")) 
 9     call append(line(".")+3"*   创 建 者:蒋浩")
10     call append(line(".")+4"*   创建日期:".strftime("%Y年%m月%d日")) 
11     call append(line(".")+5"*   描    述:"
12     call append(line(".")+6"*")
13     call append(line(".")+7"*   更新日志:"
14     call append(line(".")+8"*"
15     call append(line(".")+9"================================================================*/"
16 endfunc
17 
18 "定义函数SetTitle,自动插入文件头 
19 func SetTitle()
20     call SetComment()
21     if expand("%:e"== 'hpp' 
22  call append(line(".")+10"#ifndef _".toupper(expand("%:t:r"))."_H"
23  call append(line(".")+11"#define _".toupper(expand("%:t:r"))."_H"
24  call append(line(".")+12"#ifdef __cplusplus"
25  call append(line(".")+13"extern \"C\""
26  call append(line(".")+14"{"
27  call append(line(".")+15"#endif"
28  call append(line(".")+16""
29  call append(line(".")+17"#ifdef __cplusplus"
30  call append(line(".")+18"}"
31  call append(line(".")+19"#endif"
32  call append(line(".")+20"#endif //".toupper(expand("%:t:r"))."_H"
33     elseif expand("%:e"== 'h' 
34  call append(line(".")+10"#pragma once"
35     elseif &filetype == 'c' 
36  call append(line(".")+10,"#include \"".expand("%:t:r").".h\""
37     elseif &filetype == 'cpp' 
38  call append(line(".")+10"#include \"".expand("%:t:r").".h\""
39     endif
40 endfunc
41 


 

posted @ 2010-07-29 12:26 浩毛 阅读(7699) | 评论 (1)编辑 收藏

2010年7月6日

一个典型的游戏服务器设计中,一般都是用的多线程,服务器中一般运行两类线程,N个SOCKET IO线程,1个逻辑线程,
IO线程接受客户端发来的信息,通过消息队列发送给逻辑线程处理后,再发送消息给客户端,发送消息这里一般是IO线程处理实际发送。

其实我认为,如果逻辑线程都是消耗的CPU运算资源的话,服务器完全采用单线程的方式来做。

首先,我们看IO处理,基本就是数据入队、出队,send、recv操作,作为服务器的SOCKET处理一般都是异步SOCKET,也就是说,send、recv操作只是将信息copy到socket底层的发送接收缓冲区去了,不存在IO堵塞的问题。

然后,我们再来看逻辑处理,前面已经说了,采用单线程的前提是逻辑处理只是消耗CPU运算资源,那么,不管你开几个线程,对单核的CPU来说,它的处理速度就是这么多,并不会因为你线程开的越多,就处理的越快。

因此我们可不可以这样说呢,在单核机器上,只消耗CPU运算的服务,多线程并不比单线程能提高多少效率。

接下来,我们再讨论下多核的情况,你肯定要想,我这台服务器是4个双核CPU,就只跑一个单线程的服务器不是亏死了,多线程多好,我开8个线程,就能很好的利用我的机器啦。是啊,我也觉得这样很好,不过在LINUX、UNIX下,对线程的支持并不像WINDOWS下那么好,LINUX、UNIX下一般都是用LWP(轻量级进程)的方式来支持多线程程序的,Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,一定程度上也弥补了这一缺陷。同时,滥用多线程也会造成不必要的上下文切换,不必要的同步机制的引入(如pthread_mutex),让程序频繁的在内核和用户间频繁切换。另外,从开发角度来看,单线程开发比多线程环境开发更不容易出错和更加健壮。

在游戏服务器架构中,为了提高玩家在线人数,实现负载均衡,现在一般都是采用分布式的多进程服务器集群的方式,我们来看看服务器集群中,每个服务进程是采用多线程的方式还是单线程的方式好呢?我觉得,对于有慢速IO访问的需求的应用进程,多线程肯定比单线程好,最典型的情况就是数据库访问这块,完全可以采用N个DB线程,一个逻辑线程的架构,而对只是消耗CPU运算资源的应用进程,尽量单线程就行了,如果觉得单线程负载不行的话,完全可以分成多个进程来跑。。

以上只是我自己的一些看法,表达有限,欢迎指正。。。

posted @ 2010-07-06 00:06 浩毛 阅读(9734) | 评论 (13)编辑 收藏

2010年7月5日

    boost里的program_options提供程序员一种方便的命令行和配置文件进行程序选项设置的方法。
    其文档例子中有如下代码:
   
1 using namespace boost::program_options;
2 //声明需要的选项
3 options_description desc("Allowed options");
4 desc.add_options()
5         ("help,h""produce help message")
6         ("person,p", value<string>()->default_value("world"), "who");

    看第4到6行,是不是感觉很怪?这种方式体现了函数式编程中最大的特点:函数是一类值,引用资料来说,所谓“函数是一类值(First Class Value)”指的是函数和值是同等的概念,一个函数可以作为另外一个函数的参数,也可以作为值使用。如果函数可以作为一类值使用,那么我们就可以写出一些函数,使得这些函数接受其它函数作为参数并返回另外一个函数。比如定义了f和g两个函数,用compose(f,g)的风格就可以生成另外一个函数,使得这个函数执行f(g(x))的操作,则可称compose为高阶函数(Higher-order Function)。

    program_options里的这种方式是怎么实现的呢?通过分析boost的源代码,我们自己来写个类似的实现看看:
     test.h   
 1 #pragma once
 2 
 3 #include <iostream>
 4 using namespace std;
 5 
 6 class Test;
 7 
 8 class Test_easy_init
 9 {
10 public:
11     Test_easy_init(Test* owner):m_owner(owner){}
12 
13     Test_easy_init & operator () (const char* name);
14     Test_easy_init & operator () (const char* name,int id);
15 private:
16     Test* m_owner;
17 };
18 
19 
20 class Test
21 {
22 public:
23     void add(const char* name);
24     void add(const char* name,int id);
25 
26     Test_easy_init add_some();
27 
28 };

test.cpp
 1 #include "test.h"
 2 
 3 Test_easy_init & Test_easy_init::operator () (const char* name,int id)
 4 {
 5 
 6     m_owner->add(name,id);
 7     return *this;
 8 }
 9 
10 
11 Test_easy_init & Test_easy_init::operator () (const char* name)
12 {
13 
14     m_owner->add(name);
15     return *this;
16 }
17 
18 Test_easy_init Test::add_some()
19 {
20     return Test_easy_init(this);
21 }
22 
23 
24 void Test::add(const char* name)
25 {
26     cout<<"add:"<<name<<endl;
27 }
28 
29 void Test::add(const char* name,int id)
30 {
31     cout<<"add:"<<name<<"-"<<id<<endl;
32 }

使用方式:
1 Test t1;
2 
3 t1.add_some()
4     ("hello",1)
5     ("no id")
6     ("hello2",2);

是不是很有意思。add_some()方法返回一个Test_easy_init类的对象,Test_easy_init类重载了操作符(),操作符()方法返回Test_easy_init类对象自身的引用。。

posted @ 2010-07-05 23:08 浩毛 阅读(1773) | 评论 (2)编辑 收藏

OldJiang.com