orzAsio代码研究一些笔记,感谢nous

来源:互联网 发布:淘宝的需求分析 编辑:程序博客网 时间:2024/05/07 20:36

(1) 组包发送数据

if (_ascId.cbId.clr == 0 && _ascId.cbId.id == 0)

{

return true;

}

IPacketShrPtr pk = allocPacket();

if (!ORZ_NET_DYN_ASSERT(pk != 0, __FILE__, __LINE__, 0))

{

return false;

}

 

pk->setMsg((int32)AMT_ASYNC_CALLBACK);

pk->writePtr((void*)_ascId.apId);

pk->writeUint(_ascId.cbId.clr);

pk->writeUint(_ascId.cbId.id);

pk->writeUshort((uint16)_arg.size());

pk->put(_arg.c_str(), _arg.size());

sSvcMgr(ServiceManager)->send(_ascId.snPxy, pk);

/////////////////////////////////////////////////////////

 

(2)OrzAsio的精华,无锁hash表

 

"这种编程思想总结为:

#1 使用某种方式(例如哈希)对数据分组;

#2 每个分组独自使用互斥锁,保证同一时间最多只有一个线程可以访问组内的数据;

#3 当有线程来访问数据的时候,根据分组方式中的分组算法选择出对应的组,来进行具

体的操作;

#4 分组算法本身最好是wait-free的;(例如key % maxGroup == groupid)。":

举例:

//////////////////////////////////////////////////////

线程个数==哈希表中的桶的个数,可以做到无锁访问

 

m_threadPool->post((uint32)m_clients.hash(arc2s.nickname),  // 算出client所在的线程id

boost::bind(&ClientMgr::reg, this, arc2s.nickname, arc2s.password, arc2s.gender, _ascId));

 

void ClientMgr::reg(const std::string& _nn, const std::string& _pwd, bool _gdr, AsrpcCallId _ascId)

{

// 由于之前hash的处理,这里无需加锁

ClientPtr cln = m_clients.get(_nn, _nn, false); // 先根据_nn,算出hash值,从而得到具体的map

std::string err("ok");

if (cln)

{

err = "nickname already exist";

}

else

{

cln.reset(new Client(_nn, _pwd, _gdr));

if (m_clients.add(_nn, cln, _nn, false) != 0) // 因为一个线程对应一个hash分组,所以同一个时间只有一个线程访问这个分组故无需加锁

{

err = "internal error";

}

}

 

if (err == "ok")

{

printf("Register success!Nickname: %s/n", _nn.c_str());

}

else

{

printf("Register failed!/n");

}

 

ARegister_s2c ars2c(err);

ABase ab(MT_REG, ars2c.serialize2str());

callback(_ascId, ab.serialize2str());

}

 

 

(3) 多进程或者一些多线程经验总结

逻辑线程变成多个?这样做会给程序带来太多麻烦,从项目管理的角度来讲你不能要求每个写逻辑程序的人都精通多线程设计。而不精通,就容易写出不稳定的多线程程序。

所以最明智的做法是写成单线程多进程。

免费打工仔 发表于 2010-2-23 13:00

[i=s] 本帖最后由 免费打工仔 于 2010-2-23 13:01 编辑 [/i]

 

我不是Asio的作者,纯粹过来讨论。

多进程虽然是好的解决方案之一,但是因为在不同操作系统中进程的消耗和抢占方式不相同。在Windows类系列操作系统中创建进程和销毁进程都是比较消耗效率的。所以如果采用多进程方案的话,可能只限制在类Unix系统下比较好。

taotaotao 发表于 2010-2-23 14:27

 

我的意思是网络收发线程多个,逻辑处理线程只有一个就足够了。其实性能的瓶胫应该就是在网络收发,MMO的最大消耗资源的地方在组播和广播,只要逻辑处理没有阻塞行为,事实上只要把读写DB,读写文件等分离出来到一个线程或者一个进程,它就不会是阻塞的。

网络模块的最大瓶胫的带宽和网卡速度,其实主要还是要对广播的组播的控制,比如同一个地方1000个玩家,怎么组播移动数据?

Nous 发表于 2010-2-24 11:01

 

你完全可以设计逻辑线程是单线程的,这个取决于你怎么用OrzAsio(例如用队列从OrzAsio的多线程环境下传递网络消息到你的逻辑线程),OrzAsio中提到的多线程编程思想并没有强制使用者必须这么做。

 

另外,mmo广播的确是性能的瓶颈,现在只能在设计层面去尝试解决,例如,同一个地方1000个万家的话,说明人群非常密集,那么现实生活当中的这样的情况,会发生什么情况?那就是一堆人挤在一起,乱糟糟,你一个人接收到的声音、看到的情况都是模糊不清的,因为人太多,而且肯定会漏掉很多信息,其中最能清晰完整的辨识的消息就是离你最近的那些——那么还原到mmo中,我们在这种情况下,完全没有必要对所有1000个万家进行广播,而且根据人群密度和里玩家的距离,进行分层,越远的玩家,广播消息的频率就越低,只要保证同时广播的人数在一定范围内,服务器就可以接受。

Nous 发表于 2010-2-24 11:22

 

[i=s] 本帖最后由 Nous 于 2010-2-24 11:27 编辑 [/i]

 

还有,关于多线程开发方面,我个人有两种模型,其一是顶楼的方式;

 

另外一种是异步回调,这种方式下,没有显示的锁,所有的信息传递和api等的使用,都是异步的,基于队列,一块数据唯一绑定一个线程,这个线程除了操作它专属的数据外,不可操作其他数据,尤其是其它线程得数据。你可以理解为多进程的方式;

 

第二种方式的缺点是程序编写模式有些复杂(异步,大部分函数返回都要求用回调函数),但是多线程方面是绝对安全的,不会有诸如死锁、共享数据同时写入造成的崩溃、数据不同步等传统多线程下的问题。 

////

 

(4) 线程锁

 

//! 线程锁类

/*!

 */note 线程安全

 */note 可以自由的选择何种锁定方式(构造函数锁定、析构函数解锁,还是用成员函数加锁和解锁)

 example:

 /code

#1 情况1

boost::shared_mutexmutex;

{

DynShrLock lock(mutex);// 默认情况下,这里已经加锁,而且这种情况下选择的方式就是构造函数锁定、析构函数解锁;

// 用户只有在lock被销毁和手动调用lock.unlock()来解锁

lock.unlock();// 手动调用解锁,但是这种情况下lock.lock();无效(即无法手动加锁)

}

// lock被销毁,同时执行解锁,但由于之前手动调用了解锁,所以这里"will nothing happen"

 

#2 情况2

boost::shared_mutexmutex;

{

DynShrLock lock(mutex, DynShrLock::LT_DELAY);// 这里并没有加锁,这种情况下选择的方式是用户需要用成员函数来手动加锁和解锁;

lock.lock();// 手动加锁

}

// lock被销毁,但是由于模式为"DynShrLock::LT_DELAY",所以并不会自动解锁!

 

 

 

(4) 获取客户端ip地址

 

这个地址就是客户端的ip地址

 

ISession有一个方法,ISession::getSocket

 

取得ISocket指针,请将其根据ISocket::type来(TCP、UDP、SSL等)用dynamic_cast<>转换为对应的 Socket(TcpSocket,UdpSocket等)。

 

下面用TcpSocket举例:

 

使用TcpSocket::socket()取得boost::asio::ip::tcp::socket指针,然后用

 

boost::asio::ip::tcp::socket::remote_endpoint 方法取得远端endpoint:

 

boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint();

 

然后用boost::asio::ip::tcp::endpoint::address()方法取得ip地址类,

 

最后用boost::asio::ip::address::to_string()方法取得std::string类型的ip地址

 

 

 

这些步骤中涉及的boost::asio的api在这里查阅:

 

http://www.boost.org/doc/libs/1_ … asio/reference.html

 

ISession* session = _snPxy.getSession();

std::string ip;

int port = 0;

if( SKTP_TCP == _snPxy.getSocketType() )

{

        TcpSocket* sock = dynamic_cast<TcpSocket*>(session->getSocket());

        boost::asio::ip::tcp::socket* sc = sock->socket();

        boost::asio::ip::tcp::endpoint endpoint = sc->remote_endpoint();

        boost::asio::ip::address addr;

        addr = endpoint.address();

        ip = addr.to_string();

        port = endpoint.port();

}