вторник, 24 августа 2010 г.

Разработка клиент-серверного приложения на основе сокетов.

 
Игра “Крестики-нолики”-это логическая игра,целью которой является расставить 5 крестиков или 5 ноликов в один ряд по горизонтали,по вертикали или по горизонтали.Кто расставил,тот и выйграл.Игра происходит между двумя игроками по локальной сети.При загружении игры один из игроков ставит галочку в меню communication->server,другой же в меню communication->client,вписывает IP адресс сервера и нажимает connect.Далее оба игрока выбирают новую игру(Game->New) и наслаждаются игрой.
Screenshot:
clip_image002

Постановка задачи.

Разработать клиент-серверное приложение, в данном случае игру “Крестики-нолики”, состоящий из клиента и сервера, позволяющего посылать и принимать информацию.
Немного теории.
Применяемая в IP-сетях архитектура клиент-сервер использует IP-пакеты для коммуникации между клиентом и сервером. Клиент отправляет запрос серверу, на который тот отвечает. В случае с TCP/IP между клиентом и сервером устанавливается соединение (обычно с двусторонней передачей данных), а в случае с UDP/IP - клиент и сервер обмениваются пакетами (дейтаграммамми) с негарантированной доставкой.
Каждый сетевой интерфейс IP-сети имеет уникальный в этой сети адрес (IP-адрес). Упрощенно можно считать, что каждый компьютер в сети Интернет имеет собственный IP-адрес. При этом в рамках одного сетевого интерфейса может быть несколько сетевых портов. Для установления сетевого соединения приложение клиента должно выбрать свободный порт и установить соединение с серверным приложением, которое слушает (listen) порт с определенным номером на удаленном сетевом интерфейсе. Пара IP-адрес и порт характеризуют сокет (гнездо) - начальную (конечную) точку сетевой коммуникации. Для создания соединения TCP/IP необходимо два сокета: один на локальной машине, а другой - на удаленной. Таким образом, каждое сетевое соединение имеет IP-адрес и порт на локальной машине, а также IP-адрес и порт на удаленной машине.
Модуль socket обеспечивает возможность работать с сокетами из Python. Сокеты используют транспортный уровень согласно семиуровневой модели OSI (Open Systems Interconnection, взаимодействие открытых систем), то есть относятся к более низкому уровню, чем большинство протоколов.
Уровни модели OSI:
Физический
Поток битов, передаваемых по физической линии. Определяет параметры физической линии.
Канальный (Ethernet, PPP, ATM и т.п.)
Кодирует и декодирует данные в виде потока битов, справляясь с ошибками, возникающими на физическом уровне в пределах физически единой сети.
Сетевой (IP)
Маршрутизирует информационные пакеты от узла к узлу.
Транспортный (TCP, UDP и т.п.)
Обеспечивает прозрачную передачу данных между двумя точками соединения.
Сеансовый
Управляет сеансом соединения между участниками сети. Начинает, координирует и завершает соединения.
Представления
Обеспечивает независимость данных от формы их представления путем преобразования форматов. На этом уровне может выполняться прозрачное (с точки зрения вышележащего уровня) шифрование и дешифрование данных.
Приложений (HTTP, FTP, SMTP, NNTP, POP3, IMAP и т.д.)
Поддерживает конкретные сетевые приложения. Протокол зависит от типа сервиса.
Каждый сокет относится к одному из коммуникационных доменов. Модуль socket поддерживает домены UNIX и Internet. Каждый домен подразумевает свое семейство протоколов и адресацию. Данное изложение будет затрагивать только домен Internet, а именно протоколы TCP/IP и UDP/IP, поэтому для указания коммуникационного домена при создании сокета будет указываться константа socket.AF_INET.
В качестве примера следует рассмотреть простейшую клиент-серверную пару. Сервер будет принимать данные о ходе клиента, и отвечать ему.
Реализация программы.

1 Структура программы.
Для начала определим класс сообщений (XO). В этом классе описаны почти все переменные и функции, с помощью которых реализуются выполнение программы. Это самый основной класс. Здесь идет работа с сокетами, обмен информацией между клиентом и сервером, анализ игры.
class XO : public CFrameWnd
{
public:
void Send(char buf[bufsize]);
bool Check(int i1,int i2,int i3,int i4,int i5);
bool IsWon();
void CalcCellRect();
void DrawFrame(CPaintDC*);
XO();
private:
CString sAddr;
CMenu menu;
CPen pen;
Cell cells[25];
mode cur_mod;
bool bConnected;
bool bGame;
bool bServer;
bool bClient;
int num_cells;
bool mymove;
protected:
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
afx_msg void OnConnectClient();
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnGameNew();
afx_msg void OnGameExit();
afx_msg void OnClose();
afx_msg void OnConnectServer();
afx_msg void OnUpdateConnectClient(CCmdUI* pCmdUI);
afx_msg void OnUpdateConnectServer(CCmdUI* pCmdUI);
afx_msg void OnUpdateGameNew(CCmdUI* pCmdUI);
afx_msg LRESULT OnAsync(WPARAM wparam, LPARAM lparam);
DECLARE_MESSAGE_MAP()
};
Ещё создана вспомогательная структура(struct Cell),которая содержит характеристики ячейки.
struct Cell
{
mode mod;
bool active;
CRect place;
Cell() {active = false;}
void SetCell(mode m) {mod = m; active = true;}
};
Так же создан класс(CDialog),через который реализуется рисование окна и вычисление IP адреса.
class Dlg : public CDialog
{
public:
CString addr;
Dlg(CWnd* p=NULL)
: CDialog(IDD_CONDIALOG,p){}
protected:
virtual void DoDataExchange(CDataExchange* pDX);
};
И последним создан класс (MyApp)который реализуется рисование окна.
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance()
{
AfxSocketInit();
m_pMainWnd = new XO;
m_pMainWnd->ShowWindow(SW_SHOWNORMAL);
return TRUE;
}
};
MyApp app;
2.Описание функций.
· Функция задаёт параметры окна.
BOOL XO::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style &= ~WS_MAXIMIZEBOX;
cs.style &= ~WS_THICKFRAME;
cs.cx = 400;
cs.cy = 400;
return CFrameWnd::PreCreateWindow(cs);
}
LRESULT XO::OnAsync(WPARAM wParam,LPARAM lParam)
{
WSAEvent = WSAGETSELECTEVENT (lParam);
switch (WSAEvent)
{
case FD_READ:
if(bServer)
recv(hSock2, buf, bufsize, 0);
else recv(hSock, buf, bufsize, 0);
if(buf[0]==33)
{
for(int i=0; i<25; i++)
{
cells[i].active=false;
}
cur_mod=X;
bGame=true;
mymove=false;
num_cells=0;
Invalidate();
return 0;
}
if(mymove==false)
mymove=true;
cells[buf[0]].active=true;
cells[buf[0]].mod = buf[1]==1 ? X : O;
cur_mod= cur_mod==X ? O : X;
num_cells++;
IsWon();
Invalidate();
return 0;
case FD_ACCEPT:
lenaddr=sizeof(myaddr);
hSock2=accept(hSock,(LPSOCKADDR)&myaddr,&lenaddr);
return 0;
}
return 1;
}
· Функция, реализующая соединение с клиентом.
void XO::OnConnectClient()
{
bClient=true;
WSAStartup(WS_VERSION_REQD, &stWSAData);
hSock = socket (AF_INET, SOCK_STREAM, 0);
Dlg dlg;
dlg.DoModal();
sAddr = dlg.addr;
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = inet_addr(sAddr);
myaddr.sin_port = htons (2049);
connect (hSock, (struct sockaddr *)&myaddr,sizeof(myaddr));
nRet=WSAAsyncSelect(hSock,m_hWnd,WM_ASYNC,FD_READ);
bConnected=true;
bServer=false;
mymove=true;
SetWindowText("xo - client");
}
· Функция,реализующая соединение с клиентом.
void XO::OnConnectServer()
{
bServer=true;
WSAStartup(WS_VERSION_REQD, &stWSAData);
hSock = socket (AF_INET,SOCK_STREAM,0);
WSAAsyncSelect(hSock,m_hWnd,WM_ASYNC, FD_ACCEPT | FD_READ);
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl (INADDR_ANY);
myaddr.sin_port = htons (2049);
bind(hSock,(LPSOCKADDR)&myaddr, sizeof(struct sockaddr));
listen (hSock, 5);
bConnected=true;
bClient=false;
mymove=false;
SetWindowText("xo - server");
}
· Функция посылания информации.
void XO::Send(char buf[])
{
if(bClient)
send(hSock,buf,bufsize,0);
else send(hSock2,buf,bufsize,0);
}
void XO::OnUpdateConnectClient(CCmdUI* pCmdUI)
{
pCmdUI->Enable(bClient);
pCmdUI->SetCheck(int(bClient&&bConnected));
}
void XO::OnUpdateConnectServer(CCmdUI* pCmdUI)
{
pCmdUI->Enable(bServer);
pCmdUI->SetCheck(int(bServer&&bConnected));
}
void XO::OnUpdateGameNew(CCmdUI* pCmdUI)
{
pCmdUI->Enable(bConnected);
}
· Функция рисования.
void XO::OnPaint()
{
CPaintDC dc(this);
dc.SelectObject(&pen);
DrawFrame(&dc);
for(int i=0; i<25; i++)
{
if(cells[i].active==true)
{
if(cells[i].mod==X)
{
dc.MoveTo(cells[i].place.left,cells[i].place.top);
dc.LineTo(cells[i].place.right,cells[i].place.bottom);
dc.MoveTo(cells[i].place.left,cells[i].place.bottom);
dc.LineTo(cells[i].place.right,cells[i].place.top);
}
else if(cells[i].mod==O)
{
dc.Ellipse(cells[i].place);
}
}
}
}
void XO::DrawFrame(CPaintDC* dc)
{
CRect r;
GetClientRect(&r);
dc->MoveTo(r.left,r.bottom/5);
dc->LineTo(r.right,r.bottom/5);
dc->MoveTo(r.left,2*r.bottom/5);
dc->LineTo(r.right,2*r.bottom/5);
dc->MoveTo(r.left,3*r.bottom/5);
dc->LineTo(r.right,3*r.bottom/5);
dc->MoveTo(r.left,4*r.bottom/5);
dc->LineTo(r.right,4*r.bottom/5);
dc->MoveTo(r.right/5,r.top);
dc->LineTo(r.right/5,r.bottom);
dc->MoveTo(2*r.right/5,r.top);
dc->LineTo(2*r.right/5,r.bottom);
dc->MoveTo(3*r.right/5,r.top);
dc->LineTo(3*r.right/5,r.bottom);
dc->MoveTo(4*r.right/5,r.top);
dc->LineTo(4*r.right/5,r.bottom);
}
void XO::OnLButtonDown(UINT nFlags, CPoint point)
{
if(bGame==false || mymove==false)
return;
for(int i=0; i<25; i++)
{
if(cells[i].place.PtInRect(point) && !cells[i].active)
{
buf[0]=i;
cells[i].active=true;
cells[i].mod=cur_mod;
mymove=false;
num_cells++;
InvalidateRect(cells[i].place);
cur_mod= cur_mod== X ? O : X;
buf[0]=i;
buf[1]= cells[i].mod==X ? 1 : 0;
Send(buf);
break;
}
}
IsWon();
}
void XO::CalcCellRect()
{
CRect r;
GetClientRect(&r);
int cx = r.right;
int cy = r.bottom;
for(int j=0,t=r.top,b=r.top+cy/5; j<5; j++,t+=cy/5,b+=cy/5)
{
for(int i=0,l=r.left,p=r.left+cx/5;
i<5; i++,p+=cx/5,l+=cx/5)
{
cells[j*5+i].place.left=l+5;
cells[j*5+i].place.right=p-5;
cells[j*5+i].place.top=t+5;
cells[j*5+i].place.bottom=b-5;
}
}
}
· Функция,определяющая победителя.
·
bool XO::IsWon()
{
bool won=false;
won = Check(0,1,2,3,4)||Check(4,9,14,19,24)
||Check(20,21,22,23,24)||Check(0,5,10,15,20)
||Check(1,6,11,16,21)||Check(2,7,12,17,22)
||Check(5,6,7,8,9)||Check(3,8,13,18,23)
||Check(10,11,12,13,14)||Check(15,16,17,18,19)
||Check(0,6,12,18,24)||Check(4,8,12,16,20);
if(!won && num_cells==25)
{
MessageBox("None won ...","");
won=true;
}
if(won) bGame=false;
return won;
}
· Функция создания новой игры.
void XO::OnGameNew()
{
if(bGame==true)
return;
for(int i=0; i<25; i++)
{
cells[i].active=false;
}
bGame=true;
mymove=true;
num_cells=0;
cur_mod=X;
buf[0]=33;
Send(buf);
Invalidate();
}
· Функция,определяющая выйграшные комбинации.
bool XO::Check(int i1,int i2,int i3,int i4,int i5)
{
bool won=false;
mode m;
if(!(cells[i1].active && cells[i2].active && cells[i3].active && cells[i4].active && cells[i5].active))
return won;
if((m=cells[i1].mod) == cells[i2].mod
&& cells[i2].mod == cells[i3].mod
&& cells[i3].mod == cells[i4].mod
&& cells[i4].mod == cells[i5].mod)
{
won=true;
}
if(won==true)
{
char who= m==X ? 'X' : 'O';
CString message="Victory of ";
message += who;
MessageBox(message,"Congratulations!");
}
return won;
}
· Функция выхода.
void XO::OnGameExit()
{
OnClose();
}
· Функция закрытия.
void XO::OnClose()
{
nRet = WSACleanup();
CFrameWnd::OnClose();
}

Комментариев нет:

Отправить комментарий