QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 8981|回复: 32

创建适合网络游戏使用的套接字类库

[复制链接]
发表于 2003-10-7 19:10:12 | 显示全部楼层 |阅读模式
在网络应用开发中,随着需求的改变及性能的提高,程序变得越来越复杂,越来越大。从而导致编译和连接时间长。为更有效地重用代码,应该建立相应的类库。库是包括不同对象的文件。它可作为一个实体进行连接,因而可以极大地提高连接速度。Linux系统可以创建两种库:静态连接库和动态连接库。
    为了有效地重用代码,我们采用面向对象地编程方法来实现套接字类库。
    由于Linux系统提供的网络API均是C库形式,因此,第一次必须将C形式地系统调用封装到相应地类中,即Wrapper类。在此基础上建立一些公用类。然后,可以根据实际应用的情况,创建一些应用类。另外,还可以创建一些基类用于类的管理或特殊用途。
1 . Wrapper类
根据以上思想,我们建立套接字的Wrapper类,主要包括以下一些类。
        MySocket类:封装基础的套接字系统调用,并且实现异常处理。为了满足不同需要,提供了多种形式的函数调用。
        MyThread类:封装了POSIX线程的系统调用以实现多线程,并且提供了简单明了的方法来产生线程。
        MyMutex类:提供实现互斥锁的基本方法。
        MyCondition类:提供实现条件变量的基本方法。
2 . 公用类
公用类主要包括以下类。
        TcpServThr类:提供实现基于TCP多线程服务器的方法。
        TcpCliThr类:提供实现基于TCP多线程客户的方法。
        MessageQue类:提供实现带互斥锁的队列的方法。
3 . 应用类
        GameServer类:网络游戏的服务器类,是一个TCP多线程并发服务器。
        GameClient类:网络游戏的客户类,是一个基于TCP多线程客户。
另外,还实现了一个基类Thread_interface,用于更灵活地产生线程。
4 . 各类的关系
从图中可以看出,TcpServThr类和TcpCliThr类从MySocket继承而来,可以方便地实现网络操作。它们包括以下两个内部类。
        Receiver类:用于产生接收客户数据地线程。
        Sender类:用于产生向客户发送数据地线程。
这两个内部类从MyThread继承而来,用于实现多线程。由于这两个内部类仅为了方便地使用TcpServThr类和TcpCliThr类,并不具有普遍意义,因此将它们定义为内部的。在MessageQue类中引用了MyMutex类,以实现互斥锁来保护队列数据的完整性。
可以通过引用关系或继承关系生成新的类,例如继承MySocket类以生成多进程并发服务器。也可以对已有的类进行修改从而增加新的功能,使其更加完善。由此看出,建立套接字类库能迅速地产生新的应用,也能方便地对原系统进行升级,程序也具有良好的结构,便于阅读和调试,从而极大地提高开发效率。





套接字系统调用:MySocket类
MySocket类是该套接字类库的核心,它封装了与套接字相关的主要系统调用。其类声明源代码见如下:
[code:1]
/*File : mysocket.h*/
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>

/*Define constant*/
const unsigned MysBUF_SIZE=1024;               //固定字串缓冲区长度
const unsigned MysMAX_NAME_LEN=256;            //最大名字长度
const unsigned MysRX_BUF_SIZE=4096;            //默认接收缓冲区大小
const unsigned MysTX_BUF_SIZE=4096;            //默认发送缓冲区大小
const unsigned MAXCONN=5;                      //默认最大连接数
const unsigned MysSOCKET_DEFAULT_PORT=1234;    //默认端口号

/*Define Error*/
enum MySocketError{                          //定义枚举类型MySocketError,包括所有自定义错误码
  MySOCKET_NO_ERROR=0,
  MySOCKET_INVALID_ERROR_CODE,

  //socket error codes
  MySOCKET_ADDEPT_ERROR,
  MySOCKET_BIND_ERROR,
  MySOCKET_BUFOVER_ERROR,
  MySOCKET_CONNECT_ERROR,
  MySOCKET_FILESYSTEM_ERROR,
  MySOCKET_GETOPTION_ERROR,
  MySOCKET_HOSTNAME_ERROR,
  MySOCKET_INIT_ERROR,
  MySOCKET_LISTEN_ERROR,
  MySOCKET_PEERNAME_ERROR,
  MySOCKET_PROTOCOL_ERROR,
  MySOCKET_RECEIVE_ERROR,
  MySOCKET_REQUEST_TIMEOUT,
  MySOCKET_SERVICE_ERROR,
  MySOCKET_SETOPTOPN_ERROR,
  MySOCKET_SOCKNAME_ERROR,
  MySOCKET_SOCKETTYPE_ERROR,
  MySOCKET_TRANSMIT_ERROR,
};

/*****************************************************
Class name:MySocket
Function:A wrapper class of basoc socket function.
*****************************************************/
class MySocket                      //定义MySocket类,其成员函数包括以下几类
{
protected:
  sa_family_t address_family;
  int protocol_family;
  int socket_type;
  int port_number;
  int Mysocket;
  int conn_socket;
  MySocketError socket_error;

protected:
  int bytes_read;
  int bytes_moved;
  int is_connected;
  int is_bound;

public:
  sockaddr_in sin;
  sockaddr_in remote_sin;

public:
  MySocket();
  MySocket(int st,int port,char *hostname=0);
  MySocket(sa_family_t af,int st,int pf,int port,char* hostname=0);
  virtual ~MySocket();

public:
  void SetAddressFamily(sa_family_t af) {address_family=af;}
  void SetProtocolFamily(int pf) {protocol_family=pf;}
  int GetProtocolFamily(){return protocol_family;}
  void SetSocketType(int st){socket_type=st;}
  int GetSocketType(){return socket_type;}
  void SetPortNumber(int p){port_number=p;}
  int GetBoundSocket(){return Mysocket;}
  int GetSocket(){return Mysocket;}
  int GetRemoteSocket{return conn_socket;}

public://Socket fuction
  int Socket();
  int InitSocket(int st,int port,char *hostname=0);
  int InitSocket(sa_family_t af, int st, int pf, int port ,char* hostname=0);
  void Close();
  void Close(int &s);
  void CloseSocket();
  void CloseRemoteSocket();
  int Bind();
  int Connect();
  int Accept();
  int Listen(int mac_connections=MAXCONN);
  void ShutDown(int how=0);
  void ShutDown(int &s,int how=0);
  void ShutDownSocket(int how=0);
  void ShutCownRemoteSocket(int how=0);
  int GetSockOpt(int s, int level, int optName, void *optVal, unsigned *optLen);
  int GetSockOpt(int level, int optName, void *optVal, unsigned *optLen);
  int SetSockOpt(int s, int level, int optName,const void *optVal, unsigned optLen);
  int SetSockOpt(int level, int optName,const void *optVal, unsigned optLen);

  //Stream function
  int Recv(void *buf, int bytes, int flags=0);//just for client
  int Recv(int s, void *buf, int bytes, int flags=0);
  int Recv(void *buf, int bytes,int seconds,int useconds,int flags=0);//just for client
  int Recv(int s,void *buf, int bytes,int seconds,int useconds,int flags=0);
  int Send(const void *buf, int bytes, int flags=0);//just for client
  int Send(int s,const void *buf, int bytes, int flags=0);
  int RemoteRecv(void *buf,int bytes,int seconds,int useconds, int flags=0);//just for client
  int RemoteRecv(void *buf,int bytes,int flags=0);//just for server
  int RemoteRecv(const void *buf,int bytes,int flags=0);
  void ResetRead(){bytes_read=0;}
  void ResetWrite(){bytes_moved=0;}

  //information database
  int GetPeerName(int s, sockaddr_in *sa);
  int GetPeerName();
  int GetSockName(int s, sockaddr_in *sa);
  int GetSockName();
  int GetServByName(char *name,char *protocol=0);
  int GetServByName(int port, char *protocol=0);
  servent *GetServiceInformation(char *name,char *protocol=0);
  servent *GetServiceInformation(int port, char *protocol=0);
  int GetPortNumber();
  int GetHostName(char *sbuf);
  int GetIPAddress(char *sbuf);
  int GetDomainName(char *sbuf);
  int GetBoundIPAddress(char *sbuf);
  int GetRemoteHostName(char *sbuf);
  hostent *GetHostInformation(char *hostname);
  void GetClientInfo(char *client_name,int &r_port);
  sa_family_t GetAddressFamily();
  sa_family_t GetRemoteAddressFamily();

  //Process control functions
  int ReadSelect(int s, int seconds, int useconds);
  int BytesRead(){return bytes_read;}
  int BytesMoved(){return bytes_moved;}
  int SetBytesRead(int bytes=0){return bytes_read=bytes;}
  int SetBytesMoved(int bytes=0){return bytes_moves=bytes;}
  int *GetBytesRead(){return &bytes_read;}
  int *GetBytesMoved(){return &bytes_moved;}
  int IsConnected(){return is_connected==-1;}
  int IsBound(){return is_bound==1;}
  int SetSocket(int s){return Mysocket=s;}
  int SetRemoteSocket(int s){return conn_socket=s;}
  void ReleaseSocket(){Mysocket=(int)-1;}
  void ReleaseRemoteSocket(){conn_socket=(int)-1;}

  //Datagram fuctions
  int RecvFrom(int s,sockaddr_in *sa, void *buf,int bytes,int seconds,int useconds,int flags=0);
  int RecvFrom(int s,sockaddr_in *sa, void *buf,int bytes,int flags=0);
  int SendTo(int s, sockaddr_in *sa, void *buf);
  int RecvFrom(void *buf, int bytes, int flags=0);
  int RecvFrom(void *buf, int bytes, int seconds, int useconds, int flags=0);
  int SendTo(void *buf, int bytes, int flages);

  //Exception handling functions
  MySocketError GetSocketError(){return socket_error;}
  MySocketError GetSocketError() const {return socket_error;}
  MySocketError SetSocketError(MySocketError err){
    return socket_error=err;
  }
  MysocketError ResetSocketError(){
    return socket_error=MySOCKET_NO_ERROR;
  }
  MysocketError ResetError(){
    return socket_error=MySOCKET_NO_ERROR;
  }

};
[/code:1]
1.        MySocket类的成员变量
在一个类中,成员函数代表了该类的特性及状态。MySocket类的成员变量存放与套接字相关的信息,其内容如下:
        address_family:存放地址簇,如AF_INET。
        protocol_family:存放协议簇,如IPPROTO_TCP。
        socket_type:套接字类型,如SOCK_STREAM、SOCK_DGRAM。
        port_number:端口号。
        Mysocket:套接字的描述符。对于SOCK_STREAM类型的套接字为侦听套接字描述符。
        conn_socket:连接套接字描述符。对于SOCK_DGRAM类型的套接字无效。
        socket_error:自定义的套接字错误码。
        bytes_read:读入的字节数。
        bytes_move:写出的字节数。
        is_connected:是否已连接。0为未连接,否则已连接。
        is_bound:套接字是否已绑定。
        sin:本地套接字地址。
        remote_sin:远程套接字地址。
2.        MySocket类实现的功能
MySocket类所实现的功能分为以下几类。
        参数设置。用于设置与套接字相关的参数,如SetAddressFamily()、SetProtocolFamily()、SetPortNumber()等。
        套接字功能。用于初始化/关闭套接字,以及绑定连接等操作,如InitSocket()、Close()、Bind()、Connect()、Accept()、Listen()、ShutDown()等。
        基于流的I/O功能。用于TCP的读写操作,如Recv()、Send()、RemoteRecv()、RemoteSend()等。
        套接字信息数据库功能。用于获取与套接字相关的参数,如GetPeerName()、GetSockName()、GetIPAddress()等。
        套接字状态功能。用于获得当前套接字状态,如IsConnedted()、IsBound()、SetSocket()等。
        基于数据报I/O功能。用于UDP的读写操作,如RecvFrom()、SendTo()等。
        异常处理功能。用于获取或设置错误码,如GetSocketError()、SetSocketError()等。
3.        利用OOP的重载功能
为了适应不同的用途,同一系统调用被封装到不同的成员函数中,这些函数使用同一名称却又不同的参数列表,这是利用OOP的重载实现的。例如,基于TCP的读功能有四个成员函数:
        int Recv(void *buf,int bytes,int flags=0):只有当MySocket类为客户所使用时,才调用该函数。它从MySocket套接字中读数据。
        int Recv(void *buf,int bytesmint seconds,int useconds,int flags=0):与上一函数一样,但增加了超时处理功能。
        int Recv(int s,void *buf,int bytes,int flags=0):从一指定的套接字中读数据。
        int Recv(int s,void *buf,int bytesmint seconds,int useconds,int flags=0):与上一函数一样,但增加了超时处理功能。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| 发表于 2003-10-7 19:29:17 | 显示全部楼层
由于,源代码太长,我打算将它们放到https://gro.clinux.org/上,现在正申请中,过两天我会给出连接
回复

使用道具 举报

 楼主| 发表于 2003-10-7 21:54:10 | 显示全部楼层
刚下载了hl2泄漏的源代码,希望可以找到网络方面的有用东东
回复

使用道具 举报

发表于 2003-10-8 22:50:11 | 显示全部楼层
汗,这么多代码~~

我想问一下,现有的网络有没有提供一种保障,能够过滤掉伪造的数据包?
回复

使用道具 举报

 楼主| 发表于 2003-10-9 18:31:17 | 显示全部楼层
其实,本来不需要这么多代码,但是从更有效地重用代码,提高连接效率,以及代码的重用性考虑,还是建立一个动态联接库比较好一些……
至于sjinny所说的
我想问一下,现有的网络有没有提供一种保障,能够过滤掉伪造的数据包?

的问题,我还没有考虑,不是说先开发出Demo版试一试吗?我想Demo版出来以后再考虑,应该有各种加密算法可以解决的
回复

使用道具 举报

 楼主| 发表于 2003-10-9 19:45:00 | 显示全部楼层
[quote:fe96bb861d="deaboway"]由于,源代码太长,我打算将它们放到https://gro.clinux.org/上,现在正申请中,过两天我会给出连接 [/quote]
新的项目已经见好了,但是由于我是通过代理服务器上的网,cvs不能正常同步,郁闷ing,谁能告诉我设置方法,感激不尽~~~
错误过程如下:
[code:1][root@www root]# tcsh
[root@www ~]# setenv CVSROOT /path/to/cvsroot
[root@www ~]# bash
[root@www root]# CVSROOT=/path/to/cvsroot ; export CVSROOT
[root@www root]# CVSROOT=:ext:[email protected]:/cvs/socketb
yd CVS_RSH=ssh; export CVSROOT CVS_RSH
[root@www root]# cvs init
ssh: connect to host cvs.SocketByD.gro.clinux.org port 22: Connection timed out
cvs [init aborted]: end of file from server (consult above messages if any)[/code:1]

链接:https://gro.clinux.org/projects/socketbyd/
回复

使用道具 举报

发表于 2003-10-9 22:38:07 | 显示全部楼层
目前有个问题,服务器端,每个对象都要有一个id,得是唯一的;另外,每个数据包里得有一个数据,用来确认发送者的身份。这两个数据怎么弄呢?至少得知道方向,这样才能给以后的开发留下一条道路。
另外,如果使用TCP连接,那么有几个问题要考虑:
如果一个客户端一个连接,那么假如连接数峰值为1000,那么会不会太占资源?
如果用连接池,那么就会经常性的建立和关闭连接,那么这些操作会不会太占资源?
如果采用UDP,是否可以避免  经常性的建立和关闭连接 所造成的 速度和资源占用问题  ?

假如服务器和一个客户端建立了TCP连接,那么这时时候会通过这个连接接收到其他客户端伪造的数据包呢?我猜想这个连接之外的机器应该不能把数据发送到这个连接(我指socket)上吧?
回复

使用道具 举报

发表于 2003-10-10 13:30:16 | 显示全部楼层
我想,最好把用户的身份认证功能放进网络模块,因为我想可能会和网络连接有一定的联系;这样当网络模块从网络收到一个数据包时,会自动分析出它的发送者是谁,并把这个发送者的标识信息连同(解密和解压后的)数据包一起传出来。这样,用户的上线、下线、断线等也由网络模块负责,只是在事件发生时给出相应的信号。
回复

使用道具 举报

 楼主| 发表于 2003-10-10 20:04:30 | 显示全部楼层
目前有个问题,服务器端,每个对象都要有一个id,得是唯一的;另外,每个数据包里得有一个数据,用来确认发送者的身份。这两个数据怎么弄呢?至少得知道方向,这样才能给以后的开发留下一条道路。

这个问题完全可以解决,在继承TcpCliThr类和TcpServThr类时,可以通过重载虚函数run()的方式,得到客户端的标识。或者更好的方法(完全无后顾之忧~)就是再建立一个封装类——MySecrit(),,然后继承之,我去找一下这方面的资料~
如果一个客户端一个连接,那么假如连接数峰值为1000,那么会不会太占资源?
如果用连接池,那么就会经常性的建立和关闭连接,那么这些操作会不会太占资源?
如果采用UDP,是否可以避免 经常性的建立和关闭连接 所造成的 速度和资源占用问题 ?

tcp和udp并没有本质的区别,udp只是在tcp的基础上省去了一些东西。
连接峰值一般不会达到一百,据我所知,一般一台服务器最多连接300个就不错了~
我想,最好把用户的身份认证功能放进网络模块,因为我想可能会和网络连接有一定的联系;这样当网络模块从网络收到一个数据包时,会自动分析出它的发送者是谁,并把这个发送者的标识信息连同(解密和解压后的)数据包一起传出来。这样,用户的上线、下线、断线等也由网络模块负责,只是在事件发生时给出相应的信号。

不会吧,这个问题—— 后面的我可以接受,但是“用户的身份认证功能”与数据库的联系好像更加紧密,所以
回复

使用道具 举报

发表于 2003-10-10 23:17:33 | 显示全部楼层
关于安全性的问题,我是这么想的:
如果给每个客户端建立一个TCP连接,并且能够保证这个连接的安全性(就是不会有伪造数据包的问题),那么可以把和这个用户相对应的对象的地址和这个连接的socket保存到一起(使用类封装起来),这样每次这个连接收到了数据包,就使用相应的用户对象,就不再分析发送者身份了。
不过这里有两个关键:
1。 在建立连接时,需要用户给出用户名和密码,然后进行认证(应该和数据库有关了),在这个过程中,用户名和密码要进行加密后再传送,通过认证后,按照数据库里这个用户的信息建立一个用户对象,加入到游戏世界中,并且把这个对象的地址和这个连接保存到一起。在销毁这个连接时(下线或掉线时)把用户对象的信息保存到数据库中然后再把这个用户对象销毁。
所以,要保证上线时连接的安全性,还要做好用户身份认证的工作。
2。 在建立连接后,要维护好这个连接的安全性,具体是:过滤掉伪造的数据包,及时发现掉线问题。

关于在线人数,我觉得即使别的游戏只有300多人同时在线,但是从程序设计的角度,最好能够设计出完善的机制以支持尽量多的用户同时在线。
回复

使用道具 举报

发表于 2003-10-11 20:42:39 | 显示全部楼层
以上两个关键问题,现有技术能有办法解决吗?
回复

使用道具 举报

 楼主| 发表于 2003-10-12 21:51:31 | 显示全部楼层
[quote:f1f8c9f347="sjinny"]关于安全性的问题,我是这么想的:
1。 在建立连接时,需要用户给出用户名和密码,然后进行认证(应该和数据库有关了),在这个过程中,用户名和密码要进行加密后再传送,通过认证后,按照数据库里这个用户的信息建立一个用户对象,加入到游戏世界中,并且把这个对象的地址和这个连接保存到一起。在销毁这个连接时(下线或掉线时)把用户对象的信息保存到数据库中然后再把这个用户对象销毁。
所以,要保证上线时连接的安全性,还要做好用户身份认证的工作。
2。 在建立连接后,要维护好这个连接的安全性,具体是:过滤掉伪造的数据包,及时发现掉线问题。关于在线人数,我觉得即使别的游戏只有300多人同时在线,但是从程序设计的角度,最好能够设计出完善的机制以支持尽量多的用户同时在线。[/quote]
sjinny,你所说的都是网络安全的问题,我还没有考虑,难道你想在Demo中就考虑使用它吗? 我想还是把Demo先做出来吧   
我们的网络模块,我已经将源代码写好了,还没有调试,不知能不能通过,效果如何……,调试成功后我还是把源代码都放到这里来吧,就怕太大把屏幕撑暴了 或者发给你,你帮我放到我的项目Cvs中也行 或者你建一个新的网络项目就更好拉。
还有,好久没有见到rocklgk了,不知他的进度如何了……感觉我们人还是太少了,Demo中也要有初步的数据库模块的内容,不然搞不起来,所以sjinny兄还要多宣传一下,吸呐多一些的成员才好,我可不希望项目不了了之啊 :-(     项目仅靠一俩个人是不行的,难道没有其他人感兴趣了吗?
当然,我也不知道你那边完成的怎样?可以介绍介绍吗?
最后——我们要团结,要努力…… 鼓舞一下士气!
回复

使用道具 举报

 楼主| 发表于 2003-10-12 22:02:19 | 显示全部楼层
今天考完了高级程序员,不知道能不能顺利通过……无论如何感觉轻松了很多 不过我们有几个课程设计要做了——有我说过的小世界模型,Ems加密算法,还有我又加入了学校的PDA兴趣小组,我负责操作系统的开发,有机会我会把前两个放上来,第三个因为不是我说了算,应该没机会了
无论如何,在11月份前,我会拿出可行的网络模块方案,请大家给予支持和鼓励   
回复

使用道具 举报

发表于 2003-10-12 23:49:06 | 显示全部楼层
盼星星盼月亮终于把你盼来了!

在Demo里不考虑网络安全也是可以的啦,不过得考虑一下,得保证以后加入安全功能时不需要重新设计整个程序架构~ 所以得保证一个“隔离层”,比如以后加入安全功能时最多只是把网络模块重写一下,而在网络模块基础之上的其他模块则不需要改动。那么就意味着这些安全功能全部要放进网络模块,并且从网络模块传出的数据全都是绝对可靠的(其实是所有模块都“认为”这些数据都是可靠的)。那么我就需要确认,能否把所有安全功能都放进网络模块。我想这应该是可以的吧~

关于我这边的进度:
我把设计图的当前版本(未完成)先发上来:
目前完成的部分:
用户输入响应
从Internet接收数据

未完成的部分:
通过Internet发送数据
线程池的功能
连接池的功能 *
时间触发器
游戏整体的启动、运行和结束的过程
每个线程的工作内容
规则处理器(Rule模块)读取规则、应用规则处理消息的过程
(其他还没想到的……)

deaboway,你也许可以把代码打包然后作为附件发到论坛来~
我想等我们做出一个可以运行的东东,再找人参与就会容易多了~

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
回复

使用道具 举报

发表于 2003-10-13 11:03:29 | 显示全部楼层
网络安全我是这么想的,在传输的过程中加入一个加密协议即可,当然这只是简单的加密,要想复杂而又可行的加密,我觉得只有边设计边改进拉。
好久没来了,汗
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-4-19 11:04 , Processed in 0.060343 second(s), 16 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表