By Lei Yang
MIM (Mini Instant Messenger) is a simple instant messenger system for X Windows (requires QT, can be build for KDE). It also runs under MS Windows. MIM provides the basic chat functionality. MIM is built upon a simple client/server model, with TCP socket networking.
A central server listens on port number 4242 and accepts incoming connections. A client tries to connect to the central server on the host specified by an IP address or host name. Note that in this mini system, we do not provide client authentication and secure account. A client may simply login without a password, given that it has a valid username. The central server only handles the registration and online table updates. As soon as the communication is initiated, the clients would then talk to each other without going through the server.
After a client logs in, the server will update its internal online table, and send the entire online table to the newly connected client. Subsequently, server will send the new client information to all other users and update its online table. The client will also set up a Client's Simple Server to accept chat from other users.
A client "A" can then proceed to chat, after it has retrieved the online user table. To chat with another online user "B", "A" only needs to click on "B" in its online table listview. "A" will be notified that the connection has been set up and is ready to talk. A dialog will pop up to facilitate chat. On the other hand, "B" will also have a dialog pop up, telling "B" who is talking to him and the chat is ready to start.
To end the talk, user simply clicks the "close" button, which is at the bottom of its chat dialog. The other end will have its chat dialog grey and inactive. After clicking on the "close" button, this dialog also will close.
To disconnect from the server (logout), a client should close the connection by clicking "logout" button. Server must interpret a connection close correctly, remove the user from its online list, and update the online list of all other users afterwards.
The Qt network module offers classes to make network programming easier and portable. Essentially, there are three sets of classes, first low level classes like QSocket, QServerSocket, QDns, etc. which allow you to work in a portable way with TCP/IP sockets. In addition, there are classes like QNetworkProtocol, QNetworkOperation in the Qt base library, which provide an abstract layer for implementing network protocols and QUrlOperator which operates on such network protocols. Finally the third set of network classes are the passive ones, specifically QUrl and QUrlInfo which do URL parsing and similar.
The central server uses the QServerSocket class. QServerSocket provides a TCP-based server. This class is a convenience class for accepting incoming TCP connections. You can specify the port or have QServerSocket pick one, and listen on just one address or on all the machine's addresses. Using the API is very simple: subclass QServerSocket, call the constructor of your choice, and implement newConnection() to handle new incoming connections.
The client uses the QSocket class. QSocket provides a buffered TCP connection. It provides a totally non-blocking QIODevice, and modifies and extends the API of QIODevice with socket-specific code. The functions called most in the code are connectToHost(), bytesAvailable(), canReadLine(). connectToHost() is the most-used function. As its name implies, it opens a connection to a named host. Most network protocols are either packet-oriented or line- oriented. canReadLine() indicates whether a connection contains an entire unread line or not, and bytesAvailable() returns the number of bytes available for reading. The signals error(), connected(), readyRead() and connectionClosed() inform you of the progress of the connection. There are also some less commonly used signals. hostFound() is emitted when connectToHost() has finished its DNS lookup and is starting its TCP connection. delayedCloseFinished() is emitted when close() succeeds. bytesWritten() is emitted when QSocket moves data from its "to be written" queue into the TCP implementation. There are several access functions for the socket: state() returns whether the object is idle, is doing a DNS lookup, is connecting, has an operational connection, etc. address() and port() return the IP address and port used for the connection. The peerAddress() and peerPort() functions return the IP address and port used by the peer, and peerName() returns the name of the peer (normally the name that was passed to connectToHost()). socket() returns a pointer to the QSocketDevice used for this socket.
TCP is a stream-oriented protocol. For applications, the data appears to be a long stream, rather like a large flat file. The high-level protocols built on top of TCP are typically either line-oriented or block-oriented:
Central server and clients work with a block-oriented protocol, in the sense that they send online user information with blocks. Client simple server and clients work with a line-oriented protocol, in the sense that they only exchange line-based messages.
The central server listens on port number 4242 and accepts incoming connections. It uses QServerSocket class (provided by Qt). Central server maintains an internal online list and is responsible for updating the online list for every user (client). When a client logs in, it will send the central server its name, IP address and the port number of its own Simple Server. This information is then sent to all other users, so that they could set up connection and chat with this client. Note that the central server is not responsible for sending messages to clients in a chat. Clients have to set up connection with the desired target and send messages by themselves. When a client logs out, the central server will remove this user from its own online list and update the online list of all other users.
The central server sends newly updated online user info. The info is a binary block with the following format:
Q_UINT16 | Block size in bytes(excluding this field) |
Q_UINT8 | Update type is add ('A') or delete ('D') |
QString | User name |
QString | User IP address |
Q_UINT16 | User simple server port number |
A client tries to connect to the central server on the host specified by an IP address or host name. Its own online list is synchronized with that of the central sever. After a client is connected to the central server, it will create a simple server of its own, in order to accept chat requests from other users (clients).
As mentioned in previous sections, a client should send central server its login request. The request is a binary block with the following format:
Q_UINT16 | Block size in bytes(excluding this field) |
Q_UINT16 | Client's simple server port number |
QString | Client's name |
Class "client" is the key class for a client. It is a QWidget, i.e., it creates a Qt widget which is subsequently set as main widget in the client application. The comments in the code explains its functionality.
class Client : public QWidget { Q_OBJECT public: Client(const QString &host, Q_UINT16 port); private slots: void enableLogin(const QString& text); //Only enable login when the text content is changed (to non-empty) void startConnection(); //Try to connect to server with certain host name and port number void socketConnected(); //After connected to server, start up its own simple server void closeConnection(); //closeConnection if the user press "quit" void textReadyRead(); //Print welcome logo from server, and ready to receive online list void socketConnectionClosed(); //When server closes connection, show info in text view void socketClosed(); //Connection closed by user successfully, show info in text view void socketError(int e); //Show error info if connection meets error void talk(QListViewItem *item); //Pop up a dialog, and start to talk with selected item (user) void talkToMe(QSocket *s); //Pop up a dialog, and start to talk with the other private: void sendToServer(); //Send server the name and port number of this client void updateList(); //Update online list when new user logs in or someone logs out QSocket *socket; //Socket that connects with central server SimpleServer *simServer; //Client's own simple server QTextView *infoText; //Show welcome logo and other useful activity information QLineEdit *nameText; //Login with this name QPushButton *login; //Press to login, start connection with central server QPushButton *logout; //Press to logout,close connection with central server QListView *onlineList; //A client's private online list QString serverHost; //Host name of the central server Q_UINT16 serverPort; //Port number of the central server Q_UINT16 blockSize; //block size of a incoming packet };
Each client has its own simple server, which uses the QSocketServer class. It will bind to a port assigned automatically by the system, and listen on that port to accept connection from other clients (chat). It prints out everyline it receives to the corresponding chat dialog, and also echos back the text line it sends out. The server implementation is pretty reliable now, the number of connections is not restricted and user with same IP or same name or both could talk without any problem. As a future work, we could futher improve the scalability by using multithread. So that when server is processing a request, it is also able to handle other requests.
When the simple server detect a new connection, it will create an instance of class "ChatDialog", which will take care of the future communication. Similarly, when the user wants to talk to someone on its online list, an instance of "ChatDialog" is created, which will start the connection and show the messages of the conversation.
class ChatDialog: public QDialog { Q_OBJECT public: ChatDialog(OnlineUser *user, QString name); ChatDialog(QSocket *s, QString name); signals: private slots: void closeDialog(); //Close the dialog by clicking "close" button void enableSend(const QString &text); //Enable "send" button only when line edit is changed to non-empty void sendMessage(); //Send the message void socketConnected(); //Connected to the desired host, show welcome logo void closeConnection(); //Close the connection void socketConnectionClosed(); //Connection closed by the other end, turn dialog inactive void textReadyRead(); //Read the text lines from socket and show them on the infoText void socketError(int e); //Show socket errors private: void startConnection(OnlineUser *user); //Connect to the host by calling connectToHost void respondConnection(QSocket *s); //Someone is connected, wait till it sends its name QTextView *infoText; //Show chat history QLineEdit *mesgText; //Edit messages QPushButton *sendButton; //Send button QPushButton *closeButton; //Close button QSocket *socket; //Socket with this chat QString me; //My own name (to show in infoText) QString who; //Name of the other end QString time; //Time of one message QString date; //Date of one message };