• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            [轉載]Flash為客戶端的多人網絡游戲的實現

            來源:http://outspace.spaces.live.com/

            多人網絡游戲的實現

            項目開發的基本硬件配置
            一臺普通的pc就可以了,
            安裝好windows 2000和vc6就可以了,
            然后連上網,局域網和internet都可以,

            接下去的東西我都簡化,不去用晦澀的術語,

            既然是網絡,我們就需要網絡編程接口,
            服務器端我們用的是winsock 1.1,使用tcp連接方式,

            [tcp和udp]
            tcp可以理解為一條連接兩個端子的隧道,提供可靠的數據傳輸服務,
            只要發送信息的一方成功的調用了tcp的發送函數發送一段數據,
            我們可以認為接收方在若干時間以后一定會接收到完整正確的數據,
            不需要去關心網絡傳輸上的細節,
            而udp不保證這一點,
            對于網絡游戲來說,tcp是普遍的選擇。

            [阻塞和非阻塞]
            在通過socket發送數據時,如果直到數據發送完畢才返回的方式,也就是說如果我們使用send( buffer, 100.....)這樣的函數發送100個字節給別人,我們要等待,直到100個自己發送完畢,程序才往下走,這樣就是阻塞的,
            而非阻塞的方式,當你調用send(buffer,100....)以后,立即返回,此時send函數告訴你發送成功,并不意味著數據已經向目的地發送完畢,甚至有可能數據還沒有開始發送,只被保留在系統的緩沖里面,等待被發送,但是你可以認為數據在若干時間后,一定會被目的地完整正確的收到,我們要充分的相信tcp。
            阻塞的方式會引起系統的停頓,一般網絡游戲里面使用的都是非阻塞的方式,
            ——————————————————————
            注意,僅僅用flash作為客戶端,
            服務器端,我們使用vc6,
            我將陸續的公開服務器端的源代碼和大家共享,
            并且將講解一些網絡游戲開發的原理,
            希望對此感興趣的朋友能夠使用今后的資源或者理論開發出完整的網絡游戲。
            我們從簡單到復雜,
            從棋牌類游戲到動作類的游戲,
            從2個人的游戲到10個人的游戲,
            因為工作忙的關系,我所做的一切僅僅起到拋磚引玉的作用,
            希望大家能夠熱情的討論,為中國的flash事業墊上一塊磚,添上一片瓦。

            現在的大型網絡游戲(mmo game)都是基于server/client體系結構的,
            server端用c(windows下我們使用vc.net+winsock)來編寫,
            客戶端就無所謂,
            在這里,我們討論用flash來作為客戶端的實現,

            實踐證明,flash的xml socket完全可以勝任網絡傳輸部分,
            在別的貼子中,我看見有的朋友談論msn中的flash game
            他使用msn內部的網絡接口進行傳輸,
            這種做法也是可以的,
            我找很久以前對于2d圖形編程的說法,"給我一個打點函數,我就能創造整個游戲世界",
            而在網絡游戲開發過程中,"給我一個發送函數和一個接收函數,我就能創造網絡游戲世界."

            我們抽象一個接口,就是網絡傳輸的接口,
            對于使用flash作為客戶端,要進行網絡連接,
            一個網絡游戲的客戶端,
            可以簡單的抽象為下面的流程
            1.與遠程服務器建立一條長連接
            2.用賬號密碼登陸
            3.循環
            接收消息
            發送消息
            4.關閉

            我們可以直接使用flash 的xml socket,也可以使用類似msn的那種方式,
            這些我們先不管,我們先定義接口,
            Connect( "127.0.0.1", 20000 ); 連接遠程服務器,建立一條長連接
            Send( data, len ); 向服務器發送一條消息
            Recv( data, len ); 接收服務器傳來的消息


            說明一下普通網絡游戲中windows下面網絡傳輸的原理,
            ——————————————————————
            ?
            [有狀態服務器和無狀態服務器]
            在c/s體系中,如果server不保存客戶端的狀態,稱之為無狀態,反之為有狀態,

            在這里要強調一點,
            我們所說的服務器不是一臺具體的機器,
            而是指服務器應用程序,
            一臺具體的機器或者機器群組可以運行一個或者多個服務器應用程序,

            我們的網絡游戲使用的是有狀態服務器,
            保存所有玩家的數據和狀態,
            ——————————————————————
            一些有必要了解的理論和開發工具

            [開發語言]
            vc6
            我們首先要熟練的掌握一門開發語言,
            學習c++是非常有必要的,
            而vc是windows下面的軟件開發工具,
            為什么選擇vc,可能與我本身使用vc有關,
            而且網上可以找到許多相關的資源和源代碼,

            [操作系統]
            我們使用windows2000作為服務器的運行環境,
            所以我們有必要去了解windows是如何工作的,
            同時對它的編程原理應該熟練的掌握

            [數據結構和算法]
            要寫出好的程序要先具有設計出好的數據結構和算法的能力,
            好的算法未必是繁瑣的公式和復雜的代碼,
            我們要找到又好寫有滿足需求的算法,
            有時候,最笨的方法同時也是很好的方法,
            很多程序員沉迷于追求精妙的算法而忽略了宏觀上的工程,
            花費了大量的精力未必能夠取得好的效果,

            舉個例子,
            我當年進入游戲界工作,學習老師的代碼,
            發現有個函數,要對畫面中的npc位置進行排序,
            確定哪個先畫,那個后畫,
            他的方法太“笨”,
            任何人都會想到的冒泡,
            一個一個去比較,沒有任何的優化,
            我當時想到的算法就有很多,
            而且有一大堆優化策略,
            可是,當我花了很長時間去實現我的算法時,
            發現提升的那么一點效率對游戲整個運行效率而言幾乎是沒起到什么作用,
            或者說雖然算法本身快了幾倍,
            可是那是多余的,老師的算法雖然“笨”,
            可是他只花了幾十行代碼就搞定了,
            他的時間花在別的更需要的地方,
            這就是他可以獨自完成一個游戲,
            而我可以把一個函數優化100倍也只能打雜的原因

            [tcp/ip的理論]
            推薦數據用tcp/ip進行網際互連,tcp/ip詳解,
            這是兩套書,共有6卷,
            都是國外的大師寫的,
            可以說是必讀的,
            ——————————————————————
            網絡傳輸中的“消息”

            [消息]
            消息是個很常見的術語,
            在windows中,消息機制是個十分重要的概念,
            我們在網絡游戲中,也使用了消息這樣的機制,

            一般我們這么做,
            一個數據塊,頭4個字節是消息名,后面接2個字節的數據長度,
            再后面就是實際的數據

            為什么使用消息??
            我們來看看例子,

            在游戲世界,
            一個玩家想要和別的玩家聊天,
            那么,他輸入好聊天信息,
            客戶端生成一條聊天消息,
            并把聊天的內容打包到消息中,
            然后把聊天消息發送給服務器,
            請求服務器把聊天信息發送給另一個玩家,

            服務器接收到一條消息,
            此刻,服務器并不知道當前的數據是什么東西,
            對于服務器來講,這段數據僅僅來自于網絡通訊的底層,
            不加以分析的話,沒有任何的信息,
            因為我們的通訊是基于消息機制的,
            我們認為服務器接收到的任何數據都是基于消息的數據方式組織的,
            4個字節消息名,2字節長度,這個是不會變的,

            通過消息名,服務器發現當前數據是一條聊天數據,
            通過長度把需要的數據還原,校驗,
            然后把這條消息發送給另一個玩家,

            大家注意,消息是變長的,
            關于消息的解釋完全在于服務器和客戶端的應用程序,
            可以認為與網絡傳輸低層無關,
            比如一條私聊消息可能是這樣的,

            MsgID:4 byte
            Length:2 byte
            TargetPlayerID:2 byte
            String:anybyte < 256

            一條移動消息可能是這樣的,
            MsgID:4 byte
            Length:2 byte
            TargetPlayerID:2 byte
            TargetPosition:4 byte (x,y)

            編程者可以自定義消息的內容以滿足不同的需求
            ——————————————————————
            隊列

            [隊列]
            隊列是一個很重要的數據結構,
            比如說消息隊列,
            服務器或者客戶端,
            發送的消息不一定是立即發送的,
            而是等待一個適當時間,
            或者系統規定的時間間隔以后才發送,
            這樣就需要創建一個消息隊列,以保存發送的消息,

            消息隊列的大小可以按照實際的需求創建,
            隊列又可能會滿,
            當隊列滿了,可以直接丟棄消息,
            如果你覺得這樣不妥,
            也可以預先劃分一個足夠大的隊列,

            可以使用一個系統全局的大的消息隊列,
            也可以為每個對象創建一個消息隊列,


            這個我們的一個數據隊列的實現,
            開發工具vc.net,使用了C++的模板,
            關于隊列的算法和基礎知識,我就不多說了,

            DataBuffer.h

            #ifndef __DATABUFFER_H__
            #define __DATABUFFER_H__

            #include <windows.h>
            #include <assert.h>
            #include "g_assert.h"
            #include <stdio.h>

            #ifndef HAVE_BYTE
            typedef unsigned char byte;
            #endif // HAVE_BYTE

            //數據隊列管理類
            template <const int _max_line, const int _max_size>
            class DataBufferTPL
            {
            public:

            bool Add( byte *data ) // 加入隊列數據
            {
            G_ASSERT_RET( data, false );
            m_ControlStatus = false;

            if( IsFull() )
            {
            //assert( false );
            return false;
            }

            memcpy( m_s_ptr, data, _max_size );

            NextSptr();
            m_NumData++;

            m_ControlStatus = true;
            return true;
            }


            bool Get( byte *data ) // 從隊列中取出數據
            {
            G_ASSERT_RET( data, false );
            m_ControlStatus = false;

            if( IsNull() )
            return false;

            memcpy( data, m_e_ptr, _max_size );

            NextEptr();
            m_NumData--;

            m_ControlStatus = true;
            return true;
            }


            bool CtrlStatus() // 獲取操作成功結果
            {
            return m_ControlStatus;
            }


            int GetNumber() // 獲得現在的數據大小
            {
            return m_NumData;
            }

            public:

            DataBufferTPL()
            {
            m_NumData = 0;
            m_start_ptr = m_DataTeam[0];
            m_end_ptr = m_DataTeam[_max_line-1];
            m_s_ptr = m_start_ptr;
            m_e_ptr = m_start_ptr;
            }
            ~DataBufferTPL()
            {
            m_NumData = 0;
            m_s_ptr = m_start_ptr;
            m_e_ptr = m_start_ptr;
            }

            private:

            bool IsFull() // 是否隊列滿
            {
            G_ASSERT_RET( m_NumData >=0 && m_NumData <= _max_line, false );
            if( m_NumData == _max_line )
            return true;
            else
            return false;
            }
            bool IsNull() // 是否隊列空
            {
            G_ASSERT_RET( m_NumData >=0 && m_NumData <= _max_line, false );
            if( m_NumData == 0 )
            return true;
            else
            return false;
            }
            void NextSptr() // 頭位置增加
            {
            assert(m_start_ptr);
            assert(m_end_ptr);
            assert(m_s_ptr);
            assert(m_e_ptr);
            m_s_ptr += _max_size;
            if( m_s_ptr > m_end_ptr )
            m_s_ptr = m_start_ptr;
            }
            void NextEptr() // 尾位置增加
            {
            assert(m_start_ptr);
            assert(m_end_ptr);
            assert(m_s_ptr);
            assert(m_e_ptr);
            m_e_ptr += _max_size;
            if( m_e_ptr > m_end_ptr )
            m_e_ptr = m_start_ptr;
            }

            private:

            byte m_DataTeam[_max_line][_max_size]; //數據緩沖
            int m_NumData; //數據個數
            bool m_ControlStatus; //操作結果

            byte *m_start_ptr; //起始位置
            byte *m_end_ptr; //結束位置
            byte *m_s_ptr; //排隊起始位置
            byte *m_e_ptr; //排隊結束位置
            };


            //////////////////////////////////////////////////////////////////////////
            // 放到這里了!

            //ID自動補位列表模板,用于自動列表,無間空順序列表。
            template <const int _max_count>
            class IDListTPL
            {
            public:
            // 清除重置
            void Reset()
            {
            for(int i=0;i<_max_count;i++)
            m_dwList[i] = G_ERROR;
            m_counter = 0;
            }

            int MaxSize() const { return _max_count; }
            int Count() const { return m_counter; }
            const DWORD operator[]( int iIndex ) {

            G_ASSERTN( iIndex >= 0 && iIndex < m_counter );

            return m_dwList[ iIndex ];
            }
            bool New( DWORD dwID )
            {
            G_ASSERT_RET( m_counter >= 0 && m_counter < _max_count, false );

            //ID 唯一性,不能存在相同ID
            if ( Find( dwID ) != -1 )
            return false;

            m_dwList[m_counter] = dwID;
            m_counter++;

            return true;
            }
            // 沒有Assert的加入ID功能
            bool Add( DWORD dwID )
            {
            if( m_counter <0 || m_counter >= _max_count )
            return false;

            //ID 唯一性,不能存在相同ID
            if ( Find( dwID ) != -1 )
            return false;

            m_dwList[m_counter] = dwID;
            m_counter++;
            return true;
            }
            bool Del( int iIndex )
            {
            G_ASSERT_RET( iIndex >=0 && iIndex < m_counter, false );

            for(int k=iIndex;k<m_counter-1;k++)
            {
            m_dwList[k] = m_dwList[k+1];
            }

            m_dwList[k] = G_ERROR;
            m_counter--;
            return true;
            }
            int Find( DWORD dwID )
            {
            for(int i=0;i<m_counter;i++)
            {
            if( m_dwList[i] == dwID )
            return i;
            }

            return -1;
            }

            IDListTPL():m_counter(0)
            {
            for(int i=0;i<_max_count;i++)
            m_dwList[i] = G_ERROR;
            }
            virtual ~IDListTPL()
            {}

            private:

            DWORD m_dwList[_max_count];
            int m_counter;

            };

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


            #endif //__DATABUFFER_H__
            ——————————————————————
            socket

            我們采用winsock作為網絡部分的編程接口,

            接下去編程者有必要學習一下socket的基本知識,
            不過不懂也沒有關系,我提供的代碼已經把那些麻煩的細節或者正確的系統設置給弄好了,
            編程者只需要按照規則編寫游戲系統的處理代碼就可以了,

            這些代碼在vc6下編譯通過,
            是通用的網絡傳輸底層,
            這里是socket部分的代碼,

            我們需要安裝vc6才能夠編譯以下的代碼,
            因為接下去我們要接觸越來越多的c++,
            所以,大家還是去看看c++的書吧,

            // socket.h
            #ifndef _socket_h
            #define _socket_h
            #pragma once

            //定義最大連接用戶數目 ( 最大支持 512 個客戶連接 )
            #define MAX_CLIENTS 512
            //#define FD_SETSIZE MAX_CLIENTS

            #pragma comment( lib, "wsock32.lib" )

            #include <winsock.h>

            class CSocketCtrl
            {
            void SetDefaultOpt();
            public:
            CSocketCtrl(): m_sockfd(INVALID_SOCKET){}
            BOOL StartUp();
            BOOL ShutDown();
            BOOL IsIPsChange();

            BOOL CanWrite();
            BOOL HasData();
            int Recv( char* pBuffer, int nSize, int nFlag );
            int Send( char* pBuffer, int nSize, int nFlag );
            BOOL Create( UINT uPort );
            BOOL Create(void);
            BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
            void Close();

            BOOL Listen( int nBackLog );
            BOOL Accept( CSocketCtrl& sockCtrl );

            BOOL RecvMsg( char *sBuf );
            int SendMsg( char *sBuf,unsigned short stSize );
            SOCKET GetSockfd(){ return m_sockfd; }

            BOOL GetHostName( char szHostName[], int nNameLength );

            protected:
            SOCKET m_sockfd;

            static DWORD m_dwConnectOut;
            static DWORD m_dwReadOut;
            static DWORD m_dwWriteOut;
            static DWORD m_dwAcceptOut;
            static DWORD m_dwReadByte;
            static DWORD m_dwWriteByte;
            };


            #endif

            // socket.cpp

            #include <stdio.h>
            #include "msgdef.h"
            #include "socket.h"
            // 吊線時間
            #define ALL_TIMEOUT 120000
            DWORD CSocketCtrl::m_dwConnectOut = 60000;
            DWORD CSocketCtrl::m_dwReadOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwWriteOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwAcceptOut = ALL_TIMEOUT;
            DWORD CSocketCtrl::m_dwReadByte = 0;
            DWORD CSocketCtrl::m_dwWriteByte = 0;

            // 接收數據
            BOOL CSocketCtrl::RecvMsg( char *sBuf )
            {
            if( !HasData() )
            return FALSE;
            MsgHeader header;
            int nbRead = this->Recv( (char*)&header, sizeof( header ), MSG_PEEK );
            if( nbRead == SOCKET_ERROR )
            return FALSE;
            if( nbRead < sizeof( header ) )
            {
            this->Recv( (char*)&header, nbRead, 0 );
            printf( "\ninvalid msg, skip %ld bytes.", nbRead );
            return FALSE;
            }

            if( this->Recv( (char*)sBuf, header.stLength, 0 ) != header.stLength )
            return FALSE;

            return TRUE;
            }

            // 發送數據
            int CSocketCtrl::SendMsg( char *sBuf,unsigned short stSize )
            {
            static char sSendBuf[ 4000 ];
            memcpy( sSendBuf,&stSize,sizeof(short) );
            memcpy( sSendBuf + sizeof(short),sBuf,stSize );

            if( (sizeof(short) + stSize) != this->Send( sSendBuf,stSize+sizeof(short),0 ) )
            return -1;
            return stSize;
            }


            // 啟動winsock
            BOOL CSocketCtrl::StartUp()
            {
            WSADATA wsaData;
            WORD wVersionRequested = MAKEWORD( 1, 1 );

            int err = WSAStartup( wVersionRequested, &wsaData );
            if ( err != 0 )
            {
            return FALSE;
            }


            return TRUE;

            }
            // 關閉winsock
            BOOL CSocketCtrl::ShutDown()
            {
            WSACleanup();
            return TRUE;
            }

            // 得到主機名
            BOOL CSocketCtrl::GetHostName( char szHostName[], int nNameLength )
            {
            if( gethostname( szHostName, nNameLength ) != SOCKET_ERROR )
            return TRUE;
            return FALSE;
            }

            BOOL CSocketCtrl::IsIPsChange()
            {
            return FALSE;
            static int iIPNum = 0;
            char sHost[300];

            hostent *pHost;
            if( gethostname(sHost,299) != 0 )
            return FALSE;
            pHost = gethostbyname(sHost);
            int i;
            char *psHost;
            i = 0;
            do
            {
            psHost = pHost->h_addr_list[i++];
            if( psHost == 0 )
            break;

            }while(1);
            if( iIPNum != i )
            {
            iIPNum = i;
            return TRUE;
            }
            return FALSE;
            }

            // socket是否可以寫
            BOOL CSocketCtrl::CanWrite()
            {
            int e;

            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 0;

            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,NULL,&set,NULL,&tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;
            return FALSE;
            }

            // socket是否有數據
            BOOL CSocketCtrl::HasData()
            {
            int e;
            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 0;

            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,&set,NULL,NULL,&tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;
            return FALSE;
            }

            int CSocketCtrl::Recv( char* pBuffer, int nSize, int nFlag )
            {
            return recv( m_sockfd, pBuffer, nSize, nFlag );
            }

            int CSocketCtrl::Send( char* pBuffer, int nSize, int nFlag )
            {
            return send( m_sockfd, pBuffer, nSize, nFlag );
            }

            BOOL CSocketCtrl::Create( UINT uPort )
            {
            m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
            if(m_sockfd==INVALID_SOCKET) return FALSE;
            SOCKADDR_IN SockAddr;
            memset(&SockAddr,0,sizeof(SockAddr));
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_addr.s_addr = INADDR_ANY;
            SockAddr.sin_port = ::htons( uPort );
            if(!::bind(m_sockfd,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))
            {
            SetDefaultOpt();
            return TRUE;
            }
            Close();
            return FALSE;

            }

            void CSocketCtrl::Close()
            {
            ::closesocket( m_sockfd );
            m_sockfd = INVALID_SOCKET;
            }

            BOOL CSocketCtrl::Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
            {
            if(m_sockfd==INVALID_SOCKET) return FALSE;

            SOCKADDR_IN sockAddr;

            memset(&sockAddr,0,sizeof(sockAddr));
            LPSTR lpszAscii=(LPSTR)lpszHostAddress;
            sockAddr.sin_family=AF_INET;
            sockAddr.sin_addr.s_addr=inet_addr(lpszAscii);
            if(sockAddr.sin_addr.s_addr==INADDR_NONE)
            {
            HOSTENT * lphost;
            lphost = ::gethostbyname(lpszAscii);
            if(lphost!=NULL)
            sockAddr.sin_addr.s_addr = ((IN_ADDR *)lphost->h_addr)->s_addr;
            else return FALSE;
            }
            sockAddr.sin_port = htons((u_short)nHostPort);

            int r=::connect(m_sockfd,(SOCKADDR*)&sockAddr,sizeof(sockAddr));
            if(r!=SOCKET_ERROR) return TRUE;

            int e;
            e=::WSAGetLastError();
            if(e!=WSAEWOULDBLOCK) return FALSE;

            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 100000;

            UINT n=0;
            while( n< CSocketCtrl::m_dwConnectOut)
            {
            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,NULL,&set,NULL, &tout);

            if(e==SOCKET_ERROR) return FALSE;
            if(e>0) return TRUE;

            if( IsIPsChange() )
            return FALSE;
            n += 100;
            }

            return FALSE;

            }
            // 設置監聽socket
            BOOL CSocketCtrl::Listen( int nBackLog )
            {
            if( m_sockfd == INVALID_SOCKET ) return FALSE;
            if( !listen( m_sockfd, nBackLog) ) return TRUE;
            return FALSE;
            }

            // 接收一個新的客戶連接
            BOOL CSocketCtrl::Accept( CSocketCtrl& ms )
            {
            if( m_sockfd == INVALID_SOCKET ) return FALSE;
            if( ms.m_sockfd != INVALID_SOCKET ) return FALSE;

            int e;
            fd_set set;
            timeval tout;
            tout.tv_sec = 0;
            tout.tv_usec = 100000;

            UINT n=0;
            while(n< CSocketCtrl::m_dwAcceptOut)
            {
            //if(stop) return FALSE;
            FD_ZERO(&set);
            FD_SET(m_sockfd,&set);
            e=::select(0,&set,NULL,NULL, &tout);
            if(e==SOCKET_ERROR) return FALSE;
            if(e==1) break;
            n += 100;
            }
            if( n>= CSocketCtrl::m_dwAcceptOut ) return FALSE;

            ms.m_sockfd=accept(m_sockfd,NULL,NULL);
            if(ms.m_sockfd==INVALID_SOCKET) return FALSE;
            ms.SetDefaultOpt();

            return TRUE;
            }

            BOOL CSocketCtrl::Create(void)
            {
            m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
            if(m_sockfd==INVALID_SOCKET) return FALSE;
            SOCKADDR_IN SockAddr;

            memset(&SockAddr,0,sizeof(SockAddr));
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_addr.s_addr = INADDR_ANY;
            SockAddr.sin_port = ::htons(0);
            //if(!::bind(m_sock,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))
            {
            SetDefaultOpt();
            return TRUE;
            }
            Close();
            return FALSE;
            }

            // 設置正確的socket狀態,
            // 主要是主要是設置非阻塞異步傳輸模式
            void CSocketCtrl::SetDefaultOpt()
            {
            struct linger ling;
            ling.l_onoff=1;
            ling.l_linger=0;
            setsockopt( m_sockfd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));
            setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0);
            int bKeepAlive = 1;
            setsockopt( m_sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(int));
            BOOL bNoDelay = TRUE;
            setsockopt( m_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&bNoDelay, sizeof(BOOL));
            unsigned long nonblock=1;
            ::ioctlsocket(m_sockfd,FIONBIO,&nonblock);
            }
            ——————————————————————
            今天晚上寫了一些測試代碼,
            想看看flash究竟能夠承受多大的網絡數據傳輸,

            我在flash登陸到服務器以后,
            每隔3毫秒就發送100次100個字符的串 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" 給flash,
            然后在flash里面接收數據的函數里面統計數據,


            var g_nTotalRecvByte = 0;
            var g_time = new Date();
            var g_nStartTime = g_time.getTime();
            var g_nCounter = 0;

            mySocket.onData=function(xmlDoc)
            {
            g_nTotalRecvByte += xmlDoc.length;
            // 每接收超過1k字節的數據,輸出一次信息,
            if( g_nTotalRecvByte-g_nCounter > 1024 )
            {
            g_time = new Date();
            var nPassedTime = g_time.getTime()-g_nStartTime;
            trace( "花費時間:"+nPassedTime+"毫秒" );
            g_nCounter = g_nTotalRecvByte;
            trace( "接收總數:"+g_nTotalRecvByte+"字節" );
            trace( "接收速率:"+g_nTotalRecvByte*1000/nPassedTime+"字節/秒" );

            }
            結果十分令我意外,
            這是截取的一段調試信息,
            //
            花費時間:6953毫秒
            接收總數:343212字節
            接收速率:49361.7143678988字節/秒
            花費時間:7109毫秒
            接收總數:344323字節
            接收速率:48434.800956534字節/秒
            花費時間:7109毫秒
            接收總數:345434字節
            接收速率:48591.0817273878字節/秒
            。。。
            。。。
            。。。
            。。。
            花費時間:8125毫秒
            接收總數:400984字節
            接收速率:49351.8769230769字節/秒
            花費時間:8125毫秒
            接收總數:402095字節
            接收速率:49488.6153846154字節/秒
            花費時間:8125毫秒
            接收總數:403206字節
            接收速率:49625.3538461538字節/秒

            我檢查了幾遍源程序,沒有發現邏輯錯誤,
            如果程序沒有問題的話,
            那么我們得出的結論是,flash的xml socket每秒可以接收至少40K的數據,
            這還沒有計算xmlSocket.onData事件的觸發,調試代碼、信息輸出占用的時間。

            比我想象中快了一個數量級,
            夠用了,
            flash網絡游戲我們可以繼續往下走了
            ——————————————————————
            有朋友問到lag的問題,
            問得很好,不過也不要過于擔心,
            lag的產生有的是因為網絡延遲,
            有的是因為服務器負載過大,
            對于游戲的設計者和開發者來說,
            首先要從設計的角度來避免或者減少lag產生的機會,
            如果lag產生了,
            也不要緊,找到巧妙的辦法騙過玩家的眼睛,
            這也有很多成熟的方法了,
            比如航行預測法,路徑插值等等,
            都可以產生很好的效果,
            還有最后的絕招,就是提高服務器的配置和網絡帶寬,

            從我開發網絡游戲這段時間的經驗來看,
            我們的服務器是vc開發的,
            普通pc跑幾百個玩家,幾百個怪物是沒有問題的,


            又作了一個flash發送的測試,

            網絡游戲的特點是,
            出去的信息比較少,
            進來的信息比較多,

            這個很容易理解,
            人操作游戲的速度是很有限的,
            控制指令的產生也是隨機的,
            離散的,

            但是多人游戲的話,
            因為人多,信息的流量也就區域均勻分布了,

            在昨天接收數據的基礎上,
            我略加修改,
            這次,
            我在_root.enterFrame寫了如下代碼,
            _root.onEnterFrame = function()
            {
            var i;
            for( i = 0; i < 10; i++ )
            mySocket.send( ConvertToMsg( "01234567890123456789012345678901234567890123456789" ) );
            return;
            }

            服務器端要做的是,
            把所有從flash客戶端收到的信息原封不動的返回來,

            這樣,我又可以通過昨天onData里面的統計算法來從側面估算出flash發送數據的能力,
            這里是輸出的數據
            //
            花費時間:30531毫秒
            接收總數:200236字節
            接收速率:6558.44878975468字節/秒
            花費時間:30937毫秒
            接收總數:201290字節
            接收速率:6506.44858906811字節/秒
            花費時間:31140毫秒
            接收總數:202344字節
            接收速率:6497.88053949904字節/秒
            花費時間:31547毫秒
            接收總數:203398字節
            接收速率:6447.45934637208字節/秒

            可以看出來,發送+接收同時做,
            發送速率至少可以達到5k byte/s

            有一點要注意,要非常注意,
            不能讓flash的網絡傳輸滿載,
            所謂滿載就是flash在阻塞運算的時候,
            不斷的有數據從網絡進來,
            而flash又無法在預計的時間內處理我這些信息,
            或者flash發送數據過于頻繁,
            導致服務器端緩沖溢出導致錯誤,

            對于5k的傳輸速率,
            已經足夠了,
            因為我也想不出來有什么產生這么大的數據量,
            而且如果產生了這么大的數據量,
            也就意味著服務器每時每刻都要處理所有的玩家發出的海量數據,
            還要把這些海量數據轉發給其他的玩家,
            已經引起數據爆炸了,
            所以,5k的上傳從設計階段就要避免的,
            我想用flash做的網絡游戲,
            除了動作類游戲可能需要恒定1k以內的上傳速率,
            其他的200個字節/秒以內就可以了,
            ——————————————————————
            使用于Flash的消息結構定義

            我們以前討論過,
            通過消息來傳遞信息,
            消息的結構是
            struct msg
            {
            short nLength; // 2 byte
            DWORD dwId; // 4 byte

            ....
            data
            }

            但是在為flash開發的消息中,
            不能采用這種結構,

            首先Flash xmlSocket只傳輸字符串,
            從xmlSocket的send,onData函數可以看出來,
            發出去的,收進來的都應該是字符串,

            而在服務器端是使用vc,java等高級語言編寫的,
            消息中使用的是二進制數據塊,
            顯然,簡單的使用字符串會帶來問題,

            所以,我們需要制定一套協議,
            就是無論在客戶端還是服務器端,
            都用統一的字符串消息,
            通過解析字符串的方式來傳遞信息,

            我想這就是flash采用xml document來傳輸結構化信息的理由之一,
            xml document描述了一個完整的數據結構,
            而且全部使用的是字符串,
            原來是這樣,怪不得叫做xml socket,
            本來socket和xml完全是不同的概念,
            flash偏偏出了個xml socket,
            一開始令我費解,
            現在,漸漸理解其中奧妙。

            ——————————————————————
            Flash Msg結構定義源代碼和相關函數

            在服務器端,我們為flash定義了一種msg結構,
            使用語言,vc6
            #define MSGMAXSIZE 512
            // 消息頭
            struct MsgHeader
            {
            short stLength;
            MsgHeader():stLength( 0 ){}

            };
            // 消息
            struct Msg
            {
            MsgHeader header;
            short GetLength(){ return header.stLength; }
            };
            // flash 消息
            struct MsgToFlashublic Msg
            {
            // 一個足夠大的緩沖,但是不會被整個發送,
            char szString[MSGMAXSIZE];
            // 計算設置好內容后,內部會計算將要發送部分的長度,
            // 要發送的長度=消息頭大小+字符串長度+1
            void SetString( const char* pszChatString )
            {
            if( strlen( pszChatString ) < MSGMAXSIZE-1 )
            {
            strcpy( szString, pszChatString );
            header.stLength = sizeof( header )+
            (short)strlen( pszChatString )+1;
            }
            }

            };

            在發往flash的消息中,整個處理過后MsgToFlash結構將被發送,
            實踐證明,在flash 客戶端的xmlSocket onData事件中,
            接收到了正確的消息,消息的內容是MasToFlash的szString字段,
            是一個字符串,

            比如在服務器端,
            MsgToFlash msg;
            msg.SetString( "move player0 to 100 100" );
            SendMsg( msg,............. );
            那么,在我們的flash客戶端的onData( xmlDoc )中,
            我們trace( xmlDoc )
            結果是
            move player0 to 100 100


            然后是flash發送消息到服務器,
            我們強調flash只發送字符串,
            這個字符串無論是否內部擁有有效數據,
            服務器都應該首先把消息收下來,
            那就要保證發送給服務器的消息遵循統一的結構,
            在flash客戶端中,
            我們定義一個函數,
            這個函數把一個字符串轉化為服務器可以識別的消息,

            補充:現在我們約定字符串長度都不大于97個字節長度,


            var num_table = new array( "0","1","2","3","4","5","6","7","8","9" );
            function ConvertToMsg( str )
            {
            var l = str.length+3;
            var t = "";
            if( l > 10 )
            t = num_table[Math.floor(l/10)]+num_table[Number(l%10)]+str;
            else
            t = num_table[0]+num_table[l]+str;
            return t;
            }

            比如
            var msg = ConvertToMsg( "client login" );
            我們trace( msg );
            看到的是
            15client login

            為什么是這個結果呢?
            15是消息的長度,
            頭兩個字節是整個消息的長度的asc碼,意思是整個消息有15個字節長,
            然后是信息client login,
            最后是一個0(c語言中的字符串結束符)

            當服務器收到15client login,
            他首先把15給分析出來,
            把"15"字符串轉化為15的數字,
            然后,根據15這個長度把后面的client login讀出來,
            這樣,網絡傳輸的底層就完成了,
            client login的處理就交給邏輯層,
            ——————————————————————
            前陣子我開發了Match3D,
            一個可以把三維動畫輸出成為swf的工具,
            而且實現了swf渲染的實時三維角色動畫,
            這可以說是我真正推出的第一個flash第三方軟件,
            其實這以前,
            我曾經開發過幾個其他的flash第三方軟件,
            都中途停止了,
            因為不實用或者市場上有更好的同類軟件,

            隨著互聯網的發展,
            flash的不斷升級,
            我的flash第三方軟件目光漸漸的從美術開發工具轉移到網絡互連,
            web應用上面來,
            如今已經到了2004版本,
            flash的種種新特性讓我眼前發光,

            我最近在帝國的各個板塊看了很多貼子,
            分析里面潛在的用戶需求,
            總結了以下的幾個我認為比較有意義的選題,
            可能很片面,

            flash源代碼保護,主要是為了抵御asv之類的軟件進行反編譯和萃取
            flash與遠端數據庫的配合,應該出現一個能夠方便快捷的對遠程數據庫進行操作的方法或者控件,
            flash網際互連,我認為flash網絡游戲是一塊金子,

            這里我想談談flash網絡游戲,
            我要談的不僅僅是技術,而是一個概念,
            用flash網絡游戲,
            我本身并不想把flash游戲做成rpg或者其他劇烈交互性的游戲,
            而是想讓flash實現那些節奏緩慢,玩法簡單的游戲,
            把網絡的概念帶進來,

            你想玩游戲的時候,登上flash網絡游戲的網站,
            選擇你想玩的網絡游戲,
            因為現在幾乎所有上網的電腦都可以播放swf,
            所以,我們幾乎不用下載任何插件,
            輸入你的賬號和密碼,
            就可以開始玩了,

            我覺得battle.net那種方式很適合flash,
            開房間或者進入別人開的房間,
            然后2個人或者4個人就可以交戰了,

            這種游戲可以是棋類,這是最基本的,
            用戶很廣泛,
            我腦海中的那種是類似與寵物飼養的,
            就像當年的電子寵物,
            每個玩家都可以到服務器認養寵物,
            然后在線養成寵物,
            還可以邀請別的玩家進行寵物比武,
            看誰的寵物厲害,

            就這樣簡簡單單的模式,
            配合清新可愛的畫面,
            趣味的玩法,
            加入網絡的要素,
            也許可以取得以想不到的效果,

            今天就說到這里吧,
            想法那么多,要實現的話還有很多路要走,

            希望大家多多支持,積極參與,
            讓我們的想法不僅僅停留于紙上。
            ——————————————————————
            格斗類游戲和休閑類游戲不同,
            最大的差別在于對響應時間的要求不在同一數量級上,

            休閑類游戲允許很大的延遲,
            而動作類游戲需要盡可能小的延遲,

            服務器的作用,
            要起到數據轉發和數據校驗的作用,
            在格斗游戲設計上,
            如果采用和mmorpg相同的服務器結構,
            會產生較大的延遲,
            你可以想象,當我打出一招升龍拳,
            然后把這個信息傳遞給服務器,
            假設有100毫秒的延遲,
            服務器收到以后,
            轉發給其他的人,
            又經過100毫秒的延遲,
            也就是說,
            你打出一招,對方要得知需要200毫秒的時間,
            實際情況也許更糟,
            這對于格斗類游戲來說是很大的,
            所以現在市面上幾乎沒有真正意義上的多人格斗類網絡游戲,

            如果要實現格斗的感覺,
            盡可能減少延遲,
            有很多現有經過驗證的方法,
            比如把服務器建立在兩個對戰玩家的機器上,
            這樣就省掉了一個延遲,
            或者每個玩家機器上都建一個服務器,

            局域網摸使得聯機游戲很多采用這種的,
            比如星際爭霸,它的聯網模式服務器就是建在玩家主機上的,

            減少網絡延遲是必要的,
            但是,一個平滑爽快的網絡游戲,
            他的客戶端需要有很多預測的策略,
            用來估計未來很短的時間內,
            某個玩家會做什么,
            這樣,就可以在不等待服務器返回正確信息的情況下,
            直接估計出正確的結果或者接近正確的結果,
            當服務器返回的結果和預測結果的誤差在很小范圍內,
            則預測成功,否則,可以強行校正客戶端的狀態,
            這樣做,可以盡量減少網絡延遲帶來的損失,
            絕大部分成功的聯網游戲,
            在這個策略上,都花了很大的功夫,
            ——————————————————————
            你提到的那個游戲模式是經典模式battle.net,
            也就是戰網,
            暗黑,星際,魔獸都有戰網,

            你下面提到的是一個緩沖模式,
            當用戶發出一招升龍拳,
            并不立即向服務器發送指令,
            而是等待5ms(不過在網絡游戲中5ms設定也許太樂觀),
            如果在這期間服務器下發了新指令,
            與在緩沖中的升龍拳指令進行運算,
            用來得到是否可以發出升龍拳的評估,
            這樣做是可以的,
            不過實際開發過程中,
            這種緩沖統計的模式,
            邏輯非常的復雜,
            網絡游戲畢竟是在網絡上玩的,
            而且是多人玩的,


            你如果經常上戰網打星際的話,
            會知道它的菜單里面有一個設定操作延遲的菜單,
            高延遲的話,
            你的操作會緩沖到一定的程度然后統一向服務器發出,
            低延遲的話,
            會盡可能的立刻把你的操作向服務器發出,

            我的感覺是,
            網絡格斗類游戲與單機格斗類游戲是不同的,
            想在網絡上完美實現單機格斗游戲的內涵難度極大,
            對于開發者來說,絕對是一個挑戰,
            我想,可以積極的考慮如何在游戲設計的層面去規避一些問題。
            ——————————————————————
            我的最近一條諺語是西點軍校的軍官對新兵的訓斥,
            。。。。如果一個愚蠢的方法能夠奏效的話,那他就不是愚蠢的方法。。

            這句話簡直太妙了,
            我立刻刪除了手中那些“完美的”代碼,
            用最簡單的方法快速達到我的目的,
            雖然新寫的代碼張得很丑陋,

            可以說說題外話,
            最近策劃一直希望能夠實現一個實時渲染的怪物能夠時不時的眨巴眨巴眼睛

            主流的想法是更換眼部的貼圖,
            也有使用骨骼控制眼部肌肉的方法,
            同時還有其他“技術含量”更高的解決方案,
            不過經過評估,實現這兩者難度不大,但是需要耗費比較多的資源和制作時間,更嚴重點的是程序需要升級代碼來支持這個新的特性,
            后來,在大家集思廣益之下,
            有人提出了高明的辦法,

            把怪物的眼睛鏤空,
            然后在后面放一個畫有眼睛的矩形,
            這個矩形的正面是一個睜大的眼睛,
            而反面是一個閉上的眼睛
            這樣,眨眼就是這個矩形正反翻轉的過程,
            不錯,是個好方法,讓人眼前一亮,
            大家一致通過,
            在現有技術下,這個“愚蠢”的方法解決了這個“復雜”的問題,

            在有一個更有趣的例子,
            看似調侃,其實折射出人的智慧,
            日本人做游戲是非常講究細節的,
            他們最早做三維格斗游戲的時候遇到一個問題,
            當我打出一記重拳,命中的時候,
            我的拳頭在畫面上嵌入到敵方模型的肉里面去了,
            日本人覺得不能接受,拳頭怎么能打到肉里面去呢,
            應該剛剛好碰到敵方的身體才行,
            可是大家想想,想要剛剛好碰到,
            談何容易,
            美術要求嚴格不說,程序可能要精確的計算,
            還不一定能夠滿足要求,
            于是高招出現了,嵌到肉里就嵌到肉里吧,
            我給你來一個閃光,把有問題的畫面給你全遮住,
            這招可真絕,
            美工,程序,策劃不但偷了懶,把玩家給騙了不說,
            玩家還覺得這個游戲真酷,打中了還有閃光的特效,
            ;),實在是高,

            都一點了,我去睡了,
            ——————————————————————
            關于flash com server、remoting
            最近研究了一下flash com server
            是個好東西,
            他提供了集成的flash網絡服務,

            我認為,flash com server最具吸引我的是他的媒體能力,

            案例
            使用flash作為客戶端的圍棋游戲,
            flash實現這個游戲的界面和玩法是沒有問題的,
            但是計算圍棋的死活需要大量運算,
            這恰恰不是flash的強項,
            而且我們是網絡游戲,
            我們會把計算交給flash comm server,
            但是flash comm server的負擔也很重,
            他要承擔視頻,音頻和數據流的傳輸,
            而且他是一個面向web應用的服務器,
            并不適合做大量的計算,
            所以我們需要一個專門進行運算的服務器,


            remoting是一個網關,
            用戶可以通過flash com server+remoting間接的取得應用程序服務器的服務,

            flash客戶端可以僅僅和com server交流,
            不必關心其他應用程序服務器的存在,

            不過因為要實現應用程序服務器,
            就要用到remoting,
            而mm提供的remoting有for java的和for c#的,
            我本人對java和c#不了解,
            我只會使用c++,
            所以我設計的體系就放棄了remoting,


            現在這個交互性很強的flash娛樂平臺的體系建立起來了,
            flash comm server提供媒體的服務,
            比如視頻,音頻和數據流,
            使用xml socket直連由vc開發的應用服務器,
            這個應用服務器提供計算服務,

            這樣,就把現在網絡游戲服務器的大吞吐量的運算和flash com server的媒體服務融合到一起,

            網絡游戲盈利模式明確是眾所周知的,
            而且運營模式已經比較成熟了,

            我認為以flash為客戶端的休閑娛樂平臺一定是很有商業前景的,
            而在此貼中,一個由flash構成似于聯眾的網絡游戲平臺就這樣誕生了,
            他的特點是不需要安裝專門的插件(幾乎所有的上網電腦都有flashplayer),
            圖形表現力強,
            客戶端可以實時下載,
            這種模式可以統一到現在的網絡游戲收費模式中,
            因為至此flash的網絡游戲和普通的網絡游戲沒有什么區別了,
            要玩游戲就要買點卡,
            我們還可以提供視頻服務(這種服務可能要付更多的錢),
            一邊玩,一邊視頻聊天,

            這種平臺用來運作那些節奏稍慢的休閑益智類網絡游戲再適合不過了,
            比如紙牌,棋類,
            flash客戶端開發成本低,
            運行期間穩定,占用資源少,

            我現在已經擁有成熟的技術和解決方案,
            最近考慮投資作這個項目,
            如果有對此感興趣的或者想進行商業合作的朋友,
            發email到gamemind@sina.com聯系我
            ?
            ?????????????????????????????????????? 閃之主宰

            posted on 2007-02-05 15:33 楊粼波 閱讀(993) 評論(0)  編輯 收藏 引用

            久久久精品人妻无码专区不卡| 精品国产乱码久久久久久郑州公司| 99久久99这里只有免费费精品 | 久久人人爽人人精品视频| 伊人色综合久久| 狠狠色婷婷久久一区二区| 国产亚洲欧美精品久久久| 国内精品久久久久久中文字幕| 久久久久久亚洲精品影院| 国产午夜免费高清久久影院| 国产精久久一区二区三区| 久久久久久久女国产乱让韩| 久久久久夜夜夜精品国产| 久久AV无码精品人妻糸列| 国产叼嘿久久精品久久| 久久夜色精品国产噜噜亚洲AV| 丁香久久婷婷国产午夜视频| 五月丁香综合激情六月久久| 久久久WWW免费人成精品| 成人妇女免费播放久久久| 久久精品亚洲AV久久久无码| 99久久国产亚洲高清观看2024| 久久久久av无码免费网| 久久精品一区二区影院| 亚洲国产精品久久久久| 色偷偷偷久久伊人大杳蕉| 亚洲色欲久久久久综合网| 国产真实乱对白精彩久久| 久久综合九色综合精品| 久久国产精品99精品国产| 久久久久av无码免费网| 日本五月天婷久久网站| 亚洲国产综合久久天堂| 久久久久亚洲AV成人网| 久久精品国产99久久丝袜| 精品久久久久中文字幕一区| 国产真实乱对白精彩久久| 国产精品欧美亚洲韩国日本久久| 7国产欧美日韩综合天堂中文久久久久| 少妇久久久久久久久久| 亚洲AV无码1区2区久久|