传奇这款游戏,一直对我的影响很大。当年为了玩传奇,逃课,被老师叫过N次家长。言归正传,网上有很多源码,当然了,都是delphi的。并且很多源码还不全,

由于一直学习的c、c++。delphi还真不懂。无奈硬着头皮上。好了。废话不多说。开始。

登录网关,负责游戏最开始的登录处理(与账户服务器LoginSvr通讯)。验证登录器输入的账户密码是否正确。

界面上的控件很多。其实干活的就 就 三个:“TServerSocket”、“TClientSocket”、“DecodeTimer”这三个控件。

ServerSocket:负责与登录器进行通讯,它做的操作:

1、接收连接,代码如下:

{

函数功能:接受客户端连接,发送消息到 登录服务器

}

procedure TFrmMain.ServerSocketClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

var

UserSession:pTUserSession;

sRemoteIPaddr,sLocalIPaddr:String;

nSockIndex:Integer;

IPaddr :pTSockaddr;

begin

Socket.nIndex:=-1;

// 客户端IP地址

sRemoteIPaddr:=Socket.RemoteAddress;

if g_boDynamicIPDisMode then begin

sLocalIPaddr:=ClientSocket.Socket.RemoteAddress;

end else begin

sLocalIPaddr:=Socket.LocalAddress;

end;

// 过滤ip

if IsBlockIP(sRemoteIPaddr) then begin

MainOutMessage('过滤连接: ' + sRemoteIPaddr,1);

Socket.Close;

exit;

end;

// 当前IP是否可以连接

if IsConnLimited(sRemoteIPaddr) then begin

case BlockMethod of

// 断开

mDisconnect: begin

Socket.Close;

end;

// 动态过滤

mBlock: begin

New(IPaddr);

IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));

TempBlockIPList.Add(IPaddr);

CloseConnect(sRemoteIPaddr);

end;

// 永久过滤

mBlockList: begin

New(IPaddr);

IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));

BlockIPList.Add(IPaddr);

CloseConnect(sRemoteIPaddr);

end;

end;

MainOutMessage('端口攻击: ' + sRemoteIPaddr,1);

exit;

end;

// 如果网关准备好了

if boGateReady then begin

for nSockIndex:= 0 to GATEMAXSESSION - 1 do begin

UserSession:=@g_SessionArray[nSockIndex];

if UserSession.Socket = nil then begin

UserSession.Socket:=Socket;

UserSession.sRemoteIPaddr:=sRemoteIPaddr;

UserSession.nSendMsgLen:=0;

UserSession.bo0C:=False;

UserSession.dw10Tick:=GetTickCount();

UserSession.dwConnctCheckTick:=GetTickCount();

UserSession.boSendAvailable:=True;

UserSession.boSendCheck:=False;

UserSession.nCheckSendLength:=0;

UserSession.n20:=0;

UserSession.dwUserTimeOutTick:=GetTickCount();

UserSession.SocketHandle:=Socket.SocketHandle;

UserSession.sIP:=sRemoteIPaddr;

UserSession.MsgList.Clear;

Socket.nIndex:=nSockIndex;

Inc(nSessionCount);

break;

end;

end;

// 和本地登录服务器进行通讯

if Socket.nIndex >= 0 then begin

ClientSocket.Socket.SendText('%O' +

IntToStr(Socket.SocketHandle) +

'/' +

sRemoteIPaddr +

'/' +

sLocalIPaddr +

'$');

MainOutMessage('Connect: ' + sRemoteIPaddr,5);

end else begin

Socket.Close;

MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);

end;

end else begin //0x004529EF

Socket.Close;

MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);

end;

end;

说白了,别看 那些IP过滤规则和连接限制什么的。就是 来了一用户,直接保存到一个 UserSession中。然后通知LoginSvr,有人连接了。。现在市面上的什么:GOM引擎、Hero、HGE(原3K、IGE)、Legend、GEEM2、77M2都是换汤不换药。变的就是 加密方式。这里不得不说,JsocketJ就是TServerSocket、TClientSocket的控件,懒得看源码,看了下其属性,大致就可以了解到。

是采用的线程池的select模型。早期的游戏,都采用这种方式,不过,对于私*服,连接量不大,完全足够应付。其实有更好的解决办法,那就是IOCP来管理。效率更高。windows下最适合的模型了。

2、断开连接,代码如下:

procedure TFrmMain.ServerSocketClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

var

I:Integer;

UserSession:pTUserSession;

nSockIndex:Integer;

sRemoteIPaddr:String;

IPaddr :pTSockaddr;

nIPaddr :Integer;

begin

sRemoteIPaddr:=Socket.RemoteAddress;

nIPaddr:=inet_addr(PChar(sRemoteIPaddr));

nSockIndex:=Socket.nIndex;

for I := 0 to CurrIPaddrList.Count - 1 do begin

IPaddr:=CurrIPaddrList.Items[I];

if IPaddr.nIPaddr = nIPaddr then begin

Dec(IPaddr.nCount);

if IPaddr.nCount <= 0 then begin

Dispose(IPaddr);

CurrIPaddrList.Delete(I);

end;

Break;

end;

end;

if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin

UserSession:=@g_SessionArray[nSockIndex];

UserSession.Socket:=nil;

UserSession.sRemoteIPaddr:='';

UserSession.SocketHandle:=-1;

UserSession.MsgList.Clear;

Dec(nSessionCount);

if boGateReady then begin

ClientSocket.Socket.SendText('%X' +

IntToStr(Socket.SocketHandle) +

'$');

MainOutMessage('DisConnect: ' + sRemoteIPaddr,5);

end;

end;

end;

删除,释放,并通知 LoginSvr,有人断开连接了。。。

3、接收数据,代码如下:

procedure TFrmMain.ServerSocketClientRead(Sender: TObject;

Socket: TCustomWinSocket);

var

UserSession:pTUserSession;

nSockIndex:Integer;

sReviceMsg,s10,s1C:String;

nPos:Integer;

nMsgLen:Integer;

begin

nSockIndex:=Socket.nIndex;

if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin

UserSession:=@g_SessionArray[nSockIndex];

sReviceMsg:=Socket.ReceiveText;

if (sReviceMsg <> '') and (boServerReady) then begin

nPos:=Pos('*',sReviceMsg);

if nPos > 0 then begin

UserSession.boSendAvailable:=True;

UserSession.boSendCheck:=False;

UserSession.nCheckSendLength:=0;

s10:=Copy(sReviceMsg,1,nPos -1);

s1C:=Copy(sReviceMsg,nPos + 1,Length(sReviceMsg) - nPos);

sReviceMsg:=s10 + s1C;

end;

nMsgLen:=length(sReviceMsg);

if (sReviceMsg <> '') and (boGateReady) and (not boKeepAliveTimcOut)then begin

UserSession.dwConnctCheckTick:=GetTickCount();

if (GetTickCount - UserSession.dwUserTimeOutTick) < 1000 then begin

Inc(UserSession.n20,nMsgLen);

end else UserSession.n20:= nMsgLen;

ClientSocket.Socket.SendText('%A' +

IntToStr(Socket.SocketHandle) +

'/' +

sReviceMsg +

'$');

end;

end;

end;

end;

拿到数据,转发到 登录账户服务器。。。。。它主要干的事完了。。

ClientSocket:负责与LoginSvr进行通讯,它做的主要工作就是,

procedure TFrmMain.ClientSocketRead(Sender: TObject;

Socket: TCustomWinSocket);

var

sRecvMsg:String;

begin

sRecvMsg:=Socket.ReceiveText;

ClientSockeMsgList.Add(sRecvMsg);

end;

你没有看错,就是接收到数据。然后保存到链表,然后等待定时器来解析操作。。

DecodeTimer 定时器的工作。源码中设置的 时间精度是 1毫秒:

procedure TFrmMain.DecodeTimerTimer(Sender : TObject);

var

sProcessMsg :String;

sSocketMsg :String;

sSocketHandle :String;

nSocketIndex :Integer;

nMsgCount :Integer;

nSendRetCode :Integer;

nSocketHandle :Integer;

dwDecodeTick :LongWord;

dwDecodeTime :LongWord;

sRemoteIPaddr :String;

UserSession :pTUserSession;

IPaddr :pTSockaddr;

begin

ShowMainLogMsg();

if boDecodeLock or (not boGateReady)then exit;

try

dwDecodeTick:=GetTickCount();

boDecodeLock:=True;

sProcessMsg:='';

while (True) do begin

if ClientSockeMsgList.Count <= 0 then break;

sProcessMsg:=sProcMsg + ClientSockeMsgList.Strings[0];

sProcMsg:='';

ClientSockeMsgList.Delete(0);

while (True) do begin

if TagCount(sProcessMsg,'$') < 1 then break;

sProcessMsg:=ArrestStringEx(sProcessMsg,'%','$',sSocketMsg);

if sSocketMsg = ''then break;

if sSocketMsg[1] = '+' then begin

if sSocketMsg[2] = '-' then begin

CloseSocket(Str_ToInt(Copy(sSocketMsg,3,Length(sSocketMsg) - 2),0));

Continue;

end else begin //0x004521B7

dwKeepAliveTick:=GetTickCount();

boKeepAliveTimcOut:=False;

Continue;

end;

end; //0x004521CD

sSocketMsg:=GetValidStr3(sSocketMsg,sSocketHandle,['/']);

nSocketHandle:=Str_ToInt(sSocketHandle,-1);

if nSocketHandle < 0 then

Continue;

for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin

if g_SessionArray[nSocketIndex].SocketHandle = nSocketHandle then begin

g_SessionArray[nSocketIndex].MsgList.Add(sSocketMsg);

break;

end;

end;

end; //0x00452246

end; //0x452252

//if sProcessMsg <> '' then ClientSockeMsgList.Add(sProcessMsg);

if sProcessMsg <> '' then sProcMsg:=sProcessMsg;

nSendMsgCount:=0;

n456A2C:=0;

StringList318.Clear;

for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin

if g_SessionArray[nSocketIndex].SocketHandle <= -1 then Continue;

//踢除超时无数据传输连接

if (GetTickCount - g_SessionArray[nSocketIndex].dwConnctCheckTick) > dwKeepConnectTimeOut then begin

sRemoteIPaddr:=g_SessionArray[nSocketIndex].sRemoteIPaddr;

case BlockMethod of //

mDisconnect: begin

g_SessionArray[nSocketIndex].Socket.Close;

end;

mBlock: begin

New(IPaddr);

IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));

TempBlockIPList.Add(IPaddr);

CloseConnect(sRemoteIPaddr);

end;

mBlockList: begin

New(IPaddr);

IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));

BlockIPList.Add(IPaddr);

CloseConnect(sRemoteIPaddr);

end;

end;

MainOutMessage('端口空连接攻击: ' + sRemoteIPaddr,1);

Continue;

end;

while (True) do begin;

if g_SessionArray[nSocketIndex].MsgList.Count <= 0 then break;

UserSession:=@g_SessionArray[nSocketIndex];

nSendRetCode:=SendUserMsg(UserSession,UserSession.MsgList.Strings[0]);

if (nSendRetCode >= 0) then

begin

if nSendRetCode = 1 then begin

UserSession.dwConnctCheckTick:=GetTickCount();

UserSession.MsgList.Delete(0);

Continue;

end;

if UserSession.MsgList.Count > 100 then begin

nMsgCount:=0;

while nMsgCount <> 51 do begin

UserSession.MsgList.Delete(0);

Inc(nMsgCount);

end;

end;

Inc(n456A2C,UserSession.MsgList.Count);

MainOutMessage(UserSession.sIP +

' : ' +

IntToStr(UserSession.MsgList.Count),5);

Inc(nSendMsgCount);

end else begin //0x004523A4

UserSession.SocketHandle:= -1;

UserSession.Socket:= nil;

UserSession.MsgList.Clear;

end;

end;

end;

if (GetTickCount - dwSendKeepAliveTick) > 2 * 1000 then begin

dwSendKeepAliveTick:=GetTickCount();

if boGateReady then

ClientSocket.Socket.SendText('%--$');

end;

if (GetTickCount - dwKeepAliveTick) > 10 * 1000 then begin

boKeepAliveTimcOut:=True;

ClientSocket.Close;

end;

finally

boDecodeLock:=False;

end;

dwDecodeTime:=GetTickCount - dwDecodeTick;

if dwDecodeMsgTime < dwDecodeTime then dwDecodeMsgTime:=dwDecodeTime;

if dwDecodeMsgTime > 50 then Dec(dwDecodeMsgTime,50);

end;

又是一坨代码,简而言之,就是把刚才保存接收到的数据。分发到 每个用户的自己的消息链表中,然后遍历,发送出去,

代码如下:

// 发送用户消息

function TFrmMain.SendUserMsg(UserSession:pTUserSession;sSendMsg:String):Integer;

begin

Result:= -1;

// 如果

if UserSession.Socket <> nil then begin

// 取反

if not UserSession.bo0C then begin

// 如果不能发送,则置可用

if not UserSession.boSendAvailable and (GetTickCount > UserSession.dwSendLockTimeOut) then begin

UserSession.boSendAvailable := True;

UserSession.nCheckSendLength := 0;

boSendHoldTimeOut := True;

dwSendHoldTick := GetTickCount();

end; //004525DD

if UserSession.boSendAvailable then begin

if UserSession.nCheckSendLength >= 250 then begin

if not UserSession.boSendCheck then begin

UserSession.boSendCheck:=True;

sSendMsg:='*' + sSendMsg;

end;

if UserSession.nCheckSendLength >= 512 then begin

UserSession.boSendAvailable:=False;

UserSession.dwSendLockTimeOut:=GetTickCount + 3 * 1000;

end;

end; //00452620

UserSession.Socket.SendText(sSendMsg);

Inc(UserSession.nSendMsgLen,length(sSendMsg));

Inc(UserSession.nCheckSendLength,length(sSendMsg));

Result:= 1;

end else begin //0x0045264A

Result:= 0;

end;

end else begin //0x00452651

Result:= 0;

end;

end;

end;

登录网关,是不是很简单,这不是 重点,重点是市面上的很多引擎的登录网关都基于这套机制,只需要逆向分析下其加密算法,一个自定义网关则出来了。至于过滤规则,什么IP通道,都是浮云。。。。

Mir2源码详解之服务端-选择&lpar;角色&rpar;网关&lpar;SelGate&rpar;

其实,SelGate也就是 LoginGate,其源码实现完全相同.不必怀疑,市面上的都是这么做~!这里单独写这篇文章,就是为了说明这点!

saltstack源码详解一

目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...

Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

Android应用源码图书馆管理系统带服务端数据库

本项目是一套基于安卓的图书馆管理系统,包括jsp服务端源码,安卓客户端源码和mysql数据库.代码比较简单,供学习anroid与j2ee交互.例如Sqlite的使用.安卓客户端与jsp的web服务端的 ...

spring事务详解(三)源码详解

系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

WebSocket安卓客户端实现详解&lpar;三&rpar;–服务端主动通知

WebSocket安卓客户端实现详解(三)–服务端主动通知 本篇依旧是接着上一篇继续扩展,还没看过之前博客的小伙伴,这里附上前几篇地址 WebSocket安卓客户端实现详解(一)–连接建立与重连 We ...

条件随机场之CRF&plus;&plus;源码详解-预测

这篇文章主要讲解CRF++实现预测的过程,预测的算法以及代码实现相对来说比较简单,所以这篇文章理解起来也会比上一篇条件随机场训练的内容要容易. 预测 上一篇条件随机场训练的源码详解中,有一个地方并没有 ...

&lbrack;转&rsqb;Linux内核源码详解--iostat

Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

Shiro 登录认证源码详解

Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...

随机推荐

JAVA中去掉空格经典整理

JAVA中去掉空格经典整理 JAVA中去掉空格          1. String.trim() --------------trim()是去掉首尾空格           2.str.replac ...

p7 struct and union

struct  StudentRec           //①声明结构体类型StudentRec{        char StuNum[20];        //②定义结构体的成员变量      ...

MVC2 Area实现网站多级目录

Areas是ASP.NET Mvc 2.0版本中引入的众多新特性之一,它可以帮你把一个较大型的Web项目分成若干组成部分,即Area.实现Area的功能可以有两个组织形式: 在1个ASP.NET Mv ...

VS2005保存文件很慢

VS2005出了点毛病,边的出奇的慢,简直不可忍受. 症状是:保存文件很慢,哪怕是修改一个变量,也要等上大概20秒 保存文件的时候,VS2005会在局域网内寻找一个主机当这个主机不在线的时候vs200 ...

SSIS -&gt&semi;&gt&semi; Null &amp&semi; Null Functions

SSIS不支持值为NULL的变量.每种类型的变量都有自己的默认值. 做了一个测试,用一个Execute SQL Task输出一个NULL值给A变量,然后把A变量传到到另外一个Execute SQL T ...

centos 6&period;5 openfire安装

1.下载:http://igniterealtime.org/downloads/download-landing.jsp?file=openfire/openfire-3.9.3-1.i386.rp ...

XCode常用快捷键&lpar;转&rpar;

刚开始用Xcode是不是发现以前熟悉的开发环境的快捷键都不能用了?怎么快捷运行,停止,编辑等等.都不一样了.快速的掌握这些快捷键,能提供开发的效率. 其实快捷键在Xcode的工具栏里都标注有,只是有的 ...

注册UBER&lpar;优步&rpar;司机常见问题,如何注册uber&lpar;优步&rpar;司机

如何注册uber(优步)司机,怎么注册UBER(优步)司机 此链接为优步内部人员推荐,优先审核,基本当天就能收到短信,注意上传证件照清晰点. https://uber.avosapps.com/ref ...

iOS 中如何判断当前是2G&sol;3G&sol;4G&sol;5G&sol;WiFi

5G 什么的,还得等苹果API更新啊,不过将来还是这个处理过程就是了. 关于判断当前的网络环境是2G/3G/4G,这个问题以前经常看到,最近在一工程里看到了如果判断的API.而在撸WebRTC音视频通 ...

&lbrack;CodeForces - 463B&rsqb; Caisa and Pylons

题目链接:http://codeforces.com/problemset/problem/463/B 求个最大值 AC代码: #include #include

易语言linux登录器网关源码,Mir2源码详解之服务端-登录网关(LoginGate)相关推荐

  1. mysql db模块下载_易语言MySql数据库操作类V1.0模块源码

    易语言MySql数据库操作类V1.0模块源码 易语言MySql数据库操作类V1.0模块源码 系统结构:MySql数据库连接类_测试子程序,置连接选项,连接,关闭,是否已连接,取连接句柄,置连接句柄,执 ...

  2. mysql data文件夹恢复_【专注】Zabbix源码安装教程—步骤详解(2)安装并配置mysql...

    四.安装并配置mysql(1) 解压mysql-5.7.26.tar.gz与boost_1_59_0.tar.gz #tar -xvf mysql-5.7.26.tar.gz #tar -xvf bo ...

  3. 基于ET框架致敬LOL的Moba游戏源码,包含完整的客户端与服务端交互

    运行环境 编辑器:Unity 2020.3.12 LTS 客户端:.Net Framework 4.7.2 IDE:JetBrain Rider 2020 服务端:.Net Core 3.1 已实现功 ...

  4. 物联网系统上位机源码,含服务器和客户端 物联网服务端程序

    物联网系统上位机源码,含服务器和客户端 物联网服务端程序,可以接受市面上大多数透传数据的DTU登录,以及和DTU双向通讯 程序功能:能分组管理,不同的组别用户只可见自己组别的设备,设备和客户端登录掉线 ...

  5. Xposed源码剖析——app_process作用详解

    Xposed源码剖析--app_process作用详解 首先吐槽一下CSDN的改版吧,发表这篇文章之前其实我已经将此篇文章写过了两三次了.就是发表不成功.而且CSDN将我的文章草稿也一带>删除掉 ...

  6. php+mysql案例含源码_【专注】Zabbix源码安装教程—步骤详解(1)安装前准备

    一.实验环境准备 Rhel 7.6 x86_64(server) 192.168.163.72 Rhel 6.5 x86_64(agent) 192.168.163.61 均已配置操作安装光盘为YUM ...

  7. React 源码系列 | React Context 详解

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

  8. 送餐app+php,订餐APP源码Food Delivery App v2.1(客户端+服务端)

    android订餐APP源码Food Delivery App v2.1(客户端+服务端),包含android客户端源码.php+mysql服务端源码. Version 2.1 Food Delive ...

  9. linux Shell(脚本)编程入门实例讲解详解

    linux Shell(脚本)编程入门实例讲解详解 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活的工具.Shell不仅仅是命令的收集, ...

  10. 万字详解SSH(SSH登录原理+SSH配置+模拟实现SSH免密登录)

    文章目录 一.SSH概述 1. 加密算法 1)对称加密算法(DES) 2)非对称加密(RSA) 3) 对称加密与非对称加密区别 4)中间人攻击 2. 环境准备 3. SSH基于用户名密码的认证原理 4 ...

最新文章

  1. IDEA下用freemarker热更新的问题
  2. 复兴or幻象?VR的2021三重门
  3. 一次字节面试,被二叉树的层序遍历捏爆了
  4. POJ - 3468 A Simple Problem with Integers(分块)
  5. 在软件开发中应用80:20原则
  6. 锻造完美U盘小偷:活用消息机制
  7. C#入门详解(10)
  8. QML 自定义鼠标光标
  9. 计算机网络中的数据通信——(1)基本结构
  10. 商学院计算机系篮球策划书,篮球训练营策划书.doc
  11. 齐齐哈尔市全国计算机等级考试,2019年3月黑龙江省齐齐哈尔市计算机等级考试注意事项...
  12. 如何系统学习经济学 -- 来自知乎建议
  13. IE11的F12功能无法正常使用
  14. 基于Web SCADA平台构建实时数字化产线 - 初篇
  15. 用Python发免费短信的正确姿势
  16. c语言工业键盘确认键,工业键盘的基础知识
  17. 机器学习数学基础:线代(3)
  18. [MySQL] PRIMARY KEY 主键
  19. java计算自己从出生到现在过了多少天
  20. YOLOv7升级换代:EfficientNet骨干网络助力更精准目标检测

热门文章

  1. python怎么引入os模块的函数_Python之OS模块函数
  2. php utc时间_datetime - 以PHP格式获取UTC时间
  3. python3下载url图片假死_利用Python 向FTP 上传图片,程序假死?
  4. 漫画算法python版下载_漫画算法:小灰的算法之旅(Python篇)
  5. mysql5.5.9_centos下mysql5.5.9编译安装
  6. c语言顺序表的初始化Status,数据结构(c语言版)顺序表的建立、初始化、插入、删除、遍历等12个基本操作及测试...
  7. linux web部署命令简单记录
  8. 如何用Python脚本从文件读取数据?
  9. java基础回顾(一)—— sleep和wait的区别
  10. AP聚类算法(Affinity propagation Clustering Algorithm )