要说起Ace框架,它的跨平台性,效率还有开发的便捷使我选择Ace框架作为服务器端的首选。当然对于我坚持以C++为主的人来说,我就减少了学习和熟悉Java语言和类库。Java中有非常多适合做服务器端的开源利器,比如Mina就非常适合做服务器端框架。闲话少说我们先来使用Ace制作一个简单的C/S实例。当然Ace的安装也很简单参考网上的教程即可。
服务器端代码:
ServerHandler.h
#include"ace/Svc_Handler.h"
#include"ace/SOCK_Stream.h"
#defineMAXHOSTNAMELEN 100
classServerHandler : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
{
private:
ACE_TCHAR peer_name[MAXHOSTNAMELEN];
public:
virtualint handle_input (ACE_HANDLE fd = ACE_INVALID_HANDLE);
virtualint open (void *acceptor_or_connector = 0);
virtualint close (u_long flags = 0);
};
#endif
从头文件定义可以看出,当一个新的套接字连接服务器时,就会执行open函数,open函数是ACE_Svc_Handler的虚函数,而ServerHandler重写了该函数。你可以在一个新的连接加入服务器时进行某些操作,比如将玩家加入服务器中的玩家列表中。
ServerHandler.cpp
#include"stdafx.h"
#include"ServerHandler.h"
intServerHandler::handle_input (ACE_HANDLE handle)
{
const int INPUT_SIZE = 4096;
char buffer[INPUT_SIZE];
memset(buffer, 0, INPUT_SIZE);
int recv_cnt = this->peer().recv( buffer,sizeof(buffer));
if (recv_cnt <= 0 )
{
printf("Connectionclose\n");
return -1;
}
return 0;
}
intServerHandler::close (u_long flags){
if (ACE_Svc_Handler::close(flags) == -1){
return -1;
}
/*
*/
return 0;
}
intServerHandler::open (void *acceptor_or_connector){
if(ACE_Svc_Handler::open(acceptor_or_connector) == -1){
return -1;
}
return 0;
}
以上是具体函数的执行代码,你可以打印代码去查看或体验这个框架。
GameServer.cpp
//GameServer.cpp : 定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include"ServerHandler.h"
#include"ace/Reactor.h"
#include"ace/Acceptor.h"
#include"ace/SOCK_Acceptor.h"
int_tmain(int argc, _TCHAR* argv[])
{
WORDwVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2);
err = WSAStartup( wVersionRequested,&wsaData );
if ( err != 0 ) {
return 0;
}
if ( LOBYTE( wsaData.wVersion ) != 2||
HIBYTE( wsaData.wVersion) != 2 ) {
WSACleanup( );
return 0;
}
int port = 50001;
ACE_INET_Addr port_to_accept(port);
ACE_Acceptor<ServerHandler,ACE_SOCK_ACCEPTOR> server;
if(server.open(port_to_accept) == -1)
{
return -1;
}
ACE_Reactor::instance()->run_reactor_event_loop();
return 0;
}
//上面就是Main函数了,只需要定义网络地址和端口,不需要知道太多底层的socket原理就可以编写一个简单的服务器了。
下面来探讨下客户端:
对于客户端,因为是游戏开发所以有必要选择一个游戏框架。我一般都使用cocos2d-x。在使用cocos2d-x中的时候为了方便跨平台我都会使用ODSocket.下面是ODSocket的代码。
ODSocket.h
/*
* define file about portable socket class.
* description:this sock is suit both windowsand linux
* design:odison
* e-mail:odison@126.com>
*
*/
#ifndef_ODSOCKET_H_
#define_ODSOCKET_H_
#ifdefWIN32
#include <winsock2.h>
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
typedef int SOCKET;
//#pragma region define win32 const variable inlinux
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
//#pragma endregion
#endif
classODSocket {
public:
ODSocket(SOCKET sock = INVALID_SOCKET);
~ODSocket();
// Create socket object for snd/recv data
bool Create(int af, int type, int protocol =0);
// Connect socket
bool Connect(const char* ip, unsigned shortport);
//#region server
// Bind socket
bool Bind(unsigned short port);
// Listen socket
bool Listen(int backlog = 5);
// Accept socket
bool Accept(ODSocket& s, char* fromip =NULL);
//#endregion
int Select();
// Send socket
int Send(const char* buf, int len, int flags =0);
// Recv socket
int Recv(char* buf, int len, int flags = 0);
// Close socket
int Close();
// Get errno
int GetError();
//#pragma region just for win32
// Init winsock DLL
static int Init();
// Clean winsock DLL
static int Clean();
//#pragma endregion
// Domain parse
static bool DnsParse(const char* domain, char*ip);
ODSocket& operator = (SOCKET s);
operator SOCKET ();
protected:
SOCKET m_sock;
fd_set fdR;
};
#endif
ODSocket.cpp
#include<stdio.h>
#include"ODSocket.h"
#ifdefWIN32
#pragmacomment(lib, "wsock32")
#endif
ODSocket::ODSocket(SOCKETsock) {
m_sock = sock;
}
ODSocket::~ODSocket(){
}
intODSocket::Init() {
#ifdefWIN32
typedefstruct WSAData {
WORDwVersion; //winsockversion
WORDwHighVersion; //Thehighest version of the Windows Sockets specification that the Ws2_32.dll cansupport
charszDescription[WSADESCRIPTION_LEN+1];
charszSystemStatus[WSASYSSTATUS_LEN+1];
unsignedshort iMaxSockets;
unsignedshort iMaxUdpDg;
char FAR* lpVendorInfo;
}WSADATA, *LPWSADATA;
WSADATA wsaData;
//#define MAKEWORD(a,b) ((WORD) (((BYTE) (a)) |((WORD) ((BYTE) (b))) << 8))
WORD version = MAKEWORD(2, 0);
int ret = WSAStartup(version, &wsaData);//win sock start up
if (ret) {
// cerr << "Initilizewinsock error !" << endl;
return -1;
}
#endif
return 0;
}
//this isjust for windows
intODSocket::Clean() {
#ifdefWIN32
return (WSACleanup());
#endif
return 0;
}
ODSocket&ODSocket::operator =(SOCKET s) {
m_sock = s;
return (*this);
}
ODSocket::operatorSOCKET() {
return m_sock;
}
//createa socket object win/lin is the same
// af:
boolODSocket::Create(int af, int type, int protocol) {
m_sock = socket(af, type, protocol);
if (m_sock == INVALID_SOCKET) {
return false;
}
return true;
}
boolODSocket::Connect(const char* ip, unsigned short port) {
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = inet_addr(ip);
svraddr.sin_port = htons(port);
int ret = connect(m_sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
if (ret == SOCKET_ERROR) {
return false;
}
return true;
}
boolODSocket::Bind(unsigned short port) {
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = INADDR_ANY;
svraddr.sin_port = htons(port);
int opt = 1;
if (setsockopt(m_sock, SOL_SOCKET,SO_REUSEADDR, (char*) &opt, sizeof(opt))
< 0)
return false;
int ret = bind(m_sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
if (ret == SOCKET_ERROR) {
return false;
}
return true;
}
//forserver
boolODSocket::Listen(int backlog) {
int ret = listen(m_sock, backlog);
if (ret == SOCKET_ERROR) {
return false;
}
return true;
}
boolODSocket::Accept(ODSocket& s, char* fromip) {
struct sockaddr_in cliaddr;
socklen_t addrlen = sizeof(cliaddr);
SOCKET sock = accept(m_sock, (struct sockaddr*)&cliaddr, &addrlen);
if (sock == SOCKET_ERROR) {
return false;
}
s = sock;
if (fromip != NULL)
sprintf(fromip, "%s",inet_ntoa(cliaddr.sin_addr));
return true;
}
intODSocket::Select(){
FD_ZERO(&fdR);
FD_SET(m_sock, &fdR);
struct timeval mytimeout;
mytimeout.tv_sec=3;
mytimeout.tv_usec=0;
int result=select(m_sock,&fdR,NULL,NULL,NULL);
// 第一个参数是 0 和 sockfd 中的最大值加一
// 第二个参数是 读集, 也就是 sockset
// 第三, 四个参数是写集和异常集, 在本程序中都为空
// 第五个参数是超时时间, 即在指定时间内仍没有可读,则出错
//case -1: error handled by u;
if(result==-1){
return -1;
}
/*else if(result==0){
return -4;
}*/
else {
if(FD_ISSET(m_sock,&fdR)){
return -2;
}else {
return -3;
}
}
}
intODSocket::Send(const char* buf, int len, int flags) {
int bytes;
int count = 0;
while (count < len) {
const char* a= buf + count;
bytes = send(m_sock, buf + count, len- count, flags);
if (bytes == -1 || bytes == 0)
return -1;
count += bytes;
}
return count;
}
intODSocket::Recv(char* buf, int len, int flags) {
return (recv(m_sock, buf, len, flags));
}
intODSocket::Close() {
#ifdefWIN32
return (closesocket(m_sock));
#else
return (close(m_sock));
#endif
}
intODSocket::GetError() {
#ifdefWIN32
return (WSAGetLastError());
#else
return -1;
#endif
}
bool ODSocket::DnsParse(constchar* domain, char* ip) {
struct hostent* p;
if ((p = gethostbyname(domain)) == NULL)
return false;
sprintf(ip, "%u.%u.%u.%u", (unsignedchar) p->h_addr_list[0][0],
(unsigned char)p->h_addr_list[0][1],
(unsigned char) p->h_addr_list[0][2],
(unsigned char)p->h_addr_list[0][3]);
return true;
}
接下来就是编写ODSocket连接服务器的代码:
ODSocket *socket = new ODSocket();
socket->Init();
socket ->Create(AF_INET,SOCK_STREAM,0);
socket->Connect("127.0.0.1",50001);
你通过先运行服务器端再运行客户端就可以看到效果了。另外你可以设置断点可以更好的明白Ace框架和ODSocket的运行机制。