A message logger is a utility that helps in logging the messages thrown from functions/programs. It is useful in critical applications as it helps to know the time taken by a function to execute, the time that the function remained idle and any messages that the function wants to be logged for further analysis. If a program suddenly hangs then the log is useful in tracking in which particular function the program failed. We have all seen the messages thrown by any server on the console when it is booting. It is based on the logic of the message logger that we study here. Let us see how we can build a simple version of it.
While studying this article, we would also learn (in the sequence as shown. below):
- how to create a simple client-server application
- orking of the message logger explained in RealTimeRecord class
- ListView class explained in Display_Log class
- saving files to disk -explained in Display_Log class
- sizing the controls on the window when the window changes size
- Threading model in C# --explained in Display_Log class
Just to make the article more manageable, the RealTimeRecord class that contains the logic behind the entire working and the User Interface functionality (Display_Log class) is explained in part 2 and part3 of this article respectively.
A diagrammatic view of the screen
Thread_ListViewData class an explanation
Server
This is the class that acts as the server in our example listening for requests from clients. The client in our example is the RealTimeMessage class which sends the message string to the server. The server then displays the message on a ListView control. Let us dissect the server class and study its various features. The class Thread_ListViewData is initiated in the constructor of the Display_Log class as shown below:
public Display_Log()
{
InitializeComponent();
thr_viewdata = new Thread_ListViewData();
thr_viewdata.setSocket();
thr_viewdata.setListViewHandle(listView1.Handle);
Thread tid1 = new Thread(new ThreadStart
(thr_viewdata.disp_data_listview) );
tid1.Start();
}
The functionality of the Display_log class is discussed in part3 of the article. Once initiated, the class Thread_ListViewData calls the setSocket() function. We initiate a TcpListener object passing it the port number where the server would be running. TcpListener is a class in System.Net.Sockets namespace that listens for connections from TCP clients. We then call the Start() method which binds the underlying socket to the particular port number and is ready to listen to the clients request.
public void setSocket()
{
TcpListener listener=new TcpListener(8001);
listener.Start();
}
We would soon see that to actually detect the incoming requests we have to use the function Acceptsocket(..) of the TcpListener class. This function and the details of parsing of the data which comes from the client are discussed completely in the disp_data_listview() class. But before going into that we need to know the use of the function setListViewHandle(..) which is next called in the constructor of the Display_Log class. setListViewHandle(..) actually obtains the handle to the ListView control already created in the Display_Log class. See when we run the example we first see a window on the screen with a ListView control. Now the control is actually initialized in the Display_Log class (discussed in details in the part3 of the article) but where does it get its data?? . The data is sent from the client end to the server, which then formats it and displays it on the ListView control. So to access the ListView control class from the server, we need to have a pointer to it in the server class and that is exactly what the function setListViewHandle(..) does.
public void setListViewHandle(IntPtr hView)
{
ListView listView1 = (ListView)ListView.FromHandle(hView);
InitializeListView();
}
The function InitializeListView() creates a ListView control with the column headings as shown below
public void InitializeListView()
{
ColumnHeader header1 = this.listView1.Columns.Add(
"Clock_Tick",
100,
HorizontalAlignment.Center);
// and so on we create the column headings named
//Idle_Time, Function_Name, Status,
//Execute_Time and Message.
}
The most interesting part in the constructor of Display_Log class is the creation of thread, which it does next. We would study about the threading model in C# in part3 of the article. For now just note that the thread executes the function disp_data_listview() which we would study next.
The idea of keeping AcceptSocket() function in an infinite while(..) loop is that the server is in a continuous listen mode and dies only when killed explicitly.
The function AcceptSocket() looks for pending client requests. This function returns a socket on which to send and receive data. We then define a byte buffer and call the Receive() function of the socket class to receive the data in the byte buffer. We then convert the data (in bytes) to char and finally create a string object of the data. The data that we get from the client contains a number of fields like the clock_tick, idle_time, function_name, status, execute-time and message. The significance of the fields will become clear when we study the client. The String object, msg, that we have finally obtained contains these fields with a separator ( | ) between them. We next extract these fields as shown below:
public void disp_data_listview()
{
ArrayList hold_viewItems=new ArrayList();
while (true )
{
Socket s=listener.AcceptSocket();
while(true)
{
byte[] byte_data=new byte[256];
int bytedata_count=s.Receive(byte_data);
if( bytedata_count == 0 )
break;
char[] char_data = new char[bytedata_count];
for (int i=0;i<bytedata_count;i++)
char_data[i]=Convert.ToChar(byte_data[i]);
String msg = new String(char_data);
while(true)
{
int idd = msg.IndexOf("*");
msg = msg.Substring(idd+1);
int lastindex =0;
int idx=msg.IndexOf("|"); //clock_tick index
long clock_tick = Convert.ToInt64((msg.Substring(0,idx)));
lastindex = idx;
msg = msg.Substring(lastindex+1);
idx = msg.IndexOf("|"); //idle_time index
long idle_time = Convert.ToInt64((msg.Substring(0,idx)));
//and so on for the fields function_name,
//status, execute_time, message.
// instantiating a struct
listView_Items item_coll=new listView_Items();
//storing data in a struct
item_coll.clockTick=clock_tick;
item_coll.idleTime=idle_time;
item_coll.functionName=function_name;
item_coll.funcStatus=status;
item_coll.executeTime=execute_time;
item_coll.message=message;
hold_viewItems.Add(item_coll);
ListViewItem lvi = new ListViewItem(new string[]{
Convert.ToString(clock_tick)
,Convert.ToString(idle_time)
, function_name,status
,Convert.ToString(execute_time)
, message});
listView1.Items.Add(lvi);
int t = msg.IndexOf("*");
if( t==-1 )
break;
}
}
}
}
Now once the fields are extracted we have to do something with them. Our program does two things
- Display them in the ListView control
- Provides a facility to store them on the disk
To do the second option we have defined a struct called listView_Items.
public struct listView_Items
{
public long clockTick;
public long idleTime;
public String functionName;
public String funcStatus;
public long executeTime;
public String message;
}
We instantiate an instance of the struct and put all the fields in them. Next we need to hold each instance of the struct some-where.So, we define an ArrayList hold_viewItems at the beginning of the function. We add each struct instance to the ArrayList. This ArrayList is then called when we need to save the data to the disk. We would study an ArrayList class and saving data to disk in part3 of the article. Next we add the fields to the ListViewItem class and add that to the ListView control. To know how it is actually done see the part3 of the article where we have covered ListView in details
Conclusion: We have so far studied the server and seen how it waits for requests from the client and once a request comes to it what it does with it. Let us now study the client and see how it sends message to the server. In the process we would also study the logic behind the message logger. To study about it please click the link shown: Real time message logger part 2