I'm having a problem getting this chunk of code to work (sorry for the length). The code is sterilized to just show the relevant portions in diagnosing the problem.
It works fine when run from a console app.
But when Utlities.SendBulkEmail is called from an ASP.NET app, the BulkEmailCompleted routine never fires, and it is this routine that increments the m_CompletedWorkers counter.
How do I refactor the SendBulkEmail routine to use AsyncOperationManager instead of BackgroundWorker, so I can guarantee the thread that the results are returned on.
The SendBulkEmail routine itself is not multi-threaded. The multi-threading happens inside its foreach loop.
I think the basis of the original code was gotten from this website:
http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications.aspx
The Utilities project is shared among various solutions, and is pretty much stand alone.
I'm hoping that I'm making this clear.
Any help will be appreciated.
The code follows:
IN THE WEBSITE PROJECT (Control.ascx.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Configuration;
using <company>.BusinessObjects.Emails;
using <company>.DataAccessors;
using <company>.DataObjects;
using <company>.Enumerations;
using <company>.Interfaces;
using <company>.Utilities;
...
protected void sendButton_OnClick(object sender, EventArgs e)
{
...
if (HasBenefits)
{
ReportingEmails emailer = new ReportingEmails();
...
//Prevent send if nothing selected
if (... > 0)
{
List<EmailOutcome> results = emailer.GenerateNotificationEmailsForEmployer(<some int>, <some list>);
...
}
}
...
}
IN THE BUSINESS OBJECT PROJECT
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Mail;
using System.Resources;
using System.Text;
using System.IO;
using <company>.BusinessObjects.Emails.Helpers;
using <company>.DataAccessors;
using <company>.DataObjects;
using <company>.Enumerations;
using <company>.Utilities;
namespace <company>.BusinessObjects.Emails
{
public class ReportingEmails
{
...
public List<EmailOutcome> GenerateNotificationEmailsForEmployer(int employerID, List<int> benefits = null)
{
...
SendNotificationEmails(List<ReportData>, ref emailSuccessByBenefit, true, benefitsToExclude);
return emailSuccessByBenefit;
}
private void SendNotificationEmails(List<ReporterCommsData> reportData, ref List<EmailOutcome> emailSuccessByBenefit, bool isFleet, List<int> benefitsToExclude)
{
Dictionary<int, MailMessage> bulkEmails = new Dictionary<int, MailMessage>();
//build up the set of emails to send
foreach (ReporterCommsData report in reportData)
{
...
if (String.IsNullOrEmpty(report.Email))
{
...
}
else
{
try
{
MailMessage email = null;
...
email = ConstructEmail(<param>, out <param>, <param>);
...
bulkEmails.Add(report.BenefitID, email); //add each email to the bulk email dictionary
...
}
catch (Exception ex)
{
...
}
}
} //end foreach
//do the bulk email send and get the outcomes
try
{
...
emailSuccessByBenefit.AddRange(Utilities.Mail.SendBulkEmail(bulkEmails, credentials));
}
catch (Exception ex)
{
...
}
}
...
}
...
}
IN THE UTILITIES PROJECT
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Threading;
namespace <company>.Utilities
{
...
public class Mail
{
private static List<EmailOutcome> m_MultithreadedEmailSendResults = new List<EmailOutcome>();
private static int m_CompletedWorkers = 0;
...
/// <summary>
/// Sends a large number of emails asynchronously and then reports success of the individual items collectively
/// </summary>
/// <param name="emails">A dictionary of completed MailMessage objects to send out, keyed on an ID</param>
/// <param name="credentials">Network credentials which may be required to send the email</param>
/// <returns>List of EmailOutcome objects signifying the success or failure of sending each individual email</returns>
public static List<EmailOutcome> SendBulkEmail(Dictionary<int, MailMessage> emails, System.Net.NetworkCredential credentials = null)
{
const int NUMBER_OF_THREADS_PER_PROCESSOR = 1;
m_CompletedWorkers = 0;
List<EmailOutcome> results = new List<EmailOutcome>();
List<Dictionary<int, MailMessage>> splitEmailList = new List<Dictionary<int, MailMessage>>();
...
List<BackgroundWorker> workerThreads = new List<BackgroundWorker>();
foreach (Dictionary<int, MailMessage> splitEmails in splitEmailList)
{
// Initialise the parameter array
Object[] parameterArray = new Object[2];
parameterArray[0] = splitEmails;
parameterArray[1] = credentials;
// Runs on function startup
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(BulkEmailWorker);
worker.WorkerReportsProgress = false;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BulkEmailCompleted);
//Add worker to collection
workerThreads.Add(worker);
//Calling the BulkEmailWorker asynchronously
worker.RunWorkerAsync(parameterArray);
}
//Hold until all background workers complete
while (workerThreads.Count > m_CompletedWorkers)
{
Thread.Sleep(500); //Wait a half second
}
//Dispose of BackgroundWorkers
foreach (BackgroundWorker worker in workerThreads)
{
worker.Dispose();
}
//Get results
results.AddRange(m_MultithreadedEmailSendResults);
//Clear the static variable
m_MultithreadedEmailSendResults.Clear();
m_MultithreadedEmailSendResults = new List<EmailOutcome>();
return results;
}
...
/// <summary>
/// Event handler for the RunWorkerCompleted event. Adds the EmailOutcome results to the static
/// </summary>
private static void BulkEmailCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//Add the EmailOutcome objects to the static list of results if completed
if (worker != null)
{
Thread.Sleep(200);
worker.RunWorkerAsync();
}
else
{
m_MultithreadedEmailSendResults.AddRange(e.Result as List<EmailOutcome>);
m_CompletedWorkers++;
}
}
...
}
...
}
The way asp.net works is that it creates a Page object for each request. The same goes for controls. Your notifications of the emails being sent can only reach this object.
The threads you create will run take their time to execute, and your response will not wait for the email sending to complete. This means that you can't send the status using the same request.
However if you make another request, ajax or otherwise from the page, to get the updated status, a new Page and corresponding control objects will be created. You will have to get the status from you static objects and use that to show the status to a user.
You may find the UpdatePanel control handy to implement ajax.
Related
The WebBrowser.Print method has the limitation of not allowing the caller to specify a printer other than the system's default one. As a workaround, it has been suggested[1], [2] to alter the system's default printer prior to calling Print(), however it's also reported[3] (and I experienced firsthand) that the WebBrowser instance will continue to print to the previously defined printer even after the system default is altered.
To work around that, registering a handler to the PrintTemplateTeardown event by accessing the underlying ActiveX object of the managed WebBrowser instance and waiting for the event to fire before printing further documents has been proposed[4], [5], and that is what I am trying to implement. I simplified what is a much more complex program to the MVCE presented below.
(The program is a .NET Core 3.1 Windows Forms application, with one form containing nothing more than a BackgroundWorker object named bw.)
Form1.cs
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Printing;
using System.Management;
namespace Demo_1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
bw.RunWorkerAsync();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
BwPolling();
Thread.Sleep(20000);
}
}
private void BwPolling()
{
string[] htmlStrings = { "test1", "test2" };
foreach (string html in htmlStrings)
{
Invoke((MethodInvoker)delegate
{
PrinterSettings.StringCollection installedPrinters = PrinterSettings.InstalledPrinters;
foreach (string printer in installedPrinters)
{
string[] validPrinterNames =
{
"Microsoft Print to PDF",
"Microsoft XPS Document Writer"
};
if ( validPrinterNames.Contains(printer) )
{
SetDefaultPrinter(printer);
var wb = new WebBrowser();
wb.DocumentText = html;
// With inspiration from code by Andrew Nosenko <https://stackoverflow.com/users/1768303/noseratio>
// From: https://stackoverflow.com/a/19737374/3258851
// CC BY-SA 3.0
var wbax = (SHDocVw.WebBrowser)wb.ActiveXInstance;
TaskCompletionSource<bool> printedTcs = null;
SHDocVw.DWebBrowserEvents2_PrintTemplateTeardownEventHandler printTemplateTeardownHandler =
(p)
=> printedTcs.TrySetResult(true); // turn event into awaitable task
printedTcs = new TaskCompletionSource<bool>();
wbax.PrintTemplateTeardown += printTemplateTeardownHandler;
try
{
MessageBox.Show("Printing to " + printer);
wb.Print();
printedTcs.Task.Wait();
}
finally
{
wbax.PrintTemplateTeardown -= printTemplateTeardownHandler;
}
wb.Dispose();
}
}
});
}
}
private static bool SetDefaultPrinter(string name)
{
// With credits to Austin Salonen <https://stackoverflow.com/users/4068/austin-salonen>
// From: https://stackoverflow.com/a/714543/3258851
// CC BY-SA 3.0
using ( ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer") )
{
using ( ManagementObjectCollection objectCollection = objectSearcher.Get() )
{
foreach (ManagementObject mo in objectCollection)
{
if ( string.Compare(mo["Name"].ToString(), name, true) == 0 )
{
mo.InvokeMethod("SetDefaultPrinter", null);
return true;
}
}
}
}
return false;
}
}
}
The problem being faced is when I remove the message box from the BwPolling() method, right before calling Print(), i.e. when this line is removed:
MessageBox.Show("Printing to " + printer);
then the program freezes, nothing is printed, and the process must eventually be terminated.
I believe I can sort of understand the issue on its surface: WebBrowser requires an STA thread with an active message loop[6], [7]; by calling printedTcs.Task.Wait(); within a Invoke((MethodInvoker)delegate block (called on the Form1 instance; this. is ommited), I am blocking the STA thread and the application hangs waiting for an event that is never fired. This is in fact mentioned in a comment under the answer I credited in my code.
Just can't figure out what a proper solution would be. Got lost in attempts to run the printing routine in a secondary thread. Maybe something wrong in my execution, guess I require assistance in this. Any help?
Thanks.
I created 2 simple C# Console Projects (.net 4.5.2), added the v4.0.0.1 NetMQ Nuget package to each, loaded each program up into separate Visual Studio 2017 Community Editions, put a breakpoint on the 1 line contained within the OnReceiveReady callback method, started the subscriber program first, then started the publisher program. The ReceieveReady event is not being triggered in the subscriber. What am I doing wrong? Even if I chose subSocket.Subscribe("") then I still didn't get any messages received. Also, removing/modifying the Send/Receive HighWatermarks didn't change things either. Thanks for your help!
Here's the Publisher code:
using System;
using NetMQ;
using NetMQ.Sockets;
using System.Threading;
namespace SampleNQPub
{
class Program
{
static void Main(string[] args)
{
var addr = "tcp://127.0.0.1:3004";
using (var pubSocket = new PublisherSocket())
{
Console.WriteLine("Publisher socket binding.");
pubSocket.Options.SendHighWatermark = 10;
pubSocket.Bind(addr);
for (int i=0; i < 30; i++)
{
pubSocket.SendMoreFrame("NQ").SendFrame(i.ToString());
Thread.Sleep(1000);
}
pubSocket.Disconnect(addr);
}
}
}
}
Here's the Subscriber code:
using System.Threading;
using NetMQ;
using NetMQ.Sockets;
namespace SampleNQSub
{
class Program
{
static void Main(string[] args)
{
var addr = "tcp://127.0.0.1:3004";
using (var subSocket = new SubscriberSocket())
{
subSocket.ReceiveReady += OnReceiveReady;
subSocket.Options.ReceiveHighWatermark = 10;
subSocket.Connect(addr);
subSocket.Subscribe("NQ");
for (int i=0; i < 20; i++)
{
Thread.Sleep(1000);
}
subSocket.Disconnect(addr);
}
}
static void OnReceiveReady(object sender, NetMQSocketEventArgs e)
{
var str = e.Socket.ReceiveFrameString();
}
}
}
Ok, this is a gotcha question in the NetMQ world and I just figured it out. You MUST setup a NetMQPoller that will wind up calling every ReceiveReady callback which you have added to it (NetMQPoller).
Here is the corrected code which will at least (i.e., ReceiveFrameString still only getting the "NQ" part but that's just another method call to fix) get the ReceiveReady event triggered:
using System.Threading;
using System.Threading.Tasks;
using NetMQ;
using NetMQ.Sockets;
namespace SampleNQSub
{
class Program
{
static void Main(string[] args)
{
var addr = "tcp://127.0.0.1:3004";
NetMQPoller poller = new NetMQPoller();
using (var subSocket = new SubscriberSocket())
{
subSocket.ReceiveReady += OnReceiveReady;
subSocket.Options.ReceiveHighWatermark = 10;
subSocket.Connect(addr);
subSocket.Subscribe("NQ");
poller.Add(subSocket);
poller.RunAsync();
for (int i = 0; i < 20; i++)
{
Thread.Sleep(1000);
}
subSocket.Disconnect(addr);
}
}
static void OnReceiveReady(object sender, NetMQSocketEventArgs e)
{
var str = e.Socket.ReceiveFrameString();
e.Socket.ReceiveMultipartStrings();
}
}
}
I noticed that the authors of NetMQ decided in 4.x to take care of the Context object internally so the user wouldn't have to bare the burden of managing it. It would be nice also if they could hide this "polling pump" code from the user as well for the most simple use case.
As a comparison, take a look at the subscriber using NodeJS (with the zmq library) utilizing the Publisher console app I posted above (save this code to sub.js and, in a Windows console, type 'node sub.js'):
var zmq = require('zmq'), sock = zmq.socket('sub');
sock.connect('tcp://127.0.0.1:3004');
sock.subscribe('NQ');
console.log('Subscriber connected to port 3004');
sock.on('message', function() {
var msg = [];
Array.prototype.slice.call(arguments).forEach(function(arg) {
msg.push(arg.toString());
});
console.log(msg);
});
So where's the poller pump mechanism in this? (Answer: I don't care! I just want the messages supplied to me in a callback that I register. [Obviously, tongue-in-cheek. I get that a NetMQPoller is versatile and handles more complex issues, but for basic "give me a message in a callback when it arrives", it would be nice if it were handled internally by the library.])
As a complete beginner in the field of instant messaging (using XMPP protocol), as well as windows phone 8.1 app development; I am trying to start off by using xmedianet library in order to connect to a server and communicate using XMPP protocol. After implementing the following example and tweaking it to my needs.
Here's the part of the code where I configure the connection parameters:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using WP8Xmpp.Resources;
using System.Net.XMPP;
using System.Net.Sockets;
using System.Threading;
namespace WP8Xmpp
{
public partial class MainPage : PhoneApplicationPage
{
private Boolean IsXmppSuccess { get; set; }
/// <summary>
/// Xmpp Client
/// </summary>
public XMPPClient ObjXmppClient { get; set; }
/// <summary>
/// XMPP Connection
/// </summary>
public XMPPConnection ObjXmppCon { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
IsXmppValid();
}
private void IsXmppValid()
{
ObjXmppClient = new XMPPClient();
//initializing the xmpp client with credentials
ObjXmppClient.JID = "user#domain.com";
ObjXmppClient.JID.Resource = Guid.NewGuid().ToString();
ObjXmppClient.Password = "acc_password";
ObjXmppClient.Server = "server_uri";*/
ObjXmppClient.AutoReconnect = true;
ObjXmppClient.Port = 81; // I've already tried 5222 but 81 is the correct port in this server's case.
ObjXmppClient.RetrieveRoster = true;
ObjXmppClient.PresenceStatus = new PresenceStatus() { PresenceType = PresenceType.available, IsOnline = true };
ObjXmppClient.AutoAcceptPresenceSubscribe = true;
ObjXmppClient.AttemptReconnectOnBadPing = true;
ObjXmppCon = new XMPPConnection(ObjXmppClient);
ObjXmppCon.Connect();
ObjXmppClient.Connect();
//initializing the xmpp connection
ObjXmppCon.OnAsyncConnectFinished += ObjXmppCon_OnAsyncConnectFinished;
ObjXmppClient.OnStateChanged += new EventHandler(xMPPClient_OnStateChanged);
Thread.Sleep(2000);
} ...
When I launch this application using the WP 8.1 emulator and attempt a connection. Everything works fine until the Resource Binding step. I get the following output on the VS2013 console:
<--stream:features><ver xmlns="urn:xmpp:features:rosterver"/><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>
Setting state to CanBind
Setting state to Binding
<--<
<--iq id="xxxxxxxx-xxxx-xxxx-xxxx-0f08b82b9f1f" to="user#domain/Resource" xmlns="jabber:client" type="result"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>user#domain/Resource</jid></bind></iq>
Followed by nothingness except for a few "thread exit" messages and the program terminating after a few minutes.
I've been at it for days now.. I've tried all possible scenarios for the connection parameters, even tried fiddling with the library's code to no avail. Could anyone try to replicate this configuration with the same library to see if it's a problem on my side?
Note: connection to the server with the same account using another xmpp client works just fine.
Turned out the problem was that, for some reason, the "Bound" state was not firing even though the binding attempt returned a successful result. I've managed to "fix" this the ugly way by using Thread.sleep() and then changing the state manually to "bound" (note that the same trick is used to get to the "session" state). Here's a sample of my "patchwork" code:
void xMPPClient_OnStateChanged(object sender, EventArgs e)
{
switch (ObjXmppClient.XMPPState)
{
case XMPPState.Binding:
this.Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(2000);
ObjXmppClient.XMPPState = System.Net.XMPP.XMPPState.Bound;
}
);
break;
case XMPPState.Sessioning:
this.Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(2000);
ObjXmppClient.XMPPState = System.Net.XMPP.XMPPState.Session;
}
);
break; ...
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 9 years ago.
Improve this question
This is not actually a question. I'm trying to organize what I've learned about this subject this year. Since I am a beginner in C#, I had lots of difficulties doing this. But thanks to Stack Overflow and a lecture about packets in class, I was able to get enough information to write a program using multiple types of packets, and connections. This script is for beginners who do not know what to do about sockets.
The concept of sending a packet seems to be sending a whole class over the connection. Not writing the data directly to the stream. So, I have to make a DLL(Class Library) file which defines the packet class, and the derived classes of the packet class.
I'll write a simple code for the Packet.dll, which will be having one type of packet, the Login class.
*To make a DLL file, simply make a C# Class Library File on the VS. To compile it, press F7.
"Project Packet, Packet.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Packet
{
public enum PacketType
{
initType = 0, //it's nothing on this code.
login
}
[Serializable]
public class Packet
{
public int Length;
public int Type;
public Packet() {
this.Length = 0;
this.Type = 0;
}
public static byte[] Serialize(Object o) {
MemoryStream ms = new MemoryStream(1024 * 4); //packet size will be maximum of 4KB.
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, o);
return ms.ToArray();
}
public static Object Desirialize(byte[] bt) {
MemoryStream ms = new MemoryStream(1024 * 4);//packet size will be maximum of 4KB.
foreach( byte b in bt){
ms.WriteByte(b);
}
ms.Position = 0;
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(ms);
ms.Close();
return obj;
}
}
}//end of Packet.cs
add a new class for this Project Packet, "Login.cs".
"Project Packet, Login.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Packet
{
[Serializable] //to serialize this class, this statement is essential.
public class Login :Packet //derived class of Packet.cs. Must be public.
{
public string id_str; //id
public string pw_str; //pw
public Login(string id, string pw) { //constructer to make the code more shorter.
this.id_str = id;
this.pw_str = pw;
}
}
}//end of Login.cs
After you are finished with this, press F7 to compile, and you will get Packet.dll on your Debug folder of the Packet project file.
This is all about the Packet class. If you want to add more classes to serialize, just add a new class, and add a enum value on the PacketType.
Next, I'll write a short example source for using the Packet class.
Although it's a simple source using only one connection, and using only one type of packet, it will be written in multiple threads.
The original of this source which I wrote has many types of packets, and is expected to get multiple connections from multiple users, so I made the class "UserSocket" to make an instance of a connected user. And also, it will have the receiving thread function(A thread function for receiving packets from the client.) in another class "MessageThread.cs".
"Project Server, Form1.cs" //a Windows Form Project, which only has a textBox named textBox1.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet; //to add this, right click your project viewer and reference add Packet.dll.
using System.IO;
namespace Server
{
public partial class Form1 : Form
{
private TcpListener server_Listener;
public List<UserSocket> user_list = new List<UserSocket>(); //list to store user instances
private Thread server_Thread; //thread for getting connections.
public void setLog(string msg) //a function to write string on the Form1.textBox1.
{
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText(msg + "\n");
}));
}
private void Form1_Load(object sender, EventArgs e)
{
server_Thread = new Thread(new ThreadStart(RUN)); //starts to wait for connections.
server_Thread.Start();
}
public void RUN() // Thread function to get connection from client.
{
server_Listener = new TcpListener(7778);
server_Listener.Start();
while (true)
{
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText("Waiting for connection\n");
}));
UserSocket user = new UserSocket(); Make an instance of UserSocket
user.UserName = " ";
try
{
user.client = server_Listener.AcceptSocket();
}
catch
{
break;
}
if (user.client.Connected)
{
user.server_isClientOnline = true;
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText("Client Online\n");
}));
user.server_netStream = new NetworkStream(user.client); //connect stream.
user_list.Add(user);
MessageThread mThread = new MessageThread(user, this, user_list); //make an instance of the MessageThread.
user.receiveP = new Thread(new ThreadStart(mThread.RPACKET)); //run the receiving thread for user.
user.receiveP.Start();
}
}
} //end of Form1.cs
"Project Server, UserSocket.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using Packet;
namespace Server
{
public class UserSocket //Just a Class to make an instance of the connected user.
{
public NetworkStream server_netStream;
public bool server_isClientOnline = false;
public byte[] sendBuffer = new byte[1024 * 4];
public byte[] readBuffer = new byte[1024 * 4];
public string UserName = null; //Not in this code, but on the original, used to identify user.
public Login server_LoginClass;
public Socket client = null;
}
}//end of UserSocket.cs
"Project Server, MessageThread.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Packet;
using System.IO;
namespace Server
{
public class MessageThread //A Class for threads for each users.
{
UserSocket uzr;
Form1 f;
List<UserSocket> user_list = new List<UserSocket>();
public MessageThread(UserSocket u, Form1 formget, List<UserSocket> u_l) //Constructer.
{
uzr = u;
f = formget;
this.user_list = u_l;
}
public void RPACKET() //Thread function for receiving packets.
{
f.setLog("rpacket online");
int read = 0;
while (uzr.server_isClientOnline)
{
try
{
read = 0;
read = uzr.server_netStream.Read(uzr.readBuffer, 0, 1024 * 4);
if (read == 0)
{
uzr.server_isClientOnline = false;
break;
}
}
catch
{
uzr.server_isClientOnline = false;
uzr.server_netStream = null;
}
Packet.Packet packet = (Packet.Packet)Packet.Packet.Desirialize(uzr.readBuffer);
//Deserialize the packet to a Packet.cs Type. It's because the packet.Type is in the super class.
switch ((int)packet.Type)
{
case (int)PacketType.login: //If the PacketType is "login"
{
uzr.server_LoginClass = (Login)Packet.Packet.Desirialize(uzr.readBuffer);
f.setLog("ID : " + uzr.server_LoginClass.id_str + " PW : " + uzr.server_LoginClass.pw_str);
uzr.UserName=uzr.server_LoginClass.id_str;
}
}
}
}
}
}
This will be all for the Server part. It starts a listening thread on the form_load to get connections, and if it's connected to a client, it will make an instance of UserSocket, and the connection will be made by the UserSocket.client(Socket client). And it will bind the socket with the NetworkStream of the UserSocket, and start a listening thread.
The listening thread will deserialize packets received by the client, and assign the received class to a member class of UserSocket.
Next, it will be the sending part of this script. The client part.(On the original source, it can send, and also receive packets from the server, but on this script, I'll just make it send a packet. To receive a packet, just make a thread, and a thread function simular to the server on the main page.
"Project Client, Form1.cs"
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet;
using System.IO;
namespace Client
{
public partial class Form1 : Form //A Simple Windows Form Project with Two TextBoxes, and a Button
{
private string myid;
private NetworkStream client_Netstream;
private byte[] sendBuffer = new byte[1024 * 4];
private byte[] receiveBuffer = new byte[1024 * 4];
private TcpClient client_tcpClient;
private bool client_isOnline = false;
public Login login;
private void Form1_Load(object sender, EventArgs e)
{
//On Form1 Load, It will connect to the server directly. So, the Server must be active before executing the client.
this.client_tcpClient = new TcpClient();
try
{
this.client_tcpClient.Connect("localhost", 7778);
}
catch
{
MessageBox.Show("Connection Failure\n");
return;
}
this.client_isOnline = true;
this.client_Netstream = this.client_tcpClient.GetStream();
}
private void button1_Click(object sender, EventArgs e)
{
if (!this.client_isOnline)
return;
login = new Login();
login.Type = (int)PacketType.login; //Very essential. must be defined for the server to identify the packet.
login.id_str = this.textBox1.Text;
login.pw_str = this.textBox2.Text;
Packet.Packet.Serialize(login).CopyTo(this.sendBuffer, 0);
this.client_Netstream.Write(this.sendBuffer, 0, this.sendBuffer.Length);
this.client_Netstream.Flush();
for (int i = 0; i < 1024 * 4; i++)
this.sendBuffer[i] = 0;
}
}
}//End of Form1.cs
As I said above, this client won't have a receiving thread. So this is all for the client.
It connects to the server on form load, and if you press the button1, the value of textbox1, and textbox2 will be sent to the server as a serialized packet, PacketType of 'login'. On this example the client just sends two variables, but it can send bigger classes such as classes with Lists.
This is all I can explain about socket programming on C# using Packets. I tried to make it simple, but I couldn't make it shorter. For begginers like me, if you have a question, please leave a comment, and for more skilled experts, if this code needs modification for more efficient coding, please tell me by answering this script.
This is a very long question and I am unclear what the key point is, but:
The concept of sending a packet seems to be sending a whole class over the connection. Not writing the data directly to the stream. So, I have to make a DLL(Class Library) file which defines the packet class, and the derived classes of the packet class.
No. In TCP packets are largely an implementation detail. The socket exposes a stream of data, without any further definitions of logical or physical splits. You can invent your own partitioning / framing any way you like within that, and it doesn't need to map to any "whole class" - it can entirely be "the data" if you like. The point is, however, that "writing data to a stream" is pretty convenient to handle via serializers, and serializers work nicely with a "whole class". However, in most of my socket work the data is more subtle than that and is processed manually and explicitly.
For more general socket guidance, consider http://marcgravell.blogspot.com/2013/02/how-many-ways-can-you-mess-up-io.html
I'm trying to write some code that monitors the TFS workspace(s) on my local workstation but at the moment I'm having problems getting the events to fire.
For example if I map a new folder in my workspace I want to subscribe to the versionControl.UpdatedWorkspace event, and if I do a “get” I want to map to the versionControl.Getting event. The code below is a console application that I think should work, but when I do a get nothing happens. Does anyone know how to successfully subscribe to these events?
VS2010, TFS 2010, WinXP SP3
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace TestEventHanling
{
class Program
{
static void Main(string[] args)
{
Uri serverUri = new Uri(#"http://TfsServer:8080/tfs/collection");
using (TfsTeamProjectCollection collection = new TfsTeamProjectCollection(serverUri, CredentialCache.DefaultCredentials))
{
VersionControlServer versionControl = (VersionControlServer)collection.GetService(typeof(VersionControlServer));
versionControl.UpdatedWorkspace += new WorkspaceEventHandler(OnUpdatedWorkspace);
versionControl.Getting += new GettingEventHandler(OnGetting);
Console.WriteLine("Press \'q\' to quit.");
while (Console.Read() != 'q') ;
}
}
internal static void OnUpdatedWorkspace(object sender, WorkspaceEventArgs e)
{
foreach (WorkingFolder wf in e.Workspace.Folders)
{
Console.WriteLine("Workspace updated {0}", wf.ServerItem);
}
}
internal static void OnGetting(Object sender, GettingEventArgs e)
{
Console.WriteLine("Getting: {0}, status: {1}", e.TargetLocalItem, e.Status);
}
}
}
My understanding are that these are events that are on your local instance of VersionControlServer. That is to say, they will fire when you act on that instance in your code.
For example, if, somewhere else in your code, you updated a workspace, then the UpdatedWorkspace handler would fire.
There's a smaller set of events that you can subscribe to server-side (check-in, builds, etc.), but I'm not sure that you can monitor what's happening on the server through the VersionControlServer class.