如何开发满足5万人同时在线的聊天室软件(急) ( 积分: 200 )

  • 主题发起人 主题发起人 star123456
  • 开始时间 开始时间
S

star123456

Unregistered / Unconfirmed
GUEST, unregistred user!
如题,要开发一个能容纳5万人同时在线,在不同的房间里聊天的软件
用什么技术实现?
请各位高手指点一下!!!!
 
转一篇文章,希望对设计思路有帮助

类似于QQ游戏百万人同时在线的服务器架构实现!

转帖------
作者:Blog


QQ游戏于前几日终于突破了百万人同时在线的关口,向着更为远大的目标迈进,这让其它众多传统的棋牌休闲游戏平台黯然失色,相比之下,联众似乎已经根本不是QQ的对手,因为QQ除了这100万的游戏在线人数外,它还拥有3亿多的注册量(当然很多是重复注册的)以及QQ聊天软件900万的同时在线率,我们已经可以预见未来由QQ构建起来的强大棋牌休闲游戏帝国。
那么,在技术上,QQ游戏到底是如何实现百万人同时在线并保持游戏高效率的呢?
事实上,针对于任何单一的网络服务器程序,其可承受的同时连接数目是有理论峰值的,通过C++中对TSocket的定义类型:word,我们可以判定这个连接理论峰值是65535,也就是说,你的单个服务器程序,最多可以承受6万多的用户同时连接。但是,在实际应用中,能达到一万人的同时连接并能保证正常的数据交换已经是很不容易了,通常这个值都在2000到5000之间,据说QQ的单台服务器同时连接数目也就是在这个值这间。
如果要实现2000到5000用户的单服务器同时在线,是不难的。在windows下,比较成熟的技术是采用IOCP--完成端口。与完成端口相关的资料在网上和CSDN论坛里有很多,感兴趣的朋友可以自己搜索一下。只要运用得当,一个完成端口服务器是完全可以达到2K到5K的同时在线量的。但,5K这样的数值离百万这样的数值实在相差太大了,所以,百万人的同时在线是单台服务器肯定无法实现的。
要实现百万人同时在线,首先要实现一个比较完善的完成端口服务器模型,这个模型要求至少可以承载2K到5K的同时在线率(当然,如果你MONEY多,你也可以只开发出最多允许100人在线的服务器)。在构建好了基本的完成端口服务器之后,就是有关服务器组的架构设计了。之所以说这是一个服务器组,是因为它绝不仅仅只是一台服务器,也绝不仅仅是只有一种类型的服务器。
简单地说,实现百万人同时在线的服务器模型应该是:登陆服务器+大厅服务器+房间服务器。当然,也可以是其它的模型,但其基本的思想是一样的。下面,我将逐一介绍这三类服务器的各自作用。
登陆服务器:一般情况下,我们会向玩家开放若干个公开的登陆服务器,就如QQ登陆时让你选择的从哪个QQ游戏服务器登陆一样,QQ登陆时让玩家选择的六个服务器入口实际上就是登陆服务器。登陆服务器主要完成负载平衡的作用。详细点说就是,在登陆服务器的背后,有N个大厅服务器,登陆服务器只是用于为当前的客户端连接选择其下一步应该连接到哪个大厅服务器,当登陆服务器为当前的客户端连接选择了一个合适的大厅服务器后,客户端开始根据登陆服务器提供的信息连接到相应的大厅上去,同时客户端断开与登陆服务器的连接,为其他玩家客户端连接登陆服务器腾出套接字资源。在设计登陆服务器时,至少应该有以下功能:N个大厅服务器的每一个大厅服务器都要与所有的登陆服务器保持连接,并实时地把本大厅服务器当前的同时在线人数通知给各个登陆服务器,这其中包括:用户进入时的同时在线人数增加信息以及用户退出时的同时在线人数减少信息。这里的各个大厅服务器同时在线人数信息就是登陆服务器为客户端选择某个大厅让其登陆的依据。举例来说,玩家A通过登陆服务器1连接到登陆服务器,登陆服务器开始为当前玩家在众多的大厅服务器中根据哪一个大厅服务器人数比较少来选择一个大厅,同时把这个大厅的连接IP和端口发给客户端,客户端收到这个IP和端口信息后,根据这个信息连接到此大厅,同时,客户端断开与登陆服务器之间的连接,这便是用户登陆过程中,在登陆服务器这一块的处理流程。
大厅服务器:大厅服务器,是普通玩家看不到的服务器,它的连接IP和端口信息是登陆服务器通知给客户端的。也就是说,在QQ游戏的本地文件中,具体的大厅服务器连接IP和端口信息是没有保存的。大厅服务器的主要作用是向玩家发送游戏房间列表信息,这些信息包括:每个游戏房间的类型,名称,在线人数,连接地址以及其它如游戏帮助文件URL的信息。从界面上看的话,大厅服务器就是我们输入用户名和密码并校验通过后进入的游戏房间列表界面。大厅服务器,主要有以下功能:一是向当前玩家广播各个游戏房间在线人数信息;二是提供游戏的版本以及下载地址信息;三是提供各个游戏房间服务器的连接IP和端口信息;四是提供游戏帮助的URL信息;五是提供其它游戏辅助功能。但在这众多的功能中,有一点是最为核心的,即:为玩家提供进入具体的游戏房间的通道,让玩家顺利进入其欲进入的游戏房间。玩家根据各个游戏房间在线人数,判定自己进入哪一个房间,然后双击服务器列表中的某个游戏房间后玩家开始进入游戏房间服务器。
游戏房间服务器:游戏房间服务器,具体地说就是如“斗地主1”,“斗地主2”这样的游戏房间。游戏房间服务器才是具体的负责执行游戏相关逻辑的服务器。这样的游戏逻辑分为两大类:一类是通用的游戏房间逻辑,如:进入房间,离开房间,进入桌子,离开桌子以及在房间内说话等;第二类是游戏桌子逻辑,这个就是各种不同类型游戏的主要区别之处了,比如斗地主中的叫地主或不叫地主的逻辑等,当然,游戏桌子逻辑里也包括有通用的各个游戏里都存在的游戏逻辑,比如在桌子内说话等。总之,游戏房间服务器才是真正负责执行游戏具体逻辑的服务器。
这里提到的三类服务器,我均采用的是完成端口模型,每个服务器最多连接数目是5000人,但是,我在游戏房间服务器上作了逻辑层的限定,最多只允许300人同时在线。其他两个服务器仍然允许最多5000人的同时在线。如果按照这样的结构来设计,那么要实现百万人的同时在线就应该是这样:首先是大厅,1000000/5000=200。也就是说,至少要200台大厅服务器,但通常情况下,考虑到实际使用时服务器的处理能力和负载情况,应该至少准备250台左右的大厅服务器程序。另外,具体的各种类型的游戏房间服务器需要多少,就要根据当前玩各种类型游戏的玩家数目分别计算了,比如斗地主最多是十万人同时在线,每台服务器最多允许300人同时在线,那么需要的斗地主服务器数目就应该不少于:100000/300=333,准备得充分一点,就要准备350台斗地主服务器。
除正常的玩家连接外,还要考虑到:
对于登陆服务器,会有250台大厅服务器连接到每个登陆服务器上,这是始终都要保持的连接;
而对于大厅服务器而言,如果仅仅有斗地主这一类的服务器,就要有350多个连接与各个大厅服务器始终保持着。所以从这一点看,我的结构在某些方面还存在着需要改进的地方,但核心思想是:尽快地提供用户登陆的速度,尽可能方便地让玩家进入游戏中。
 
那位能提供一下完成端口的例子代码呀
 
1台服务器肯定实现不了。
 
用udp加心跳处理 在用完成端口做 信息异步处理应该可以实现 20000 人单服务器
 
完成端口的例子,我已经在参考这个例子

unit IOCPServerSocket;
interface

uses
SysUtils, Classes, SyncObjs, Windows, Sockets, WinSock;

type
EIOCPError = Exception;
TCustomIOCPServer = class;

TIOCPClientSocketThread = class(TThread)
private
FIOCPServer: TCustomIOCPServer;
FClientSocket: TCustomIpClient;
protected
procedure Execute; override;
public
constructor Create(AIOCPServer: TCustomIOCPServer);
destructor Destroy; override;
end;

TIOCPServerSocketThread = class(TThread)
private
FIOCPServer: TCustomIOCPServer;
FSocket: TCustomTcpServer;
protected

procedure Execute; override;
public
constructor Create(AIOCPServer: TCustomIOCPServer);
destructor Destroy; override;
end;

TIOCPThreadStatusType =
(iocpActual, iocpCurrent, iocpClient);
TIOCPThreadStatusRec = packed record
CurThread: integer;
ActThread: integer;
ClientThread: integer;
end;

TErrorEvent = procedure(AE: Exception) of object;
TUpdateEvent = procedure(IOCPThreadStatus: TIOCPThreadStatusRec)
of object;
TProcessEvent = procedure(ASock: TCustomIpClient) of object;

TCustomIOCPServer = class(TComponent)
private
FServerSocketThread: TIOCPServerSocketThread;
FErrorException: EIOCPError;
FIOCPHandle: THandle;
FLocalHost: TSocketHost;
FLocalPort: TSocketPort;
FThreadPool: TList;
FThreadStatus: TIOCPThreadStatusRec;
FMaxThreadsInPool: integer;
FMinThreadsInPool: integer;
FTerminateEvent: TSimpleEvent;
FSvcTerminateEvent: TSimpleEvent;
FCriticalSection: TCriticalSection;
FIdleTimeOut: integer;
FOnErrorEvent: TErrorEvent;
FOnUpdateEvent: TUpdateEvent;
FOnProcessEvent: TProcessEvent;
function GetActive: Boolean;
procedure SetActive(const Value: Boolean);
procedure SetLocalHost(const Value: TSocketHost);
procedure SetLocalPort(const Value: TSocketPort);

procedure IncThreadStatus(AType: TIOCPThreadStatusType);
procedure DecThreadStatus(AType: TIOCPThreadStatusType);
procedure SetMaxThreadsInPool(const Value: integer);
procedure SetMinThreadsInPool(const Value: integer);
procedure SetIdleTimeOut(const Value: integer);

procedure SyncErros;
protected
procedure EnsureClosed;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

procedure WaitForTerminate;

procedure Open; virtual;
procedure Close; virtual;
procedure SyncUpdate;

property Active: Boolean
read GetActive write SetActive;
property IdleTimeOut: integer
read FIdleTimeOut write SetIdleTimeOut;
property LocalHost: TSocketHost
read FLocalHost write SetLocalHost;
property LocalPort: TSocketPort
read FLocalPort write SetLocalPort;
property MaxThreadsInPool: integer
read FMaxThreadsInPool write SetMaxThreadsInPool;
property MinThreadsInPool: integer
read FMinThreadsInPool write SetMinThreadsInPool;

property OnUpdate: TUpdateEvent
read FOnUpdateEvent write FOnUpdateEvent;
property OnProcess: TProcessEvent
read FOnProcessEvent write FOnProcessEvent;
property OnError: TErrorEvent
read FOnErrorEvent write FOnErrorEvent;
end;

TIOCPServer = class(TCustomIOCPServer)
published
property Active;
property IdleTimeOut;
property LocalHost;
property LocalPort;
property MaxThreadsInPool;
property MinThreadsInPool;

property OnUpdate;
property OnProcess;
property OnError;
end;

implementation

const
TERMINATE_THREAD = $7fffffff;

resourcestring
ERRIOCPCreation =
'Could not initialize IOCP connection';


{ TIOCPClientSocketThread }

constructor TIOCPClientSocketThread.Create(AIOCPServer: TCustomIOCPServer);
begin
inherited Create(False);
FreeOnTerminate := True;
FIOCPServer := AIOCPServer;
end;

destructor TIOCPClientSocketThread.Destroy;
begin
inherited Destroy;
end;

procedure TIOCPClientSocketThread.Execute;
var
Transfered: dword;
AClientSocket: TSocket;
OverlappedPtr: POverlapped;
Thread: TThread;
i: integer;

begin
with FIOCPServer do
begin
while not Terminated do
begin
if not GetQueuedCompletionStatus(FIOCPHandle, Transfered,
Cardinal(AClientSocket), OverlappedPtr, FIdleTimeOut + 500) then
begin
if GetLastError = WAIT_TIMEOUT then
begin
FCriticalSection.Enter;
try
if (FThreadStatus.CurThread > FMinThreadsInPool) then
Terminate;
finally
FCriticalSection.Leave;
end;
end else
begin
if (AClientSocket = TERMINATE_THREAD) then
begin

Terminate;
Break;
end;
IncThreadStatus(iocpActual);
DecThreadStatus(iocpClient);
Synchronize(SyncUpdate);
FCriticalSection.Enter;
try
if (FThreadStatus.CurThread < FMaxThreadsInPool) then
begin
if (FThreadStatus.ActThread = FThreadStatus.CurThread) then
begin
Thread := TIOCPClientSocketThread.Create(FIOCPServer);
FThreadPool.Add(Thread);
IncThreadStatus(iocpCurrent);
Synchronize(SyncUpdate);
end;
end;
finally
FCriticalSection.Leave;
end;
FClientSocket := TCustomIpClient.Create(nil);
with FClientSocket do
try
try
FServerSocketThread.FSocket.Accept(FClientSocket);
if Assigned(FOnProcessEvent) then
FOnProcessEvent(FClientSocket);
except
on E:Exception do
begin
FCriticalSection.Enter;
try

FErrorException := e;
finally
FCriticalSection.Leave;
end;
Synchronize(SyncErros);
end;
end;
finally
DecThreadStatus(iocpActual);
Close;
Synchronize(SyncUpdate);
FreeAndNil(FClientSocket);
end;
end;
end;
end;
FCriticalSection.Enter;
try
i := FThreadPool.IndexOf(Self);
if i >= 0 then
FThreadPool.Delete(i);
DecThreadStatus(iocpCurrent);
Synchronize(SyncUpdate);
if FThreadStatus.CurThread = 0 then
FTerminateEvent.SetEvent;
finally
FCriticalSection.Leave;
end;
end;

end;

{ TIOCPServerSocketThread }

constructor TIOCPServerSocketThread.Create(AIOCPServer: TCustomIOCPServer);
begin
inherited Create(False);
FreeOnTerminate := True;
FIOCPServer := AIOCPServer;
FSocket := TCustomTcpServer.Create(nil);
FSocket.BlockMode := bmBlocking;
end;

destructor TIOCPServerSocketThread.Destroy;
begin
FSocket.Free;
inherited Destroy;

end;

procedure TIOCPServerSocketThread.Execute;
var
ASocket: TSocket;
AClientSocket: TCustomIpClient;
i: integer;
begin
with FSocket do
begin
LocalHost := FIOCPServer.LocalHost;
LocalPort := FIOCPServer.LocalPort;
Open;
repeat
if WaitForData(5000) then
begin
AClientSocket := TCustomIpClient.Create(nil);
try
Accept(AClientSocket);
ASocket := AClientSocket.Handle;
finally
AClientSocket.Free;
end;
if ASocket <> INVALID_SOCKET then
begin
FIOCPServer.IncThreadStatus(iocpCurrent);
PostQueuedCompletionStatus(FIOCPServer.FIOCPHandle, 0,
Cardinal(ASocket), nil);
end;
end;
until Terminated;
Close;
FIOCPServer.FCriticalSection.Enter;
try
for i := (FIOCPServer.FThreadPool.Count-1) downto 0 do
PostQueuedCompletionStatus(FIOCPServer.FIOCPHandle, 0,
Cardinal(TERMINATE_THREAD), nil);
finally
FIOCPServer.FCriticalSection.Leave;
end;
WaitForSingleObject(FIOCPServer.FTerminateEvent.Handle, INFINITE);
end;

end;

{ TCustomIOCPServer }

procedure TCustomIOCPServer.Close;
begin
try
FServerSocketThread.FSocket.Close;
FServerSocketThread.Terminate;
FServerSocketThread.WaitFor;
finally
CloseHandle(FIOCPHandle);
FSvcTerminateEvent.SetEvent;
end;
end;

constructor TCustomIOCPServer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FIOCPHandle := 0;
FMinThreadsInPool := 4;
FMaxThreadsInPool := 10;
FCriticalSection := TCriticalSection.Create;
FTerminateEvent := TSimpleEvent.Create;
FSvcTerminateEvent:= TSimpleEvent.Create;
FThreadPool := TList.Create;
end;

procedure TCustomIOCPServer.DecThreadStatus(AType: TIOCPThreadStatusType);
begin
case AType of
iocpActual: InterlockedDecrement(FThreadStatus.ActThread);
iocpCurrent: InterlockedDecrement(FThreadStatus.CurThread);
iocpClient: InterlockedDecrement(FThreadStatus.ClientThread);
end;
end;

destructor TCustomIOCPServer.Destroy;
begin
FThreadPool.Free;
FSvcTerminateEvent.Free;
FTerminateEvent.Free;
FCriticalSection.Free;
inherited Destroy;

end;

procedure TCustomIOCPServer.EnsureClosed;
begin
if Active then
Close;
end;

function TCustomIOCPServer.GetActive: Boolean;
begin
Result := (FIOCPHandle <> 0);

end;

procedure TCustomIOCPServer.IncThreadStatus(AType: TIOCPThreadStatusType);
begin
case AType of
iocpActual: InterlockedIncrement(FThreadStatus.ActThread);
iocpCurrent: InterlockedIncrement(FThreadStatus.CurThread);
iocpClient: InterlockedIncrement(FThreadStatus.ClientThread);
end;
end;

procedure TCustomIOCPServer.Open;
var
AClientThread: TIOCPClientSocketThread;
ASystemInfo: TSystemInfo;
i: integer;
begin
if not (csLoading in ComponentState) and
not (csDesigning in ComponentState) then
begin
EnsureClosed;
GetSystemInfo(ASystemInfo);
FIOCPHandle := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0,
ASystemInfo.dwNumberOfProcessors);
if (FIOCPHandle = 0) then

raise EIOCPError.Create(ERRIOCPCreation);
try
for i := 0 to FMinThreadsInPool-1 do
begin
AClientThread := TIOCPClientSocketThread.Create(Self);
FThreadPool.Add(AClientThread);
IncThreadStatus(iocpCurrent);
end;
FServerSocketThread := TIOCPServerSocketThread.Create(Self);
except
EnsureClosed;
raise;
end;
end;

end;

procedure TCustomIOCPServer.SetActive(const Value: Boolean);
begin
if (Value <> Active) and
(not (csLoading in ComponentState) and
not (csDesigning in ComponentState)) then
begin
if Value then Open else Close;
end;

end;

procedure TCustomIOCPServer.SetIdleTimeOut(const Value: integer);
begin
EnsureClosed;
FIdleTimeOut := Value;
end;

procedure TCustomIOCPServer.SetLocalHost(const Value: TSocketHost);
begin
FLocalHost := Value;
end;


procedure TCustomIOCPServer.SetLocalPort(const Value: TSocketPort);
begin
FLocalPort := Value;
end;

procedure TCustomIOCPServer.SetMaxThreadsInPool(const Value: integer);
begin
EnsureClosed;
FMaxThreadsInPool := Value;
end;

procedure TCustomIOCPServer.SetMinThreadsInPool(const Value: integer);

begin
EnsureClosed;
FMinThreadsInPool := Value;
end;

procedure TCustomIOCPServer.SyncErros;
begin
if Assigned(FOnErrorEvent) then
FOnErrorEvent(FErrorException);
end;

procedure TCustomIOCPServer.SyncUpdate;
begin
if Assigned(FOnUpdateEvent) then
FOnUpdateEvent(FThreadStatus);

end;

procedure TCustomIOCPServer.WaitForTerminate;
begin
FSvcTerminateEvent.WaitFor(INFINITE)
end;

end.
 
谁有完成端口做的聊天室的例子呀?
完成端口我没用过,不会用呀!!!
 
不会用就学习啊
授人以鱼不如授人以渔
你要做好还是认真学一下理论吧,不然给你源码你也看不懂
 
我下载了一个完成端口的控件,但是没有源码 IOCPBST
谁有这个源码呀
 
这些公开代码的 IOCP 99% 一攻就死,完全没作用.
 
to 白河愁
对待攻击一般采用什么方法?
 
我说的攻击只是非法的数据包.
这些数据发送到服务器的话往往会造成死连接,
死连接多了服务器就会出错.

避免的方法就是使用一个可靠的完成端口控件.
你可以把你的服务器端公开一下,我攻击测试就知道这个完成端口有没有问题了.
 
我现在还没有找到带源码的完成端口控件
 
老实说, 5W 人在线绝对不建议用没有任何技术支持的免费控件.
 
我有一个聊天室软件:

qq:474482251
 
谁能给说一下,多人在线的聊天室,聊天内容都是由服务器转发,
还是由客户端直接发给房间里的其他人?
用tcp还是udp发送?
求高手给指点一下大概的思路呀
 
5万人同时在线,在不同的房间里聊天
1. 就算用IOCP单台SERVER,也不过承受几千的并发量而已.
2. 5万人同时在线,只不过是服务器端保持5万人在线的记录(数据)而已.并不是一台
SERVER连接5万用户.
3. 5万用户,一台登录服务器就算用IOCP模型也承受不了. 登录服务器肯定是集群.
4. 既然是聊天室, UDP的广播肯定是少不了的. 合理的划分房间的大小, 是网络性能的关
键.
5. 用户之间的密聊,和文件收发, 肯定是用P2P比较合适, 不占用服务器端资源.
6. 多台登录服务器的处理问题, 服务器端需要一定数量的登录服务器, 大厅服务器,房
间服务器. 服务器肯定是用集群的模式. 不要想着单一一台,二台就OK的.
7. 负载均衡是少不了的. 如何减轻每台SERVER的压力,方法有多种的, 最简单的办法就是
注册.固定的用户,分配固定的服务器为他处理.
8. 海量数据问题, 如果需要保存记录. 就会有海量数据问题. 分布式数据库是必须要的.
一二台数据库根本承受不了, 把用户信息,分散到不同的数据库中存放.
9. 再强调一点,登录集群服务器一般采用IOCP模型, 处理完用户登录之后,就会断开用户
连接.只保留用户的Session. 大厅服务器, 房间服务器,可能也会有多台SERVER完成.
大厅服务器也只是保留用户在线信息而已.并不和用户连接. 真正和用户连接的可能只
是一个房间服务器, 或者房间服务器的的代理服务器.这个服务器需要用IOCP模型,处
理用户大量的并发数据. 但经过层层的细分之后, 把百万大军,已经化整为零. 以排,
连为单位. 各伺其职, 有事向上级(服务器)反映.
10. 如此多的用户,注定了不可能为每个用户分配一个线程, IOCP的线程池. 也只是其收
发数据作用. 所有数据,一律进入堆栈. 所有数据,一律进入出堆栈. 由不同的线程去
完成. 所有的数据处理, 放在一起处理. 整个系统看上去, 有几道流水线同时作业
同时工作. 互相响应.
11. 程序就象一个忙碌的国家, 忙而不乱.各伺其职, 井井有条.

说了,也是空话, 看源码最能说明问题, 对分布式系统感兴趣的,可以加我聊聊.
 
小顶一下,顺便听听课 [:D]
 
列表服务器用来登陆大厅

然后寻找实际聊天用的服务器

基本就这原理了,没什么大不了的

凡事多想想,这么简单的问题以后就不要来问了!
 
必须要用每台机器高效线程池,多台机器可并联端口
 
后退
顶部