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

对于网络游戏来说,它和单机版游戏最大的区别就在于网络通信部分。可以说,网络通信是构成网游的最基本元素。在这里我不想详细的论述如何使用DELPHIWINDOWS下进行网络编程,因为这是一个非常复杂的话题。这里我只是想说明在我设计的棋牌类游戏中如何实现游戏网络部分的。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

对于WINDOWS来说,它的通信模型大概分为5种。分别是:

1SELECT模型。

2WSAAsynSelect模型。

3WSAEventSelect模型。

4IO重叠模型

5:完成端口模型。

对于性能来说,完成端口可以管理上万连接(理论连接数量可以达到65535),所以我选择它来作为我游戏服务器的通信模型。

由于游戏客户端和游戏服务器的连接只需要一条就可以,所以我选择了Select模型作为客户端通信模型。

 

关于如何编写完成端口以及在完成端口中如何加入心跳,如何避免网络通信中的粘包现象在我以前的BLOG中已经有了详细的讲解,由于篇幅太多,我这里就不在将其贴出。详细的可以参看我的BLOG DELPHI中完成端口(IOCP)的简单分析(1)》- DELPHI中完成端口(IOCP)的简单分析(4)》和《网络通信中的心跳机制的实现!》

但是在我最近重新检查代码的时候发现我以前关于IOCP的一些处理方式不合适或者有些地方是错误的,我已经在以前的BLOG帖子中做了相关的修改。

 

在这里我们讨论一下如何实现客户端通信模型(Select模型)。对于熟悉网络编程的程序员来说,编写一个SELECT通信模型是一件很简单的事,因为它是5中通信模型中最简单的一种。

编写select模型的代码如下:

 

Var

  FsocketTsokcet;

 

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通信模型编写的一个客户端通信部分所需要的代码。如果要和服务器正确的通信,客户端自然少不了编写粘包处理的函数。其处理的过程类似于服务器中粘包的处理,我这里就不再重述。

通过对客户端和服务器端的编写,一个完整的网络通信部分已经建立。有了稳定的网络通信作为基础,我们就可以顺利的进行后续开发。下一篇我会和大家探讨如何实现棋牌类游戏界面效果。好的界面可以使游戏效果锦上添花,是不可忽视的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值