Friday, October 23, 2009

Client to Client file transfer using C# .Net Programming - Remoting technology

For basic information regarding .Net Remoting technology you may refer my previous article “Global Text Chat Room Application Using C#.NET Remoting Technology”.

User manual:



To run that application first you have to run server application. Then you will get one winforms with two buttons ‘Start’ and ‘Stop’ as screenshot. Your have to run server by pressing Start button.

Now run client application. Here you will get first a winform which will ask your name and server address, port etc. If you try with same machine then the given address is ok. But if it different address then need to update ‘localhost’ with particular IP address or host name. But do not change port number and remaining thing and press enter or join button. See screenshot.

Then you will get another form. Here you will see online users in a TreeView. From here you need to select a user by double clicking on his/her name, and then need to select a file by pressing ‘Select a File’ button. A file dialog box window will come from where you need to select a file. Then press Send button. On receiver end one popup will come which will ask receiver will he receive it reject. If select receive then will ask a path where it will save. Then the file will transfer from sender to receiver.





Application logic:

For file transfer I have used basic codes of my previous project. Here also has three part –

(1) Base Remote Class (DLL)

(2) Server Class and

(3) Client Class

Server and Client class is using Base Remote Class.

In Base Remote Class have three sections (region) –

  1. Join to server and user related information
  2. File related information – file name, size, sender name, receiver name etc. and
  3. File transferring between two users.

For online user list I have used ArrayList object and for transferring data have used queue by HashTable.

In Server Class nothing very special. Here have just two methods, for starting server and stopping server. Start server is opening a TCPChannel and registering it, on the other side Stop Server is closing TCPChannel and unregistering it.

For login to server it exactly same as my previous article “Global Text Chat Room Application Using C#.NET Remoting Technology”.

In Client Class, there have few interesting parts. The major is a timer event. By this event client is checking to server if any data/information is available to it or not. Also by that timer collects latest user related information from server.

When client wants to send a file to some one then first it reads file related information and sends it to server with receiver id through SetFileInfo method of RemoteObject. Then it reads the file by a BinaryReader part by part and sends each part to server. In Server side it stores in a Hashtable from which receiver will receives it and server will remove each part one by as and when receiver confirms to remove after receivers’ acceptance. Here major roles plays SendFile method and Timer tick of Client and SendDataToServer, GetDataFromServer method of remote object class.

Basic architecture of my application is that, remaining of all you can understand if you go though the codes.

Remoting / Common base code is here:

using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Collections;


namespace RemoteBase

{

///

/// Sample object to demonstrate the use of .NET Remoting.

///

public class SampleObject : MarshalByRefObject

{

ArrayList alOnlineUser = new ArrayList();

Hashtable htUserData = new Hashtable();

Hashtable htFileData = new Hashtable();

private int key = 0;

private string receiverID,senderID,fileName,fileTransferStatus;

private int fileSize;

private bool isFileTransferring = false;


#region USER JOINING AND ONLINE USER RELATED

public bool JoinToChatRoom(string name)

{

if (alOnlineUser.IndexOf(name) > -1)

return false;

else

{

alOnlineUser.Add(name);

return true;

}

}

public void LeaveChatRoom(string name)

{

alOnlineUser.Remove(name);

}

public ArrayList GetOnlineUser()

{

return alOnlineUser;

}

public int CurrentKeyNo()

{

return key;

}

#endregion


#region FILE INFORMATION ACCESS BETWEEN SENDER AND RECEIVER

public bool SetFileInfo(string senderId, string receiverId, string fileName, int fileSize)

{

if (!this.isFileTransferring)

{

lock (this)

{

this.isFileTransferring = true;

}

this.senderID = senderId;

this.receiverID = receiverId;

this.fileName = fileName;

this.fileSize = fileSize;

return true;

}

else//Now some file transferring, so need to wait

return false;

}

public string GetFileName_Size_SenderId(string receiverId)

{

if (this.receiverID == receiverId)

return this.fileName + ":" + this.fileSize.ToString() + ":" + this.senderID;

else

return null;

}

#endregion



#region FILE TRANSFER BETWEEN SENDER AND RECEIVER


///

/// This method is used to receive data from sender and hold in a array to deliver receiver.

///

///

///

///

///

public bool SendDataToServer(byte[] data, int dataSliceNo, string senderId)

{

lock (this)

{

htFileData.Add(receiverID + dataSliceNo.ToString(), data);//Key= receiverId + sliceNo, binary data

}

return true;

}

// This method is used to receive data from server to receiver.

public byte[] GetDataFromServer(string receiverId, int nextSliceNo)

{

if (htFileData.Contains(receiverID + nextSliceNo.ToString()))

{

lock (this)

{

byte[] tempByteData = (byte[])htFileData[receiverID + nextSliceNo.ToString()];

htFileData.Remove(receiverID + nextSliceNo.ToString());//Key= receiverId + sliceNo

return tempByteData;

}

}

else

return null;

}


///

/// This method is used to declare that file has succesfully received by the receiver. It invokes after the whole file

/// received by the receiver.

///

///

public void ReceiveFileConfirm(string clientID)

{

if (clientID == receiverID)

{

this.fileTransferStatus = "File transfered successfully by " + this.receiverID;

this.receiverID = "";

lock (this)

{

this.isFileTransferring = false;

htFileData.Clear();

}

}

}

public void RejectFile(string clientID)

{

if (this.receiverID == clientID)

{

this.fileTransferStatus = "File has rejected by " + this.receiverID;

this.receiverID = "";

lock (this)

{

this.isFileTransferring = false;

htFileData.Clear();

}

}

}

#endregion

}

}


Server code is here:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using RemoteBase;

namespace RemoteServer

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

TcpChannel channel;

private void btnStart_Click(object sender, EventArgs e)

{

if (channel == null)

{

channel = new TcpChannel(8080);

ChannelServices.RegisterChannel(channel, false);

RemotingConfiguration.RegisterWellKnownServiceType(typeof(SampleObject), "HelloWorld", WellKnownObjectMode.Singleton);


lblStatus.Text = "Running...";

btnStart.Enabled = false;

btnStop.Enabled = true;

}

}


private void btnStop_Click(object sender, EventArgs e)

{

if (channel != null)

{

ChannelServices.UnregisterChannel(channel);

channel = null;

lblStatus.Text = "Stopped.";

btnStart.Enabled = true;

btnStop.Enabled = false;

}

}


}

}


Client code is here:

Login and server connecting code:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Collections;

using RemoteBase;

namespace RemotingClient

{

public partial class frmLogin : Form

{

TcpChannel chan;

ArrayList alOnlineUser = new ArrayList();

frmChatWin objChatWin;

public frmLogin()

{

InitializeComponent();

}


private void btnJoin_Click(object sender, EventArgs e)

{

JoinToChatRoom();

}

private void JoinToChatRoom()

{

if (chan == null && txtName.Text.Trim().Length != 0)

{

chan = new TcpChannel();

ChannelServices.RegisterChannel(chan,false);


// Create an instance of the remote object

objChatWin = new frmChatWin();

objChatWin.remoteObj = (SampleObject)Activator.GetObject(typeof(RemoteBase.SampleObject), txtServerAdd.Text);


if (!objChatWin.remoteObj.JoinToChatRoom(txtName.Text))

{

MessageBox.Show(txtName.Text+ " already joined, please try with different name");

ChannelServices.UnregisterChannel(chan);

chan = null;

objChatWin.Dispose();

return;

}

objChatWin.key = objChatWin.remoteObj.CurrentKeyNo();

objChatWin.myName= txtName.Text;


this.Hide();

objChatWin.Show();

}

}

}

}

File transferring code:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Collections;

using RemoteBase;

using System.IO;

using System.Threading;

namespace RemotingClient

{

public partial class frmChatWin : Form

{

internal SampleObject remoteObj;

internal int key = 0,fileSize;

int sliceSize = 5 * 1024;

internal string myName,fileName;

ArrayList alOnlineUser = new ArrayList();

string selectedUserName;

OpenFileDialog ofd;

public frmChatWin()

{

InitializeComponent();

}


private void btnSend_Click(object sender, EventArgs e)

{

SendFile();

}

private void timer1_Tick(object sender, EventArgs e)

{

timer1.Stop();

if (remoteObj != null)

{

string receiveFileName, senderId;

int fileSize;

string FileName_Size = remoteObj.GetFileName_Size_SenderId(myName);

if (FileName_Size != null)

{


string[] recvFileDesc = FileName_Size.Split(':');

receiveFileName = recvFileDesc[0];

fileSize = int.Parse(recvFileDesc[1]);

senderId = recvFileDesc[2];


DialogResult usrRes = MessageBox.Show(senderId + " want to send a file. Will you accept it?", "", MessageBoxButtons.YesNo);

if (usrRes == DialogResult.Yes)

{

FolderBrowserDialog fbdSelect = new FolderBrowserDialog();

fbdSelect.Description = "Select a path to save received file.";

if (fbdSelect.ShowDialog() == DialogResult.OK)

{

BinaryWriter bWrite = new BinaryWriter(new FileStream(fbdSelect.SelectedPath+"\\" + receiveFileName, FileMode.Append));

for (int i = 0; i * sliceSize <= fileSize; )

{

byte[] buffer = remoteObj.GetDataFromServer(myName, i + 1);//i+1 because when data send to server the it starts from 1

if (buffer != null)

{

bWrite.Write(buffer);

i++;

}

}

bWrite.Close();

remoteObj.ReceiveFileConfirm(myName);

MessageBox.Show("File received successfully.");

}

}

else

{

remoteObj.RejectFile(myName);

}

}

ArrayList onlineUser = remoteObj.GetOnlineUser();

foreach (string name in onlineUser)

{

if (name != myName && !tvOnlineUser.Nodes.ContainsKey(name))

tvOnlineUser.Nodes.Add(name, name);

}

}

//**** Button Enable - Disable

if (tvOnlineUser.Nodes.Count > 0 && lblSelUser.Text.Length > 15)

btnSelect.Enabled = true;

else

btnSelect.Enabled = false;

btnSend.Enabled = false;

if (ofd != null)

if (ofd.FileName.Length > 0)

btnSend.Enabled = true;

timer1.Start();

}

private void SendFile()

{

if (remoteObj != null)

{

BinaryReader bRead = new BinaryReader(new FileStream(ofd.FileName, FileMode.Open));

fileSize = (int)bRead.BaseStream.Length;

if (fileSize > 1024 * 1024 * 50)

MessageBox.Show("You can send maximum 50 MB file.", "Limit cross!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

else

{


byte[] smallPiece;


if (fileSize <>

{

smallPiece = new byte[fileSize];

bRead.Read(smallPiece, 0, fileSize);

}

else//File is more than 5KB

{

smallPiece = new byte[sliceSize];

if (remoteObj.SetFileInfo(myName, selectedUserName, fileName, fileSize))

{

for (int i = 1; i * sliceSize <= fileSize + sliceSize; )

{

if (i * sliceSize <= fileSize) // Last slice yet not reached

{

bRead.Read(smallPiece, 0, sliceSize);

if (remoteObj.SendDataToServer(smallPiece, i, myName))

i++;

}

else//Last slice of data is going to fetch and for last slice data remains less than 5KB

{

int remainDataSize = fileSize - ((i - 1) * sliceSize);

smallPiece = new byte[remainDataSize];

bRead.Read(smallPiece, 0, remainDataSize);

if (remoteObj.SendDataToServer(smallPiece, i, myName))

i++;

}

}

}//End of SetFileInfo

}

}

bRead.Close();

}

}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)

{

if (remoteObj != null)

{

remoteObj.LeaveChatRoom(myName);

}

Application.Exit();

}


private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)

{

System.Diagnostics.Process.Start("iexplore.exe", "http://socketprogramming.blogspot.com");

}

private void btnSelect_Click(object sender, EventArgs e)

{

ofd = new OpenFileDialog();

if (ofd.ShowDialog() == DialogResult.OK)

{

lblSelectedFile.Text = "Selected File: "+ofd.SafeFileName;

fileName = ofd.SafeFileName;

}

}


private void tvOnlineUser_DoubleClick(object sender, EventArgs e)

{

selectedUserName = tvOnlineUser.SelectedNode.Name;

lblSelUser.Text = "Selected User: "+selectedUserName;

}

}

}


Tuesday, August 18, 2009

Global Text Chat Room Application using C#.Net Programming - Remoting technology

The basic and simple architecture of .Net Remoting technology has three parts, these are –
1. Base Remoting class: This is like a bridge to communicate between Client and Server. It exists in a DLL file which shares Server and Client program.
2. Server class: It is server to server Client requests, every client connect to Server to communicate each other. This program holds a Remoting class’s DLL.
3. Client class: This is client part of Remoting architecture. This also holds a copy of Remoting base DLL. It connects to Server and via server communicates to other client.This is very simple idea of Remoting architecture, if you want to learn about this technology then you may read from MSDN site. I am not going to explain about its theory, I am focusing mainly about its application.

Now I will describe about a Global Text Chat Room application using this technology. This is very easy to develop and interesting also. In this program I have not covered about thread related issues, this is very basic type of chat application.



As Remoting architecture here has a base class and after compiling produces a DLL file with name ‘RemoteBase.dll’. This DLL has about six methods like, JoinToChatRoom, LeaveChatRoom, SendMsgToSvr (Send Message To Server), GetMsgFromSvr (Get Message From Server) etc.




Next one is Server, this is a Windows Form (WinForm) application. This application uses ‘RemoteBase.dll’ as its reference file for library. Server registers a TCP channel with a port number. You may choose any port number from 1025 to 65k. And it registers for well known type of RemoteBase and mode type is Singleton. (Remember it should not work for Singlecall type, details and different will found on MSDN).
When you run server you will see a window as attached screen shot and need to press button ‘Start’ to start server and check server status as ‘Running’. To stop the server need to press on ‘Stop’ button.




Last one is Client part, it also a Windows form (WinForm) application with two windows forms. As server client also take reference of ‘RemoteBase.dll’ for library. When you run this client application one popup window will come and ask for your name which will be used to chat room to represent you. Then press on Join button. After that chat room window will open.

There also has server address like ‘tcp://localhost:8080/HelloWorld’ here ‘localhost’ is server address and 8080 is port number. Server address needs to tell where your server is running. I am using server and client in same machine so server address is ‘localhost’ you may give any IP address here. Port number also can be changed but server opening port number and client requesting port number should be same. You can not change reaming thing in address otherwise this chat application will not work.


Chat room window has four sections largest one to see all chat message and below of that to type chat message, and send button to send message to server. Just above list to display all online user.

When you put your name then client application creates a remote base class’s object and connects to server by registering TCP channel. Then connects to Chat Room and seek latest message number. After that main Chat Room window opens. From that window it seeks latest available message in server by a timer. To get message from server it invokes ‘GetMsgFromSvr()’, and get available online user through ‘GetOnlineUser (), and user message sends from client application to server by invoking ‘SendMsgToSvr()’. For better understanding you may go through the code.

Source code as below and full source code can download from following link:

How the code is working

Start the Server:

When users are trying start Server by clicking Start button the following code executes –


private void btnStart_Click(object sender, EventArgs e)

{
if (channel == null)
{
channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(SampleObject), "ChatRoom"WellKnownObjectMode.Singleton);
lblStatus.Text = "Running...";
btnStart.Enabled = false;
btnStop.Enabled = true;
}
}
Here a TcpChannel opens with port number 8080 and register it as WellKnownServiceType. ‘ChatRoom’ it is the ‘ObjectUri’ it will require to connect to server from client.
On the other hand when user press to ‘Stop’ server then unregister the channel and stop the server. Codes are below-
private void btnStop_Click(object sender, EventArgs e)
{
if (channel != null)
{
ChannelServices.UnregisterChannel(channel);
channel = null;
lblStatus.Text = "Stopped.";
btnStart.Enabled = true;
btnStop.Enabled = false;
}

}


Join to ChatRoom:
Next coming the client, how client connect to server and user login to the chat room. To joining to chat room the below codes are executes
private void JoinToChatRoom()
{
if (chan == null && txtName.Text.Trim().Length != 0)
{
chan = new TcpChannel();
ChannelServices.RegisterChannel(chan,false);
// Create an instance of the remote object
objChatWin = new frmChatWin();
objChatWin.remoteObj = (SampleObject)Activator.GetObject(typeof(RemoteBase.SampleObject), txtServerAdd.Text);
if (!objChatWin.remoteObj.JoinToChatRoom(txtName.Text))
{
MessageBox.Show(txtName.Text+ " already joined, please try with different name");
ChannelServices.UnregisterChannel(chan);
chan = null;
objChatWin.Dispose();
return;
}
objChatWin.key = objChatWin.remoteObj.CurrentKeyNo();
objChatWin.yourName= txtName.Text;
this.Hide();
objChatWin.Show();
}
}
Here from user client application takes a name and check to server is the name available or not. If name is available then user gets the ‘CurrentKeyNo’ of server, it is the number of last chat message (how the key generated, describe in later) and open the ChatRoom window.
If user name is already taken by other user, then application asks to user for different name.
In server side to join a user in chat server “JoinToChatRoom()” method invokes, lets see what happens within the method –

public bool JoinToChatRoom(string name)
{
if (alOnlineUser.IndexOf(name) > -1)
return false;
else
{
alOnlineUser.Add(name);
SendMsgToSvr(name + " has joined into chat room.");
return true;
}
}
Here is user can successfully logged in to server then his name is added in a user collection, ‘alOnlineUser’ is it an ArrayList type object.


Send message to server:
When user type some message and press on ‘Send’ button or just press ‘Enter’ button then client application try to send message to server. To do it client call the below method –
private void SendMessage()
{
if (remoteObj != null && txtChatHere.Text.Trim().Length>0)
{
remoteObj.SendMsgToSvr(yourName + " says: " + txtChatHere.Text);
txtChatHere.Text = "";
}
}
Here client application in invokes the “SendMsgToSvr()” method of server. Let’s see the method what doing-
public void SendMsgToSvr(string chatMsgFromUsr)
{
hTChatMsg.Add(++key, chatMsgFromUsr);
}
Wow! This is very small code. Actually this is adding the users’ message to another collection. I have used for this collection of HashTable type, you may use any other collection type to store string data.
See here has one counter ‘key’ which is incrementing by one. This is the counter which is maintaining the chat message number. It will help us to get chat message from server.
Receive Message from Server:
Ok friend, next look at how data are getting from server –
In chat room a timer always fires, which try to get message from server and current available user in server. Here below codes plays in Client side –
private void timer1_Tick(object sender, EventArgs e)
{
if (remoteObj != null)
{
string tempStr = remoteObj.GetMsgFromSvr(key);
if (tempStr.Trim().Length > 0)
{
key++;
txtAllChat.Text = txtAllChat.Text + "\n" + tempStr;
}
ArrayList onlineUser = remoteObj.GetOnlineUser();
lstOnlineUser.DataSource = onlineUser;
skipCounter = 0;
if (onlineUser.Count < 2)
{
txtChatHere.Text = "Please wait untill atleast two user join in Chat Room.";
txtChatHere.Enabled = false;
}
else if(txtChatHere.Text == "Please wait untill atleast two user join in Chat Room." && txtChatHere.Enabled == false)
{
txtChatHere.Text = "";
txtChatHere.Enabled = true;
}
}
}
Here client invokes “GetMsgFromSvr()” method to get message, with parameter key. The codes of the method are –
public string GetMsgFromSvr(int lastKey)
{
if (key > lastKey)
return hTChatMsg[lastKey + 1].ToString();
else
return "";
}
The server just takes the key as last-key of user’s message and uses it in Chat message collection to fetch the next chat message, after that the message return to the client.
To online user client application invokes “GetOnlineUser()” method, lets see what happen in server side within the method.
public ArrayList GetOnlineUser()
{
return alOnlineUser;
}
So this method returns the user collection object which was created in “JoinToChatRoom()” method.

Leave the Chat Room:
When user closes the chat room, then client application request to server to remove his/her name from server’s online user list. In client side the below method invokes –
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (remoteObj != null)
{
remoteObj.LeaveChatRoom(yourName);
txtChatHere.Text = "";
}
Application.Exit();
}
In server side the “LeaveChatRoom()”method invokes, the code of that method is –
public void LeaveChatRoom(string name)
{
alOnlineUser.Remove(name);
SendMsgToSvr(name + " has left the chat room.");
}
Now server has deleted your name from online user list.