Introduction
This article presents a basic multi-client/server chat in C language using Windows APIs. The reason why I am writing this article is when I started
learning about sockets, all the tutorials i found on internet were only dealing with console based programs. After getting enough
knowledge I decided
to code a multi-client/server chat with a graphical user interface in order to make it more attractive and I hope it would help somebody else who
has a similar idea but does not know where to start. I'm still a beginner in Networks, Programming ,and even worse, (I am self-educated)
since my university did not teach me these things, and i haven't even completed my degree yet. So there might be mistakes and I will really
appreciate if you could help to correct those mistakes. Thank you.
The Client
The functions I am using in client code are:
- init_dll(): initialize ws_32.dll
- init_connection(): we create a socket (sock) and a variable (sin) of type
SOCKADDR_IN
that will be used to connect to the server.
We retrieve the port number and ip address from ip control and edit control in the user interface. With
WSAAsyncselect()
I also created a user define message (WM_SOCKET
) that I will
be using to get notifications of network events such as FD_READ and FD_CLOSE on sockets. - end_connection(): Terminates the use of ws_32.dll
- reset_controls(): Sets all the controls to their initial state
The connection to the server is made once the user clicks on "connect" button in the user interface. We first call
init_dll()
then init_connection()
. We get the IP address from ip control , and the port number from the edit control
(we also check the port number to ensure that it is not a reserved port number). Once the connection is succesfull we can send (by clicking on the send button)
and receive a message from the server . When the user clicks on send button, we get the message he has entered from edit control by calling
GetDlgItemText()
, and we allocate a buffer for storing that message and then we pass that buffer to send() function.
case IDC_SEND :
{
int n = GetWindowTextLength(GetDlgItem(hWnd, IDC_TO_SERVER));
if(n == 0)
{
MessageBox(hWnd , TEXT("You must enter a message") ,
TEXT("Hey!") , 0);
break;
}
char *buffer = (char*)GlobalAlloc(GPTR, n + 1);
GetDlgItemText(hWnd , IDC_TO_SERVER , buffer , n+1);
send(sock , buffer , strlen(buffer) , 0);
GlobalFree((HANDLE)buffer);
SendDlgItemMessage(hWnd , IDC_TO_SERVER , WM_SETTEXT , 0 , reinterpret_cast<LPARAM>("")); } break;
Whenever we get a message from the server, an FD_READ
message is sent to our Windows procedure and we retrieve the message using
recv()
function. FD_CLOSE
is sent whenever the server is no longer available.
case FD_READ: {
int n = recv(sock , buffer1 , sizeof(buffer1) - 1 , 0);
buffer1[n] = 0;
strcat(buffer2 , buffer1);
SendDlgItemMessage(hWnd , IDC_LISTBOX , LB_ADDSTRING , 0 ,
reinterpret_cast<LPARAM>(buffer2));
}
break;
case FD_CLOSE :
{
SendDlgItemMessage(hWnd , IDC_LISTBOX , LB_RESETCONTENT , 0 , 0);
SendDlgItemMessage(hWnd , IDC_LISTBOX , LB_ADDSTRING , 0 ,
reinterpret_cast<LPARAM>("\r\nserver closed connection"));
reset_controls(hWnd);
end_connection();
}
break;
The Server
The server has similar functions that I have already mentioned in Client part, so I will just quickly mention those functions name :
- init_dll()
- init_conection()
- end_connection()
What we do new in the server part is that, when a client initiates a connection, we retrieve his ip address and the port number
he is using then we send a message to the list box to say that a client has connected to server. We also send his name (defined by
his IP address and port number) to the combo box and we attach the socket descriptor to that name in the combobox so that we
will be able to retrieve it whenever we want to send a message to that specific client.
int index = SendDlgItemMessage(hWnd , IDC_SELECT_CLIENT ,
CB_ADDSTRING , 0 , reinterpret_cast<LPARAM>(buffer));
SendDlgItemMessage(hWnd , IDC_SELECT_CLIENT , CB_SETITEMDATA , (WPARAM)index , (LPARAM)s_client);
Button_Enable(GetDlgItem(hWnd , IDC_SELECT_CLIENT) , TRUE); strcat(buffer , " is connected");
SendDlgItemMessage(hWnd , IDC_LISTBOX , LB_ADDSTRING , 0 , reinterpret_cast<LPARAM>(buffer));
Whenever we get an FD_READ
notification we loop through all the socket descriptors that we initially bounded in combo box in order
to find out which client sent the message. Once we get that client who sent the message, we store his message in buffer1 , we format
it and display it in the listbox.
for(i = 0 ; i < n_client; i++)
{
s_client = SendDlgItemMessage(hWnd , IDC_SELECT_CLIENT , CB_GETITEMDATA , (WPARAM)i , 0);
if(recv(s_client , buffer1 , sizeof(buffer1) - 1 , 0) != -1)
{
getpeername(s_client , (SOCKADDR*)&clt , &clt_size); sprintf(buffer_client_id , "%s",inet_ntoa(clt.sin_addr));
strcat(buffer_client_id , "@Port:");
sprintf(buffer2, "%d",clt.sin_port);
strcat(buffer_client_id,buffer2);
strcat(buffer_client_id , " : ");
strcat(buffer_client_id , buffer1);
SendDlgItemMessage(hWnd , IDC_LISTBOX , LB_ADDSTRING , 0 , reinterpret_cast<LPARAM>(buffer_client_id));
}
}
Points of Interest
As I said earlier , this is my first post , I might have done few things the wrong way and I would really appreciate if you have a better idea
to improve my code. Thank you.