Andiamo per ordine.
Ci sono due approcci: push e pull.
Nel primo caso i client "dormono" (o sono in busy waiting per la verità) e il server gli manda un messaggio per "svegliarli" dicendo TI E' ARRIVATO QUESTO MESSAGGIO.
Nel secondo caso i client pollano (cioè interrogano ogni tot) il server chiedendo "c'è un messaggio per me?"
Normalmente si fa con una variabile che per ogni client memorizza l'ultima chiave autoincrementante lavorata nel db del server.
Se il client memorizza il valore poniamo 3274, farà qualcosa del tipo
select max(chiave) from tabellamessaggi where (client=mestesso) and (chiave>3274)
Se ritorna, poniamo, 3274 significa che non ci sono messaggi. Se torna 3333 allora ci sono (e normalmente farai una seconda query per pigliarli).
Avendo un = e un > nella query (soprattutto il > ovviamente) con un indice ad albero puoi fare una query di tipo range (cioè molto efficiente)
Siccome il numero di messaggi è normalmente piccolo (fatto da umani) più il protocollo di check è spartano, cioè trasporta pochi dati, meglio è.
Ecco perchè, usualmente, si paga il costo di due query (una di check, una di prelevamento).
Con database a connessioni leggere (uno a caso: mariadb/mysql) questo non è un grosso problema.
---
Resta il fatto che, normalmente, si preferisce il push: è il server che spedisce in opportuno protocollo e con codifica che vuoi (JSON, proprietaria, XML...) il pacchetto regalo al client.
Il quale client, a sua volta, memorizzerà in un minidb locale (sqllite? o alla peggio direttamente in un file di testo) i messaggi ricevuti ed inviati, per poi poterli "rimostrare" al successivo utilizzo del programma.