I am trying to create a "virtual printer" application in C# that receives print jobs over the network, parses the raw print data for certain information, and then saves the document into a database. A modified version of the following class is working for postscript print jobs (it saves the incoming data to a valid .prn file, just as though the printer was set to print to the "FILE:" port.) When I try to capture .XPS documents from Microsoft XPS Document Writer, though, the documents cannot be opened. Valid XPS files should also be valid ZIP files if the extension is renamed, and this doesn't work either. When I print the same document to the FILE: port and then to my application, and I compare the results in Notepad++, there is a 5-character difference in the length of the data, but it looks identical (it is not plaintext so it's difficult to look at, but the first few characters and last few characters appear to be the same). The file saved the "normal" way works fine, but the file generated by my code does not.
More generally speaking, I'm trying to receive arbitrary data through a TCP port and write it to a file. My solution is "close" but not working. I don't know what kind of encoding XPS uses, but I am using ASCII for postscript and I have tried ASCII and UTF8 for this XPS version.
Any help is greatly appreciated! Here is the relevant part of my code:
class XPSListener
{
private TcpListener tcpListener;
private Thread listenThread;
private string instanceName = "";
private string fileShare = (Settings.Default.SharedPath.Substring(Settings.Default.SharedPath.Length - 1) == #"\") ? Settings.Default.SharedPath : Settings.Default.SharedPath + #"\"; // use SharedPath setting value - append backslash if it isn't already there.
public XPSListener(string initInstanceName, Int32 initPort)
{
this.instanceName = initInstanceName;
this.tcpListener = new TcpListener(IPAddress.Any, initPort);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.Start();
}
private void ListenForClients()
{
try
{
this.tcpListener.Start();
}
catch (Exception e)
{
MessageBox.Show("Socket Error 1 - " + e.StackTrace);
}
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication with connected client
Thread clientThread = new Thread(new ParameterizedThreadStart(AcceptXPSData));
clientThread.Start(client);
}
}
private void AcceptXPSData(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
byte[] message = new byte[65536];
int bytesRead;
string input;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 65536);
Debug.WriteLine("Bytes read: " + bytesRead.ToString());
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
if (instanceName != "DontPrint")
{
Debug.WriteLine(instanceName + " Receiving Data");
//ASCIIEncoding encoder = new ASCIIEncoding();
UTF8Encoding encoder = new UTF8Encoding();
using (FileStream fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
input = encoder.GetString(message, 0, bytesRead);
sw.Write(input);
// first capture this input and write it to an xps file. This file can be converted to PDF at a later time by Ghostscript
// but we will still have access to the temp file for parsing purposes.
}
}
}
}
tcpClient.Close();
// processXPS();
}
You have at least two problems in your code, one of them almost certainly the reason the file you write is incorrect:
You keep reopening the file you're writing to, rather than just opening it once.
You are interpreting the bytes you receive as text and then re-encoding them.
The first issue is more of an efficiency/file-locking issue than a correctness problem. But the second is a big problem.
As you seem to be aware, an XPS file is basically a .zip file. That means that while the underlying data is XML (i.e. UTF8), the file itself is a compressed binary file. You can't interpret that as text in any meaningful way.
You should simply write the bytes you read straight to the file. A better version of your code would look like this:
private void AcceptXPSData(object client)
{
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
using (TcpClient tcpClient = (TcpClient)client)
using (NetworkStream clientStream = tcpClient.GetStream())
using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
{
clientStream.CopyTo(fs);
}
// processXPS();
}
If you actually want to monitor the I/O as it occurs, you can deal with it explicitly, but still much more simply than your code was:
private void AcceptXPSData(object client)
{
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
using (TcpClient tcpClient = (TcpClient)client)
using (NetworkStream clientStream = tcpClient.GetStream())
using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
{
byte[] message = new byte[65536];
int bytesRead;
while ((bytesRead = clientStream.Read(message, 0, message.Length)) > 0)
{
fs.Write(message, 0, bytesRead);
// Add logging or whatever here
}
}
// processXPS();
}
Note that if you want to handle exceptions, you need to handle only those you specifically expect might happen, and for which you have a reasonable way to deal with. Bare catch clauses, or broad catch (Exception) should be avoided in code like this.
Related
I have two different executable files running on same computer which has Windows OS. Both of them are build in Unity. I want to send message from one to other without using network.
How do you send message from an exe program to another exe program in Unity?
Is this possible with integrated Mono/.net functionality or something else?
You can use Named Pipes which uses shared memory to communicate with another application on the-same machine.
Go to File --> Build Settings... select PC, Mac & Linux Standalone then click on Player Settings.... Now, change Api Compatibility Level to .NET 2.0.
Close and re-open Visual Studio. Now, you can import using System.IO.Pipes; and be able to use NamedPipeServerStream and NamedPipeClientStream.
Below is a very simplified server and client. You must do that in a Thread and should also handle exception.
If you don't want to use Thread, there is also asynchronous parameter (PipeOptions.Asynchronous) that makes it a non blocking operator. It gets complicated from there and you have to look for some examples for that on MS doc.
Simple Server:
//Create Server Instance
NamedPipeServerStream server = new NamedPipeServerStream("MyCOMApp", PipeDirection.InOut, 1);
//Wait for a client to connect
server.WaitForConnection();
//Created stream for reading and writing
StreamString serverStream = new StreamString(server);
//Send Message to Client
serverStream.WriteString("Hello From Server");
//Read from Client
string dataFromClient = serverStream.ReadString();
UnityEngine.Debug.Log("Received from Client: " + dataFromClient);
//Close Connection
server.Close();
Simple Client:
//Create Client Instance
NamedPipeClientStream client = new NamedPipeClientStream(".", "MyCOMApp",
PipeDirection.InOut, PipeOptions.None,
TokenImpersonationLevel.Impersonation);
//Connect to server
client.Connect();
//Created stream for reading and writing
StreamString clientStream = new StreamString(client);
//Read from Server
string dataFromServer = clientStream.ReadString();
UnityEngine.Debug.Log("Received from Server: " + dataFromServer);
//Send Message to Server
clientStream.WriteString("Bye from client");
//Close client
client.Close();
The StreamString class from MS Doc:
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
public StreamString(Stream ioStream)
{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}
public string ReadString()
{
int len = 0;
len = ioStream.ReadByte() * 256;
len += ioStream.ReadByte();
byte[] inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);
return streamEncoding.GetString(inBuffer);
}
public int WriteString(string outString)
{
byte[] outBuffer = streamEncoding.GetBytes(outString);
int len = outBuffer.Length;
if (len > UInt16.MaxValue)
{
len = (int)UInt16.MaxValue;
}
ioStream.WriteByte((byte)(len / 256));
ioStream.WriteByte((byte)(len & 255));
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();
return outBuffer.Length + 2;
}
}
You could have a file they both write and read to.
You can put a timestamp with it to show when the last message was written
In my server program I am supposed to take a file from client but this can be any size so, how can I figure out its size so I can set buffer size for it. I tried this code but at the end I just get 1kb folder which is not working anymore.
private void checkRequest()
{ // Checks if request is a download or upload request and calls function that fits.
...
...
...
else if (Request.Contains("Upload")) //If request is upload (Client wants to upload)
{
info = Request;
nickName = Request.Substring(0, Request.IndexOf("Upload")); //Takes nickname
info = info.Replace(nickName, ""); //Takes nickName of the client and deletes
info = info.Replace("Upload", ""); //Deletes request.
if (!sList.Contains(nickName)) //If nick name is unique
{
info = info.Substring(0, info.IndexOf("end"));
sList.Add(nickName); //Adds nick name into a list.
Receive(info);
}
}
else
{
serverSocket.Close(); // If any problem occurs server becomes offline.
}
}
private void Receive(string receivedFileName)
{
byte[] buffer = new byte[1024]; //This is the part I can't fit anything.
activity.AppendText("File downloading to " + fileDir + " destination");
while (tempSocket.Receive(buffer) != 0)
{
File.WriteAllBytes(fileDir + "//" + fileName, buffer); //Creates a new file or overwrites it.
}
activity.AppendText("File downloaded..."); // Updates activity log(text box.)
}
Before you do the File.WriteAllBytes() function call, write a Int64 out to the socket with the file length.
Then have your client look for that length first, and set the buffer appropriately.
Remark
If you want the TCP Stream to only contain data from the file, you could have a protocol that included multiple sockets:
Control Socket - This socket waits for connections to tell it that a file needs to be uploaded. Once a client connects, the client will pass it information, such as file size. The server will then respond with a port for a new socket. (Data Socket)
Data Socket - When the client connects to this socket, it will immediately send the entire file. Once the server receives the agreed upon number of bytes, it will close the socket.
Since you leave the socket open, you'll indeed have to send the size of the file first as Andrew suggested.
But even then, don't read everything into an array in memory, but consider using a FileStream and write the data directly to disk in smaller chunks.
Something like:
private void Receive(string receivedFileName)
{
byte[] buffer = new byte[1024];
// receive file size
if (tempSocket.Receive(buffer, sizeof(ulong), SocketFlags.None) != sizeof(ulong))
{
// failed to receive the size
return;
}
ulong fileSize = BitConverter.ToUInt64(buffer, 0);
// receive file data
activity.AppendText("File downloading to " + fileDir + " destination");
using (FileStream stream = new FileStream(fileDir + "//" + fileName, FileMode.Create, FileAccess.Write)
{
ulong totalBytesReceived = 0;
while (totalBytesReceived < fileSize)
{
int bytesReceived = tempSocket.Receive(buffer);
if (bytesReceived > 0)
{
stream.Write(buffer, 0, bytesReceived);
totalBytesReceived += (ulong)bytesReceived;
}
else
{
Thread.Sleep(1);
}
}
}
activity.AppendText("File downloaded..."); // Updates activity log(text box.)
}
I have a system where in one end I have a device communicating with a module via RS-232.
The module is connected to a PC via TCP, and translates TCP messages to RS-232 and vice versa.
This is how I do it:
I read out every byte on the stream
build a string
compare the latter part of the string to a delimiter
then I stop reading the stream (and fire an event, though not shown here).
My current code for handling this is
string delimiter = "\r\n";
byte[] reply = new byte[1];
string replyString = string.Empty;
bool readOk = true;
int dl = delimiter.Length;
bool delimiterReached = false;
do
{
try
{
stream.Read(reply, 0, 1);
}
catch (Exception e)
{
readOk = false;
break;
}
replyString += Encoding.ASCII.GetString(reply, 0, reply.Length);
int rl = replyString.Length;
if (rl > dl)
{
string endString = replyString.Substring(rl-dl, dl);
if (endString.Equals(delimiter))
delimiterReached = true;
}
} while (!delimiterReached);
where stream is the TcpClient.GetStream()
I don't much care for the constant string building, so I was wondering if there is a better way of doing this?
Once the TCP connection is established, wrap the TcpClient stream in a StreamReader:
var reader = new StreamReader( stream, Encoding.ASCII );
Since your delimiter is \r\n, StreamReader.ReadLine will read a complete message:
var reply = reader.ReadLine();
Be careful to create the StreamReader once per TCP connection; it may read and buffer additional data from the underlying TCP stream for efficiency.
I am having issues with FileStreams. I'm in the process of writing a C# serial interface for FPGA project I'm working on which receives a packet (containing 16 bytes) creates and writes the bytes to a file and subsequently appends to the created file.
The program is not throwing any errors but doesn't appear to get past creating the file and does not write any data to it.
Any Ideas? IS there a better way to OpenOrAppend a file?
Thanks in Advance,
Michael
private void SendReceivedDataToFile(int sendBytes)
{
if (saveFileCreated == false)
{
FileStream writeFileStream = new FileStream(tbSaveDirectory.Text, FileMode.Create);
writeFileStream.Write(oldData, 0, sendBytes);
writeFileStream.Flush();
writeFileStream.Close();
saveFileCreated = true;
readByteCount = readByteCount + sendBytes;
}
else
{
using (var writeFilestream2 = new FileStream(tbSaveDirectory.Text, FileMode.Append))
{
writeFilestream2.Write(oldData, 0, sendBytes);
writeFilestream2.Flush();
writeFilestream2.Close();
readByteCount = readByteCount + sendBytes;
}
}
if (readByteCount == readFileSize) // all data has been recieved so close file.
{
saveFileCreated = false;
}
}
FileMode.Append already means "create or append", so really you only need the else {} part of your if. You also don't need to call Flush() or Close() - disposing the stream will do that for you.
Not sure about not writing data... did you try to trace your code?
So first I would reduce your code to
private void SendReceivedDataToFile(int sendBytes)
{
using (var fs = new FileStream(tbSaveDirectory.Text, FileMode.Append))
fs.Write(oldData, 0, sendBytes);
readByteCount += sendBytes;
}
then try to figure what exactly in the oldData.
I am wanting to write a WCF web service that can send files over the wire to the client. So I have one setup that sends a Stream response. Here is my code on the client:
private void button1_Click(object sender, EventArgs e)
{
string filename = System.Environment.CurrentDirectory + "\\Picture.jpg";
if (File.Exists(filename))
File.Delete(filename);
StreamServiceClient client = new StreamServiceClient();
int length = 256;
byte[] buffer = new byte[length];
FileStream sink = new FileStream(filename, FileMode.CreateNew, FileAccess.Write);
Stream source = client.GetData();
int bytesRead;
while ((bytesRead = source.Read(buffer,0,length))> 0)
{
sink.Write(buffer,0,length);
}
source.Close();
sink.Close();
MessageBox.Show("All done");
}
Everything processes fine with no errors or exceptions. The problem is that the .jpg file that is getting transferred is reported as being "corrupted or too large" when I open it.
What am I doing wrong?
On the server side, here is the method that is sending the file.
public Stream GetData()
{
string filename = Environment.CurrentDirectory+"\\Chrysanthemum.jpg";
FileStream myfile = File.OpenRead(filename);
return myfile;
}
I have the server configured with basicHttp binding with Transfermode.StreamedResponse.
I think the problem is this:
while ((bytesRead = source.Read(buffer,0,length))> 0)
{
sink.Write(buffer,0,length);
}
Imagine you're reading the last bit of your file - maybe it's not 256 bytes anymore, but only 10.
You read those last 10 bytes, bytesRead will be 10, but on the sink.Write operation, you're still using the fixed value length (256). So when you read the last block of data, you're writing out a block that might be too large.
You need to change the line for the sink to:
sink.Write(buffer, 0, bytesRead);
and then it should work.