📜 ⬆️ ⬇️

We write chat for the local network using C ++ Builder. Server part

A few months ago it took to develop a chat for the local network of one office, as well as to speak with this program at a scientific conference. I decided to do it in the Builder C ++ 2006 development environment. When writing an article, I had one major problem - a complete lack of experience in working with networks in the builder, so I am writing an article for the same "programmers". I will note right away that there are many programs on the Internet that will undoubtedly be better than mine, but the task was not to find the program, but to develop it. The article will be large, so I will divide it into 2 parts - server and client.

First of all it was necessary to decide what it will be for the application.

Idea


The first thing that came to mind was to open a server on each computer, uniting all the computers into a ring. The message is sent to the client to which we are now connected. There to check whether this message is to us or not; if not, send further until it reaches the addressee. I didn’t like the idea, mainly because I didn’t want the message to pass through intermediate users.

I liked the second idea. Let it be a client-server application. We open a server on one computer, all clients connect to it. From the client we send the message to the server, and he already forwards it to the addressee. By the way, the second advantage of this network architecture is that each client needs to know only one IP address - the server IP address. And when a client leaves the network, he will not have to look for the one to whom he was connected.
')
Of course, there must be a graphical interface, moreover, such that it works on the principle of “plug and play” - it launched the program and you can immediately take it for correspondence. Therefore, in the program window there will be a minimum of components, not even a bar menu.

How do we pour messages? Using sockets, namely the standard components of ClientSocket and ServerSocket, which will be used in client and server programs, respectively.

Implementation


Server

The server program is designed for one-time use. Those. when you exit it, no customer information is stored, everything in the program itself is stored in an array. In general, the socket interface itself is quite interesting. In order to send a message to the client, use the SendText command that accepts a message string like AnsiString (I read about 4.3 billion characters somewhere, which is impressive), but to send it to exactly who needs it, you should specify client number, and not, for example, its IP address. In this case, numbers are issued to customers in the order in which they connected. An .s file is declared as an array of type AnsiString consisting of 100 elements. Honestly, I did not check the maximum possible number of connections to the server, so we will assume that it is limited only by the size of this array. When a client connects, the first thing is to send his name to the server. It is entered into the first free cell of the array, and the element number will be the client number to which we will send messages. To find the first empty cell, I wrote the analog () function, which simply iterates over the array and returns the number of the empty cell.

int TFormMain::analog() { int a; for(int i=0;i<mass;i++) { if(m[i]=="") { a=i; break; } } return a; } 

In the server's OnConnect event, a timer is started. For some reason, the code executed by the timer I could not execute immediately in the event, so the timer executes it and immediately turns off. By timer, the server uses a loop to send a message to all clients, in which one line contains all the names of currently connected clients. Why this is done - I will tell in the description of the client. The client list is formed by the online () function “gluing” the clients' names from the array.

Connect customers
 void __fastcall TFormMain::ServerSocketClientConnect(TObject *Sender, TCustomWinSocket *Socket) { Timer1->Enabled=true; } //--------------------------------------------------------------------------- void __fastcall TFormMain::Timer1Timer(TObject *Sender) { if(ServerSocket->Socket->ActiveConnections!=0) for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++) ServerSocket->Socket->Connections[i]->SendText("8714"+online()); Timer1->Enabled=false; } //--------------------------------------------------------------------------- 

 AnsiString TFormMain::online() { char str[500]=""; for(int i=0;i<analog();i++) { strcat(str,m[i].c_str()); strcat(str,","); } return str; } 


However, the most interesting things happen in the OnRead server event. The structure of each message sent by both the client and the server necessarily contains 4 digits at the beginning. This is a random combination of numbers, invented by me so that the server and the client can distinguish between the messages necessary for "authorization", or the messages that contain the text to send. In total, the client can send 4 types of messages to the server. Messages with code 6141 are sent to the server at the first connection, they also tell the server the name of the new client, and the server enters it into the array and displays it in the Memo (a decorative element created just to know who is currently connected). The message with codes 5280 and 5487 lost their relevance, but for some reason they were not removed by me from the server code. Messages with code 3988 are the most important. This is the message containing all the information for the exchange of messages between users. The structure of such a message:

3988 <Sender Name>% <Recipient Name>: <Message Text>.

In general, from each received message the server first of all selects the code by the SubString method, its further actions depend on it in the future. From the same message, the server also selects the sender and the receiver, as well as the text of the message. Then a message of the form 7788 <Sender's name>: <Message text> is formed. It is sent to the receiving client. How, if only his number is known and not his name? For this purpose, the numer (AnsiString) function is written, which accepts a name, enumerates the array and returns the number of the cell in which this name is located.

Inbound processing
 void __fastcall TFormMain::ServerSocketClientRead(TObject *Sender, TCustomWinSocket *Socket) { message=Socket->ReceiveText(); time=Now().CurrentDateTime(); if(message.SubString(1,4).AnsiCompare("6141")==0) { m[analog()]=message.SubString(5,message.Length()); ListBox1->Clear(); for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++) { ListBox1->Items->Add(m[i]); } } else if(message.SubString(1,4).AnsiCompare("5487")==0) { for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++) ServerSocket->Socket->Connections[i]->SendText("8714"+online()); } else if(message.SubString(1,4).AnsiCompare("3988")==0) { nametowho=message.SubString(message.AnsiPos('')+1,message.AnsiPos(':')-message.AnsiPos('')-1); name=message.SubString(5,message.AnsiPos('')-5); if(nametowho.IsEmpty()==false && (message.SubString(message.AnsiPos(':')+1,message.Length()).IsEmpty())==false) { ServerSocket->Socket->Connections[numer(nametowho)]->SendText("7788"+name+":"+message.SubString(message.AnsiPos(':')+1,message.Length())); ofstream fout("chat.txt",ios::app); fout<<time.c_str()<<" "<<message.c_str()<<endl; fout.close(); } } else if(message.SubString(1,4).AnsiCompare("5280")==0) { ServerSocket->Socket->Connections[numer(message.SubString(message.Pos('#')+1,message.Pos('%')-message.Pos('#')-1))]->SendText( "6734"+message.SubString(message.Pos('%')+1,message.Length()-message.Pos('%'))); } } 


When any client is disconnected, the array is cleared, the request for receiving the name is sent again to the clients (the names are sent and received in the order the clients are connected), and the array is refilled. Also, customers are immediately sent a new list of customers who are online. This is also done inside the timer. Name request messages are sent to clients using a loop:

 void __fastcall TFormMain::ServerSocketClientDisconnect(TObject *Sender, TCustomWinSocket *Socket) { if(ServerSocket->Socket->ActiveConnections!=0) { for(int i=0;i<mass;i++) { m[i]=""; } TestNames(); Timer1->Enabled=true; } } 

The grafical part



The appearance of the server window:



Initially, I did not have plans to display any information in the server window, but in the end I decided to output the most important thing: the IP address of the server, the number of active connections, the server port, the name of the computer it is running on (for some reason, always “1” I could not fix it yet), and a list of names of connected clients. The server is minimized to the notification area. It is implemented with several functions, I will not analyze them in detail. Also, on the server, error catching is completely disabled (which did not occur in 2 weeks of continuous work at all, but you never know, the network’s operation is completely paralyzed).

The grafical part
 void __fastcall TFormMain::DrawItem(TMessage& Msg) { IconDrawItem((LPDRAWITEMSTRUCT)Msg.LParam); TForm::Dispatch(&Msg); } //--------------------------------------------------------------------------- void __fastcall TFormMain::MyNotify(TMessage& Msg) { POINT MousePos; switch(Msg.LParam) { case WM_RBUTTONUP: if (GetCursorPos(&MousePos)) { PopupMenu1->PopupComponent = FormMain; SetForegroundWindow(Handle); PopupMenu1->Popup(MousePos.x, MousePos.y); } else Show(); break; case WM_LBUTTONDBLCLK: Show(); break; default: break; } TForm::Dispatch(&Msg); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- bool __fastcall TFormMain::TrayMessage(DWORD dwMessage) { NOTIFYICONDATA tnd; PSTR pszTip; pszTip = TipText(); tnd.cbSize = sizeof(NOTIFYICONDATA); tnd.hWnd = Handle; tnd.uID = IDC_MYICON; tnd.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; tnd.uCallbackMessage = MYWM_NOTIFY; if (dwMessage == NIM_MODIFY) { tnd.hIcon = (HICON)IconHandle(); if (pszTip) lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip)); else tnd.szTip[0] = '\0'; } else { tnd.hIcon = NULL; tnd.szTip[0] = '\0'; } return (Shell_NotifyIcon(dwMessage, &tnd)); } //--------------------------------------------------------------------------- HICON __fastcall TFormMain::IconHandle(void) { return (Image2->Picture->Icon->Handle); } //--------------------------------------------------------------------------- PSTR __fastcall TFormMain::TipText(void) { return ("Office Chat"); } //--------------------------------------------------------------------------- LRESULT IconDrawItem(LPDRAWITEMSTRUCT lpdi) { return 0; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void __fastcall TFormMain::FormDestroy(TObject *Sender) { TrayMessage(NIM_DELETE); } //--------------------------------------------------------------------------- void __fastcall TFormMain::N1Click(TObject *Sender) { Show(); } //--------------------------------------------------------------------------- void __fastcall TFormMain::N2Click(TObject *Sender) { Application->Terminate(); } //--------------------------------------------------------------------------- void __fastcall TFormMain::FormCloseQuery(TObject *Sender, bool &CanClose) { CanClose=false; FormMain->Hide(); } //--------------------------------------------------------------------------- void __fastcall TFormMain::FormCreate(TObject *Sender) { unsigned long Size = 256; char *Buffer = new char[Size]; Label5->Caption=GetUserName(Buffer, &Size); delete [] Buffer; } //--------------------------------------------------------------------------- 


In conclusion, I want to say that it turned out to be a rather primitively written, but stably working server, which allows 20 people to simultaneously write to each other (I just didn’t check out more). All source codes, exe-files and full analysis of the client code will be in the second article.

Thanks for attention.

Source: https://habr.com/ru/post/259641/


All Articles