deaboway 发表于 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类以生成多进程并发服务器。也可以对已有的类进行修改从而增加新的功能,使其更加完善。由此看出,建立套接字类库能迅速地产生新的应用,也能方便地对原系统进行升级,程序也具有良好的结构,便于阅读和调试,从而极大地提高开发效率。

http://www.linuxfans.org/nuke/modules/Forums/files/1_539.png



套接字系统调用:MySocket类
MySocket类是该套接字类库的核心,它封装了与套接字相关的主要系统调用。其类声明源代码见如下:

/*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;
}

};

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):与上一函数一样,但增加了超时处理功能。

deaboway 发表于 2003-10-7 19:29:17

由于,源代码太长,我打算将它们放到https://gro.clinux.org/上,现在正申请中,过两天我会给出连接 :wink:

deaboway 发表于 2003-10-7 21:54:10

刚下载了hl2泄漏的源代码,希望可以找到网络方面的有用东东 :mrgreen:

sjinny 发表于 2003-10-8 22:50:11

汗,这么多代码~~

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

deaboway 发表于 2003-10-9 18:31:17

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

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

deaboway 发表于 2003-10-9 19:45:00

由于,源代码太长,我打算将它们放到https://gro.clinux.org/上,现在正申请中,过两天我会给出连接 :wink:
新的项目已经见好了,但是由于我是通过代理服务器上的网,cvs不能正常同步,郁闷ing,谁能告诉我设置方法,感激不尽~~~
错误过程如下:
[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)

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

sjinny 发表于 2003-10-9 22:38:07

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

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

sjinny 发表于 2003-10-10 13:30:16

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

deaboway 发表于 2003-10-10 20:04:30


目前有个问题,服务器端,每个对象都要有一个id,得是唯一的;另外,每个数据包里得有一个数据,用来确认发送者的身份。这两个数据怎么弄呢?至少得知道方向,这样才能给以后的开发留下一条道路。
这个问题完全可以解决,在继承TcpCliThr类和TcpServThr类时,可以通过重载虚函数run()的方式,得到客户端的标识。或者更好的方法(完全无后顾之忧~)就是再建立一个封装类——MySecrit(),,然后继承之,我去找一下这方面的资料~

如果一个客户端一个连接,那么假如连接数峰值为1000,那么会不会太占资源?
如果用连接池,那么就会经常性的建立和关闭连接,那么这些操作会不会太占资源?
如果采用UDP,是否可以避免 经常性的建立和关闭连接 所造成的 速度和资源占用问题 ?

tcp和udp并没有本质的区别,udp只是在tcp的基础上省去了一些东西。
连接峰值一般不会达到一百,据我所知,一般一台服务器最多连接300个就不错了~

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

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

sjinny 发表于 2003-10-10 23:17:33

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

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

sjinny 发表于 2003-10-11 20:42:39

以上两个关键问题,现有技术能有办法解决吗?

deaboway 发表于 2003-10-12 21:51:31

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

deaboway 发表于 2003-10-12 22:02:19

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

sjinny 发表于 2003-10-12 23:49:06

盼星星盼月亮终于把你盼来了! :mrgreen:

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

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

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

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

rocklgk 发表于 2003-10-13 11:03:29

网络安全我是这么想的,在传输的过程中加入一个加密协议即可,当然这只是简单的加密,要想复杂而又可行的加密,我觉得只有边设计边改进拉。
好久没来了,汗 :oops:
页: [1] 2 3
查看完整版本: 创建适合网络游戏使用的套接字类库