使用Delphi编写棋牌类游戏 -- 基础篇(2)

对于网络游戏来说,它和单机版游戏最大的区别就在于网络通信部分。可以说,网络通信是构成网游的最基本元素。在这里我不想详细的论述如何使用 DELPHI WINDOWS 下进行网络编程,因为这是一个非常复杂的话题。这里我只是想说明在我设计的棋牌类游戏中如何实现游戏网络部分的。
对于 WINDOWS 来说,它的通信模型大概分为 5 种。分别是:
1 SELECT 模型。
2 WSAAsynSelect 模型。
3 WSAEventSelect 模型。
4 IO 重叠模型
5 :完成端口模型。
对于性能来说,完成端口可以管理上万连接(理论连接数量可以达到 65535 ),所以我选择它来作为我游戏服务器的通信模型。
由于游戏客户端和游戏服务器的连接只需要一条就可以,所以我选择了 Select 模型作为客户端通信模型。
 
关于如何编写完成端口以及在完成端口中如何加入心跳,如何避免网络通信中的粘包现象在我以前的 BLOG 中已经有了详细的讲解,由于篇幅太多,我这里就不在将其贴出。详细的可以参看我的 BLOG DELPHI 中完成端口 (IOCP) 的简单分析( 1 )》 - DELPHI 中完成端口 (IOCP) 的简单分析( 4 )》和《网络通信中的心跳机制的实现!》
但是在我最近重新检查代码的时候发现我以前关于 IOCP 的一些处理方式不合适或者有些地方是错误的,我已经在以前的 BLOG 帖子中做了相关的修改。
 
在这里我们讨论一下如何实现客户端通信模型 (Select 模型 ) 。对于熟悉网络编程的程序员来说,编写一个 SELECT 通信模型是一件很简单的事,因为它是 5 中通信模型中最简单的一种。
编写 select 模型的代码如下:
 
Var
  Fsocket Tsokcet;
 
procedure Star;
var
  CliAddrIn:TSockAddrIn;
  hThread:THandle;
  ThreadID,outByte:DWORD;
  keep_alive,out_keep_alive:TTimeVal;
  iAddrSize,opt,I:Integer;
begin
// 加载 SOCKET, 我使用的是 2.2 版为了后面方便加入心跳
if WSAStartUp($202, wsData) <> 0 then
begin
   WSACleanup();
end;
// 创建一个套接字
Fsocket :=socket(AF_INET,SOCK_STREAM,0);
  if Fsocket =SOCKET_ERROR then
  begin
    closesocket( Fsocket );
  end
  else
  begin
       // 这里填入实际的服务器 IP 地址和服务器监听端口
    CliAddrIn.sin_addr.s_addr:=inet_addr(Pchar(‘127.0.0.1’));
    CliAddrIn.sin_family:=AF_INET;
CliAddrIn.sin_port:=htons(5500);
// 连接服务器
    if (connect( Fsocket ,CliAddrIn,sizeof(CliAddrIn))<>SOCKET_ERROR) then
    begin
      // 设置套接字的心跳属性
      opt:=1;
      if setsockopt( Fsocket ,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then
      begin
        closesocket( Fsocket );
      end;
      FinKeepAlive.onoff:=1;
      FinKeepAlive.keepalivetime:=FKeepTime;
      FinKeepAlive.keepaliveinterval:=1;
      Finsize:=sizeof(TTCP_KEEPALIVE);
      Foutsize:=sizeof(TTCP_KEEPALIVE);
      if WSAIoctl( Fsocket ,SIO_KEEPALIVE_VALS,@FinKeepAlive,Finsize,@FoutKeepAlive,Foutsize,@outByte,nil,nil)=SOCKET_ERROR then
      begin
        closesocket( Fsocket );
      end;
      // 启动接收线程
      hThread:=CreateThread(Nil,0,@RevData,Pointer( Fsocket ),0,ThreadID);
      CloseHandle(hThread);
    end
    else
    begin
      closesocket( Fsocket );
    end;
  end;
end;
 
// 接收线程
function RevData(sc:Pointer):Integer;stdcall;
var
  szBuf:array[0..DATA_BUFSIZE-1] of char;
  sRevSize:Integer;
  DeBuf:array[0..DATA_BUFSIZE-1]of char;
  Delen:Integer;
  SpBuf:array[0..DATA_BUFSIZE-1]of char;
  SpLen:Integer;
  IsEnd:Boolean;
  AnalyzeData:array [0..DATA_BUFSIZE-1]of char;
  AnalyzeDataLen:Integer;
  t_sc:TSocket;
  InterlockedCrit:TRTLCriticalSection;
begin
  // 初始化临界区
InitializeCriticalSection(InterlockedCrit);
  FillChar(FBuffer,SizeOf(FBuffer),#0);
  FBufferLen:=0;
  t_sc:=TSocket(sc);
  // 接收数据死循环
while TRUE do
  begin
FillChar(szBuf,sizeof(szBuf),#0);
// 接收数据
sRevSize:=recv(t_sc,szBuf,sizeof(szBuf),0);
// 根据接收函数的返回值得到接收到的数据长度
    if sRevSize = -1 then
begin
  // 如果接收长度为 -1 便表示已经和服务器断开了连接,这样可以进入断开连接处理函数中
      EnterCriticalSection(InterlockedCrit);
      …….
closesocket(t_sc);
      LeaveCriticalSection(InterlockedCrit);
      Exit;
    end
    else if (Integer(t_sc)=-1) or (sRevSize=0) then
begin
  // 如果套接字变为 -1 或者接收长度变为 0 也便是和服务器断开了连接,这样可以进入断开连接处理函数中
      EnterCriticalSection(InterlockedCrit);
      ……..
      closesocket(t_sc);
      LeaveCriticalSection(InterlockedCrit);
      Exit;
    end
    else if (sRevSize>0) and (sRevSize<=DATA_BUFSIZE) then
    begin
      IsEnd:=false;
      while not IsEnd do
      begin
        Delen:=0;
        SpLen:=0;
        if sRevSize>0 then
        begin
          // 进入数据正确处理函数中
          ……
        end
        else
        begin
          IsEnd:=true;
        end;
      end;
    end;
  end;
  DeleteCriticalSection(InterlockedCrit);
end;
// 数据发送函数的处理
function SendBuffer(Data: array of char; DataLen: Integer;sc:TSocket):Boolean;
var
  SendData:array[0..DATA_BUFSIZE-1] of char;
  SendDataLen:Integer;
  RealLen:Integer;
  LenStr:String;
begin
  Result:=false;
  // 在发送数据的前面加入 4 位的发送长度。
  SetArrayLength(DataLen,LenStr);
  // 初始化发送数组
  Fillchar(SendData,sizeof(SendData),#0);
  // 填充发送数组
  strmove(SendData,Pointer(LenStr),4);
  strmove(SendData+4,Data,DataLen);
  SendDataLen:=DataLen+4;
  // 发送数据
  RealLen:=Send(FSocket,SendData,SendDataLen,0);
  if RealLen = DataLen + 4 then
  begin
    Result:=true;
  end;
以上的代码就是使用 SELECT 通信模型编写的一个客户端通信部分所需要的代码。如果要和服务器正确的通信,客户端自然少不了编写粘包处理的函数。其处理的过程类似于服务器中粘包的处理,我这里就不再重述。
通过对客户端和服务器端的编写,一个完整的网络通信部分已经建立。有了稳定的网络通信作为基础,我们就可以顺利的进行后续开发。下一篇我会和大家探讨如何实现棋牌类游戏界面效果。好的界面可以使游戏效果锦上添花,是不可忽视的。

你可能感兴趣的:(开发,职场,Delphi,休闲,棋牌游戏)