
/*
 Простейшая "чаттилка" написанная на C--. Уж не обессудте - это первая моя
 графическая программа под Win32,так что ничего особенного тут увидеть нельзя.
 Вроде откомментировал все,если есть какие-то вопросы,пишите на
 xdeviator@yahoo.com.
 10.05.2000
 P.S. Может иногда глючить - обламало меня везде делать проверку на наличие
      соединения.
*/

?jumptomain NONE
#pragma option w32
#pragma option wsi
?include "windows.h"			// Различные include файлы
#include "chat.rc"

?define	MAX_CLIENTS 128			// Максимальное количество клиентов
?define	DEF_PORT    55555		// TCP Port

?define MB_WARNING	MB_ICONWARNING	// Так,для красоты :)

dword	hInst;				// App Instance
char	hostname[129];
char	nickname[17];
struct  WSAData		WSAs;
struct  sockaddr_in	mysockaddr,srvaddr;
//struct  fd_set		myfd,serverfd;
SOCKET	mysock,srvsock;
dword	xth;
dword	xhwnd,chwnd;

char	said[128];			// some strings
char	rcv[128];
char	src[128];

//char	cglue[128];

byte	empty;
byte	connected;
byte	haveserver;

SOCKET	socks[MAX_CLIENTS];
char	clientnames[MAX_CLIENTS*16];

dword	clients;

char	lname[10] = "localhost";
dword	don = 123;
char	uname[16];
char	ubuf[128];
char	xbuf[128];
//--------------------------------------------
byte	ucmd_enter	=	{0x01,0x01,0x00}; // Somebody entered chat
byte	ucmd_leave	=	{0x01,0x02,0x00}; // Somebody leaved chat

struct SrvClient
{
dword   mySock;
dword   myNum;
} srv;

main()
{
	connected  = FALSE;
	haveserver = FALSE;
	WSAStartup(0x101,#WSAs);	// Инициализация WinSock

  hInst = GetModuleHandleA(0);
  DialogBoxParamA(EAX,DLG_CONNECT,0,#ConnectDialog,EAX);
					// Главный диалог. Ну ломало меня
					// создавать окошки на каждый компонент
					// диалогового окна
  WSACleanup();
  ExitProcess(0);
}

void	CreateServerSocket()		// Создает серверный сокет
{
	srvsock =	socket(AF_INET,SOCK_STREAM,PCL_NONE);
	IF (EAX == INVALID_SOCKET)
		{
		MessageBoxA(0,"Couldn't create server socket !","Error",0);
		ExitProcess(0);
		}
	setsockopt(srvsock,IPPROTO_TCP,TCP_NODELAY,#don,sizeof(don));

// Тут еще одна проблема. По RFC-951 (вроде) все данные пересылаемые по сети
// должны паковатся в довольно обьемистые пакеты - так меньше расходуется
// пропускной способности сети на мелкие пакеты. Для чата это не приемлимо -
// пакет отошлется только тогда,когда наберется достаточное количество
// информации.A выключается это просто...

}


void	CreateClientSocket()
{
	mysock	=	socket(AF_INET,SOCK_STREAM,PCL_NONE);
	IF (EAX == INVALID_SOCKET)
		{
		MessageBoxA(0,"Couldn't create client socket !","Error",0);
		ExitProcess(0);
		}
	setsockopt(mysock,IPPROTO_TCP,TCP_NODELAY,#don,sizeof(don));
}

dword	ConnectDialog(dword	hdlg,cmd,wpar,lpar)
{
 EAX = cmd;
 switch (AX) {
	case WM_INITDIALOG:
	 xhwnd = GetDlgItem(hdlg,IDC_LISTBOX1);	// Chat Listbox handle
	 chwnd = GetDlgItem(hdlg,IDC_LISTBOX2);	// People Listbox handle
	 DoFocus:
	  GetDlgItem(hdlg,IDC_EDIT3);		// Установим фокус на Edit
	  SetFocus(EAX);
		break;
  CASE WM_CLOSE:				// Закрытие ?
	 xQuit:
		connected = FALSE;
		haveserver = FALSE;
		closesocket(srvsock);		// Закроем сокеты
		closesocket(mysock);
		EndDialog(hdlg,0);
		break;
	CASE WM_COMMAND:			// Кто-то что-то нажал ?
		EAX = wpar;
		switch (AX)
		{
		case IDC_PUSHBUTTON1:		// Connect ?
		if (connected == FALSE)
		 {
			GetDlgItemTextA(hdlg,IDC_EDIT1,#hostname,128);
						// Берем имя хоста
		 xConnect:
			GetDlgItemTextA(hdlg,IDC_EDIT2,#nickname,16);
						// Берем ник

			CreateClientSocket();	// Создадим socket

			mysockaddr.sin_family	= AF_INET;
			mysockaddr.sin_port	= htons(DEF_PORT);// Port
			gethostbyname(#hostname);		// IP ADDR
			IF (EAX == 0)
			 {
			 closesocket(mysock);
			 MessageBoxA(0,"Specified host couldn't be found !","Error",MB_WARNING);
			 break;
			 }
			EAX 			= DSDWORD[EAX+HOST_ENT];
			EAX			= DSDWORD[EAX];
			mysockaddr.S_addr	= DSDWORD[EAX];
			connect(mysock,#mysockaddr,sizeof(mysockaddr));
						// Пробуем соеденится
			IF (EAX == SOCKET_ERROR)
			 {
			 closesocket(mysock);
			  MessageBoxA(0,"Couldn't connect to host !","Error",MB_WARNING);
			  break;
			 }
			connected = TRUE;	// Есть такое !
			SetDlgItemTextA(hdlg,IDC_PUSHBUTTON1,"Disconnect");
						// Меняем надпись кнопки
			CreateThread(0,64000,#ClientThread,0,0,#xth);
						// Создаем клиентскую нить
			break;
		  } ELSE
		  {
			connected = FALSE;	// Нажали на Disconnect
			closesocket(mysock);
			SetDlgItemTextA(hdlg,IDC_PUSHBUTTON1,"Connect");
			break;
		  };
		case IDC_PUSHBUTTON2:		// Create server ?
		 if (haveserver == FALSE)
			{
			CreateServerSocket();
                         srvaddr.sin_family	=	AF_INET;
                         srvaddr.sin_port	=	htons(DEF_PORT);// Port
                         clients		=	0;
                         bind(srvsock,#srvaddr,sizeof(srvaddr));
                          IF (EAX == SOCKET_ERROR)
                          	{
                                MessageBoxA(0,"Couldn't bind to socket !","Error",MB_WARNING);
                                closesocket(srvsock);
                                break;
                                }
			SetDlgItemTextA(hdlg,IDC_PUSHBUTTON2,"Stop server");
						// Название кнопки
			haveserver = TRUE;
			CreateThread(0,64000,#ListenThread,0,0,#xth);
						// Серверная нить
			FOR (ECX = 0; ECX<10; ECX++)
			  hostname[ECX] = lname[ECX];
						// Переписали localhost
			goto xConnect;		// Коннектимся
			} ELSE
			{
			SetDlgItemTextA(hdlg,IDC_PUSHBUTTON2,"Create");
			haveserver = FALSE;	// Закрыли сервер
			closesocket(srvsock);
			BREAK;
			};
		case IDC_PUSHBUTTON5:		// Quit  ?
			goto xQuit;
                case IDC_PUSHBUTTON4:		// Say ?
		IF (connected)			// А законекчены ?
		 {
		GetDlgItemTextA(hdlg,IDC_EDIT3,#said,128);
		IF (said[0] != 0)		// Есть что говорить ?
		{
		send(mysock,#said,128,0);	// Послали
		SetDlgItemTextA(hdlg,IDC_EDIT3,#empty);
						// Очистили Edit
		}
		 }
		goto DoFocus;
	}
 }
 EAX = 0;
}

void	GlueName(dword	toplace,glue,nick)	// Создает более или менее
{						// приемлимый вид ника
 EDI = toplace;
 AL = char '<';
 $stosb
 ESI = nick;
 WHILE (AL != 0)
  {
  $lodsb
  $stosb
  }
 EDI--;
 AL = char '>';
 $stosb
 ESI = glue;
 WHILE (AL != 0)
  {
  $lodsb
  $stosb
  }
}

void	strcat(dword	toplace,glue,uname)	// обломало меня lstrcat юзать
{
 EDI = toplace;
 ESI = glue;
 do
  {
  $lodsb
  $stosb
  } while  (AL != 0);
 EDI--;
 ESI = uname;
  do {
  $lodsb
  $stosb
  } while (AL != 0);
}

void	strcpy(dword	toplace,glue)		// аналогично lstrcpy
{
 EDI = toplace;
 ESI = glue;
  do {
  $lodsb
  $stosb
  } while (AL != 0);
}

//-------------------------------------------------------//
void	ClientThread()
dword	clinum;
{
 send(mysock,#nickname,16,0);		// Клиент. Посылаем наше имя
 recv(mysock,#clinum,4,0);		// Нам в ответ - количество юзеров
  EBX = clinum;

 loop (EBX)
	{
	recv(mysock,#rcv,16,0);				// Получаем юзера
	SendMessageA(chwnd,LB_ADDSTRING,0,#rcv);	// Добавили в список
	}

 while (connected)
 {
	recv(mysock,#rcv,128,0);	// Принимаем

	IF (EAX == 0) || (EAX == -1) connected = FALSE;	// Приняли ?
	else
	IF (rcv[0] != 1)			// А не управляющий код ?
	 {
	SendMessageA(xhwnd,LB_ADDSTRING,0,#rcv);	// Нет.Добавили в chat
	 } else
	switch (rcv[1]){
	 CASE 1:			// Кто-то присоеденился ?
		SendMessageA(chwnd,LB_ADDSTRING,0,#rcv+2);
		strcat(#xbuf,#rcv+2," entered chat...");
		SendMessageA(xhwnd,LB_ADDSTRING,0,#xbuf);
		BREAK;
	 CASE 2:			// Кто-то отсоеденился ?
		SendMessageA(chwnd,LB_FINDSTRING,0,#rcv+2);
		IF (EAX != LB_ERR)			// А он уже был ?
		 {
		  SendMessageA(chwnd,LB_DELETESTRING,EAX,0);
		 strcat(#xbuf,#rcv+2," leaved chat...");
		  SendMessageA(xhwnd,LB_ADDSTRING,0,#xbuf);
		 }
		BREAK;
	}
 SendMessageA(xhwnd,LB_GETCOUNT,0,0);
 EAX--;
 SendMessageA(xhwnd,LB_SETTOPINDEX,EAX,0);	// На самый конец ListBox'a
 }
 SendMessageA(xhwnd,LB_ADDSTRING,0,"Disconnected...");
 clinum = SendMessageA(chwnd,LB_GETCOUNT,0,0);	// Очищаем список
 loop(clinum) SendMessageA(chwnd,LB_DELETESTRING,clinum-1,0);
 closesocket(mysock);
 ExitThread(0);
}

//-------------------------------------------------------//
void	MassSend(dword	msg/*,dword me*/)		// Массовая рассылка
dword	i;
{
 FOR (i = 0; i<clients; i++)
 /*   if (i != me) */ send(socks[i],msg,128,0);
}

void	ServerThread(dword      Client)		// Серверная нить.
// В начале я решил сделать обработку юзеров циклом, но в результате этого
// появилиь 2 проблемы. 1 - сильно кушались ресурсы системы, 2 - при большом
// количестве юзеров (>500 :) чуствовалась довольно значительная задержка.
// Теперь каждого юзера обслуживает отдельная нить.
dword	j;
dword   mySock;
dword   MyNum;
{
 mySock = DSDWORD[Client];
 MyNum  = DSDWORD[Client+4];

 while (haveserver)			// До тех пор, пока жив сервер....
  {
   recv(mySock,#src,128,0);		// Принимаем от юзера сообщение
   IF (EAX == -1) || (EAX == 0)		// Юзер не отключился ?
      {
		strcat(#uname,#ucmd_leave,MyNum<<4 + #clientnames);
		 clients--;		// Ну так ему и надо. Выкинем из списка
		 FOR (j = MyNum; j<clients; j++)
			{
			socks[j] = socks[j+1];
			strcpy(j<<4 + #clientnames,j+1<<4 + #clientnames);
			}
		MassSend(#uname/*,MyNum*/);	// А теперь осведомим всех
		GOTO AbortIt;
      }
  GlueName(#uname,#src,MyNum<<4+#clientnames);
  MassSend(#uname/*,MyNum*/);		// Нет. Что-то видимо сказать хочет.
  }
AbortIt:
 closesocket(mySock);
 ExitThread(0);
}

void	ListenThread()			// Нить обрабатывающая новые соединения
dword	i;
{
 listen(srvsock,MAX_CLIENTS-1);
 while (haveserver)
  {
	accept(srvsock,0,0);			// Ждем connect'a
	if (EAX != SOCKET_ERROR)		// Есть ?
	{
	if (clients < MAX_CLIENTS) {		// А не много ли юзеров ?
	 socks[clients] = EAX; 			// Запомнили socket

         srv.mySock = EAX;			// Для серверной нити
         srv.myNum  = clients;

	 recv(socks[clients],#uname,16,0);	// получили имя
	 strcat(#ubuf,#ucmd_enter,#uname);
	 MassSend(#ubuf/*,clients*/);		// разошлем всем инфу

	 strcpy(clients<<4+#clientnames,#uname);

	clients++;
        send(socks[clients-1],#clients,4,0); //пошлем клиенту количество юзверей
	FOR (i = 0; i < clients; i++)		// имена оных
		send(socks[clients-1],i<<4+#clientnames,16,0);

	 CreateThread(0,64000,#ServerThread,#srv,0,#xth);// Личная нить юзера

	} ELSE closesocket(EAX);		// Нет мест. Свободен...
	}
  }
 closesocket(srvsock);
 ExitThread(0);
}
