• <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>
            教父的告白
            一切都是紙老虎
            posts - 82,  comments - 7,  trackbacks - 0
            一直以來,flash就是我非常喜愛的平臺,
            因為他簡單,完整,但是功能強大,
            很適合游戲軟件的開發,
            只不過處理復雜的算法和海量數據的時候,
            速度慢了一些,
            但是這并不意味著flash不能做,
            我們需要變通的方法去讓flash做不善長的事情,

            這個貼子用來專門討論用flash作為客戶端來開發網絡游戲,
            持續時間也不會很長,在把服務器端的源代碼公開完以后,
            就告一段落,
            注意,僅僅用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 ); 接收服務器傳來的消息

            項目開發的基本硬件配置
            一臺普通的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。
            阻塞的方式會引起系統的停頓,一般網絡游戲里面使用的都是非阻塞的方式,


            [有狀態服務器和無狀態服務器]
            在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的處理就交給邏輯層,


            謝謝大家的支持,
            很感謝斑竹把這個貼子置頂,
            我寫這文章的過程也是我自己摸索的過程,
            文章可以記錄我一段開發的歷史,
            一個思考分析的歷程,
            有時候甚至作為日志來寫,

            由于我本身有雜務在身,
            所以貼子的更新有點慢,
            請大家見諒,

            我喜愛flash,
            雖然我在帝國中,但我并不能稱之為閃客,
            因為我制作flash的水平實在很低,
            但是我想設計開發出讓其他人能更好的使用flash的工具,

            前陣子我開發了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個人就可以交戰了,

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

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

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

            希望大家多多支持,積極參與,
            讓我們的想法不僅僅停留于紙上。
            大家好,
            非常抱歉,
            都很長時間沒有回貼了,
            因為手頭項目的原因,
            幾乎沒有時間做flash multiplayer的研究,

            很感謝大家的支持,
            現在把整個flash networking的源代碼共享出來,
            大家可以任意的使用,
            其實里面也沒有多少東西,
            相信感興趣的朋友還是可以從中找到一些有用的東西,

            這一次的源代碼做的事情很簡單,
            服務器運行,
            客戶端登陸到服務器,
            然后客戶端不斷的發送字符串給服務器,
            服務器收到后,在發還給客戶端,
            客戶端統計一些數據,
            posted on 2009-09-23 23:43 暗夜教父 閱讀(603) 評論(0)  編輯 收藏 引用 所屬分類: Game Development

            <2009年9月>
            303112345
            6789101112
            13141516171819
            20212223242526
            27282930123
            45678910

            常用鏈接

            留言簿(2)

            隨筆分類

            隨筆檔案

            文章分類

            文章檔案

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            2020久久精品亚洲热综合一本| 国产午夜久久影院| 国产美女久久久| 久久精品国产久精国产思思| 狠狠色婷婷久久综合频道日韩| 国产精品久久久香蕉| 无码任你躁久久久久久老妇| 亚洲国产精品成人久久蜜臀 | 亚洲中文字幕无码久久2017| 久久久久亚洲国产| 亚洲中文久久精品无码ww16| 麻豆亚洲AV永久无码精品久久| 中文字幕日本人妻久久久免费| 国产激情久久久久久熟女老人 | 国产亚洲精午夜久久久久久 | 99精品国产在热久久| 国内精品久久久久影院免费| 久久免费视频观看| 久久久久免费视频| 亚洲欧美伊人久久综合一区二区 | 久久亚洲精品中文字幕三区| 99久久精品免费国产大片| 久久无码国产| 精品熟女少妇AV免费久久| 91视频国产91久久久| 久久嫩草影院免费看夜色| 一本久久知道综合久久| 久久se精品一区精品二区| 久久亚洲精品无码观看不卡| 亚洲av成人无码久久精品| 久久青草国产精品一区| 偷偷做久久久久网站| 久久久久久久99精品免费观看| 色综合久久天天综线观看| 久久久婷婷五月亚洲97号色| 国产精品亚洲美女久久久| 亚洲精品午夜国产VA久久成人| 99久久99久久精品国产片果冻| 亚洲va久久久噜噜噜久久男同| 国产福利电影一区二区三区,免费久久久久久久精 | 久久天堂电影网|