I'm trying to send a zip file from a server to client using TCP ports.
I'm creating a zip file in a server folder, reading it into a byte array, sending it to a connected client, writing the bytes into a file and storing it in a client folder. Currently the process is just on the same computer.
However, whenever the zip file is saved into the client folder, it's either an invalid zip or a zip with corrupted files. When this happens they'll have the exact same size as the zip file in the server folder, so I don't think I'm losing any bytes in the network transfer. The server zip file also works as expected, you are able to open and view contents in the folder.
The hashes between the client zip and the server zip are different. I've tested with regular .txt, .cs and .csv files, and those are sent and can be opened in the client folder. They have the same has as their server counterpart.
The zip file I'm sending just has a .csv, a .txt, and a .cs file in it.
This is my code
Server:
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO.Compression;
String filename = "C:\\BabyServerZip\\ServerFiles.zip";
File.Delete("C:\\BabyServerZip\\ServerFiles.zip");
ZipFile.CreateFromDirectory("C:\\BabyServerSend", "C:\\BabyServerZip\\ServerFiles.zip");
IPHostEntry ipHostInfo = Dns.GetHostEntry("localhost");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint endpoint = new(ipAddress, 58008);
Socket listener = new(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(endpoint);
listener.Listen();
var handler = await listener.AcceptAsync();
var buffer = new byte[1024];
buffer = File.ReadAllBytes(filename);
handler.Send(buffer);
Client:
using System.Text;
using System.Net;
using System.Net.Sockets;
String outputPath = "C:\\BabyClientReceive\\sent.zip";
IPHostEntry = ipHostInfo = Dns.GetHostEntry("localhost");
IPAddress serverIP = ipHostInfo.AddressList[0];
IPEndPoint clientEnd = new(serverIP, 58008);
Socket clientSocket = new(clientEnd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
clientSocket.ConnectAsync(clientEnd);
while (true)
{
if (clientSocket.Available > 0)
{
using (StreamWriter fw = new StreamWriter(outputPath))
{
char[] response;
do
{
var buffer = new byte[1024];
int received = clientSocket.Receive(buffer, SocketFlags.None);
response = Encoding.ASCII.GetChars(buffer, 0, received);
fw.Write(response, 0, received);
while (response.Length == 1024);
fw.Flush();
fw.Close();
}
}
}
Is there something that I'm missing in order to correctly send zip files over a network? Why are the uncompressed files working correctly and the zip files breaking?
Fixed the issue thanks to the tip from #MySkullCaveIsADarkPlace.
StreamWriter is a text writer, and zip files are binary. Switching the StreamWriter for BinaryWriter fixed the problem.
Your client code is all wrong. You are using a StreamWriter as if the Zip is text, but it's not, it's binary. Replacing it with a BinaryWriter is silly also, because that is primarily for writing custom binary formats bit by bit.
There are numerous other issues with the way you are handling this, as you need to expect that the response will not come as a single blob.
Instead you should not use raw sockets, but use TcpClient. You just need to copy the stream straight into a FileStream.
There are also missing await and you should use IPAddress.Any instead of localhost.
String outputPath = "C:\\BabyClientReceive\\sent.zip";
using TcpClient client = new();
await clientSocket.ConnectAsync(IPAddress.Any, 58008);
using (var ns = client.GetStream())
using (var fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
{
await ns.CopyToAsync(fs);
}
Related
I am trying to establish a TCP Client-Server connection between my locally connected computers (MacBook-BigSur is the running server while a Windows 10 laptop is acting as a client). For this, I'm using Visual Studio Code to run both applications as follows:
On macOS:
Console.WriteLine("Starting build agent...");
var listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 12345);
Console.WriteLine("Build agent Started ");
listener.Start();
while (true)
{
var fileName = $"s-{DateTime.Now.Ticks}.tar.gz";
using (var client = listener.AcceptTcpClient())
using (var stream = client.GetStream())
using (var output = File.Create(fileName))
{
Console.WriteLine("Client connected. Starting to receive the file...");
// read the file in chunks of 1KB
var buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, bytesRead);
}
Console.WriteLine("Client connected. File received successfuly.");
Console.WriteLine(Execute($"tar -xvf '{fileName}'"));
}
}
On Windows:
var client = new TcpClient("192.168.0.109", 12345);
Byte[] data = File.ReadAllBytes(#"D:\shared\file.tar.gz");
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
Console.WriteLine("data sent.");
However, when the Windows client is trying to establish the connection, it fails saying:
System.Net.Internals.SocketExceptionFactory.ExtendedSocketException: 'No connection could be made because the target machine actively refused it. 192.168.0.109:12345'
Note that after running the server I can see Visual Studio is using the port 12345 by checking it using sudo lsof -i :123456 in Terminal. Besides, my Mac device is using the port 192.168.0.109, the firewall is disabled and I can ping it using my Windows command prompt.
127.0.0.1 is localhost which means by definition the server is only reachable on the same host.
If you want the server to listen on all interfaces don't specify IPAddress.Any respectively IPAddress.IPv6Any or use 0.0.0.0.
I got a little problem. In the following code i recieve a file and the ending of the file from Networkstream. After that the File is saved under the path i received. Everytime i run the code i get the path.getinvalidpathchars exception. here is a short code example:
ns.Read(ending,0,1212);
string endung = "saved." + Encoding.UTF8.GetString(ending);
string path = Path.Combine(#"c:\users\user\desktop\" , endung);
Console.WriteLine(path);
File.WriteAllBytes(path ,file);
the byte array file is an array of bytes from the file i received. the full path is c:\users\user\desktop\saved. and the rest (txt, jpg, exe) should be changebel (that means if the received string sas "txt" the path should be c:\users\user\desktop\saved.txt if it sas "exe" c:\users\user\desktop\saved.exe and so on)!
I solved the problem with a StreamReader!
//byte[] file = File.ReadAllBytes(#"c:\users\user\desktop\file.txt");
TcpListener listen = new TcpListener(IPAddress.Parse(mes),223);
listen.Start();
TcpClient client = listen.AcceptTcpClient();
NetworkStream ns = client.GetStream();
StreamReader sre = new StreamReader(ns);
string ende = sre.ReadLine();
string path = Path.Combine(#"c:\users\user\desktop\file2." + ende);
Console.WriteLine(path);
File.WriteAllBytes(path ,file);
listen.Stop();
I have to develop a simple TCP File Transfer Client as part of an exercise/demonstration but I am having trouble with data delivery.
I am already provided with the TCP File server which will receive the incoming files and I have to code a Client that will connect to and send a file to the server. Thus far I have been successful as far as converting the data of the selected file into to a format ready to transferring and successfully opening a connection and then sending of the data, however even encounter a problem on server that is receiving - I am not allowed to change the code of the server and should thus change my client code in such a way that the data sent can be interpreted by the server. Here is the code I use (some is left out which is simple overhead, like input-box to get IP address etc.):
Link for TCP Server VS Solution (if you prefer): https://www.mediafire.com/?682owf9wtdzmxac
TCP File Transfer Client Sending method:
private void TransferFile(string _sFileName, string _sIPAdress)
{
//Convert data for transfer
Stream strmfilestream = File.OpenRead(_sFileName);
Byte[] bFileBuffer = new Byte[strmfilestream.Length];
//Open TCP/IP Connection
TcpClient tcpClientSocket = new TcpClient(_sIPAdress,8080);
NetworkStream nsNetworkStream = tcpClientSocket.GetStream();
nsNetworkStream.Write(bFileBuffer,0,bFileBuffer.GetLength(0));
nsNetworkStream.Close();
}
*Note: _sFileName is just the full file path + File name from a OpenFileDialog.
Here is the Load Method of the server to get things going:
if (!Directory.Exists(#"C:\TCPFileServer"))
Directory.CreateDirectory(#"C:\TCPFileServer");
//Get Ip address of server host machine
IPHostEntry IPHost = Dns.GetHostEntry(Dns.GetHostName());
lblServerIP.Text = IPHost.AddressList[5].ToString();
lstSockets = new ArrayList();
Thread thdListener = new Thread(new ThreadStart(listenerThread));
thdListener.IsBackground = true; //This will enabe the thread to terminate when application is closed
thdListener.Start();
Here is the listener thread method:
public void listenerThread()
{
TcpListener tcpListener = new TcpListener(IPAddress.Any, 8080);
tcpListener.Start();
while (true)
{
Socket handlerSocket = tcpListener.AcceptSocket();
if (handlerSocket.Connected)
{
this.Invoke((Action)(() => lstConnections.Items.Add(handlerSocket.RemoteEndPoint.ToString() + " connected.")));
lock (this)
{
lstSockets.Add(handlerSocket);
}
ThreadStart thdsHandler = new ThreadStart(handlerThread);
Thread thdHandler = new Thread(thdsHandler);
thdHandler.Start();
}
}
}
And then lasty here is the handler thread method:
public void handlerThread()
{
try
{
int iBlockSize = 1024 * 3000; //3mb block size
Byte[] dataByte = new Byte[iBlockSize];
Byte[] rcvdData = new Byte[128000 * 1024];//128mb File Limit
Socket handlerSocket = (Socket)lstSockets[lstSockets.Count - 1];
NetworkStream networkStream = new NetworkStream(handlerSocket);
int i = 0;
int iRcvdBytes = 0;
while (true)
{
//Read from socket and store to buffer 'dataByte'
iRcvdBytes = networkStream.Read(dataByte, 0, iBlockSize);
dataByte.CopyTo(rcvdData, i);//Copy recieved bytes,from buffer, to another byte array
i += iRcvdBytes;
if (iRcvdBytes == 0) break;
}
//Get the File name length, BitConvertor occupies the first 4 bytes
int iFileNameLength = BitConverter.ToInt32(rcvdData, 0);
//Get the file name using length as the size and 4 as the offset
string sFileName = Encoding.ASCII.GetString(rcvdData, 4, iFileNameLength);
Stream fileStream = File.Open("C:\\TCPFileServer\\" + sFileName, FileMode.Create);
//Populate raw File on local machine
fileStream.Write(rcvdData, 4 + iFileNameLength, i - 4 - iFileNameLength);
fileStream.Close();
//Update BRS Net Files Server Log
this.Invoke((Action)(() => lstConnections.Items.Add(sFileName + ": Transfered.")));
//Close Connection
networkStream.Close();
handlerSocket = null; //Clear socket
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Now I have debugged and as far I can determine, the first time I can see a problem is when we are trying to determine the file name length where the code reads int iFileNameLength = BitConverter.ToInt32(rcvdData, 0); - When debugging this variable is always determined as '0', which I assume is not correct? I am not sure. The required results that needs to be achieved is that the Server should receive the file and after the successful transfer, the file name should be displayed in the ListBox.
Here is screen shots showing the problem i'm experiencing:
We can see that bytes are definitely received:
Note that the file name can not be retrieved from using the file name length and offset:
And thus, the following error occurs:
My experience and expertise lie elsewhere and this is the first time I am coding within this paradigm(File transfer over network). I do however have theoretical knowledge about/studied the OSI model and TCP/IP Protocol stack, but never coded anything like this. One note given to me was that the server was coded with the assumption that it will be executed on a specific PC and I am allowed to change code of the Server application if it is absolutely necessary.
Try this. You have to do thinks in reverse on the client and using a List makes things easier.
private void TransferFile(string _sFileName, string _sIPAdress)
{
List<Byte> bFileBuffer = File.ReadAllBytes(_sFileName).ToList();
byte[] bFileName = Encoding.ASCII.GetBytes(_sFileName);
bFileBuffer.InsertRange(0, bFileName);
//Get the File name length, BitConvertor occupies the first 4 bytes
byte[] brcvdDataCount = BitConverter.GetBytes((UInt32)_sFileName.Count());
bFileBuffer.InsertRange(0, brcvdDataCount);
//Open TCP/IP Connection
TcpClient tcpClientSocket = new TcpClient(_sIPAdress, 8080);
NetworkStream nsNetworkStream = tcpClientSocket.GetStream();
nsNetworkStream.Write(bFileBuffer.ToArray(), 0, bFileBuffer.Count);
nsNetworkStream.Close();
}
There are two things with the server code you should be aware of
/Get the File name length, BitConvertor occupies the first 4 bytes
int iFileNameLength = BitConverter.ToInt32(rcvdData, 0);
//Get the file name using length as the size and 4 as the offset
string sFileName = Encoding.ASCII.GetString(rcvdData, 4, iFileNameLength);
1) You need to add a byte count to beginning of upload.
2) The code is using Ascii Encoding which means you can not upload binary data.
I want to send two different lines of XML to a socket as follows:
<GetServerTime UpTimeAtRequest="1919"/>
<Subscribe FrontId="priceFeed" URI="ffo:/price/productCode=VP-4-6-"/>
I'm using Hercules to test this, but it won't let me send it in that format. How should I delimit or format the XML above so I can send it directly to the socket with Hercules after connecting to the appropriate ip address and port?
I would be happy to send this using a WebClient or something in C# also.
Thanks.
I've no idea what Hercules is, but sending arbitrary data over a client is easy:
using (var client = new TcpClient())
{
client.Connect(host, porg);
using (var stream = client.GetStream())
{
// Or some other encoding, of course...
byte[] data = Encoding.UTF8.GetBytes(xmlString);
stream.Write(data, 0, data.Length);
// Whatever else you want to do...
}
}
WebClient is pretty simple to use (assuming your socket is using the HTTP protocol), so using UploadString would look something like this:
Uri uri = new Uri(#"http://www.mywebsite.com/someurl");
string myXml = "<insert valid xml here> /"
using (WebClient wc = new WebClient()
{
wc.UploadString(uri, myXml);
}
I'd only worry that your xml isn't valid as it has two root nodes and no xml header.
With my code I can read a message on the server and write from the client. But I am not being able to write a response from the server and read in the client.
The code on the client
var cli = new TcpClient();
cli.Connect("127.0.0.1", 6800);
string data = String.Empty;
using (var ns = cli.GetStream())
{
using (var sw = new StreamWriter(ns))
{
sw.Write("Hello");
sw.Flush();
//using (var sr = new StreamReader(ns))
//{
// data = sr.ReadToEnd();
//}
}
}
cli.Close();
The code on the server
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
while (run)
{
var client = tcpListener.AcceptTcpClient();
string data = String.Empty;
using (var ns = client.GetStream())
{
using (var sr = new StreamReader(ns))
{
data = sr.ReadToEnd();
//using (var sw = new StreamWriter(ns))
//{
// sw.WriteLine("Hi");
// sw.Flush();
//}
}
}
client.Close();
}
How can I make the server reply after reading the data and make the client read this data?
Since you are using
TcpClient client = tcpListener.AcceptTcpClient();
, you can write back to the client directly without needing it to self-identify. The code you have will actually work if you use Stream.Read() or .ReadLine() instead of .ReadToEnd(). ReadToEnd() will block forever on a network stream, until the stream is closed. See this answer to a similar question, or from MSDN,
ReadToEnd assumes that the stream
knows when it has reached an end. For
interactive protocols in which the
server sends data only when you ask
for it and does not close the
connection, ReadToEnd might block
indefinitely because it does not reach
an end, and should be avoided.
If you use ReadLine() at one side, you will need to use WriteLine() - not Write() - at the other side. The alternative is to use a loop that calls Stream.Read() until there is nothing left to read. You can see a full example of this for the server side in the AcceptTcpClient() documentation on MSDN. The corresponding client example is in the TcpClient documentation.
Cheesy, inneficient, but does the trick on a one-time throwaway program:
Client: In the stream, include the port and IP address it wishes to receive the response from.
Client: Create a listener for that
port and IP.
Server: Read in the port/IP info and
in turn connect, then send the reply
stream.
However, this is a great place to start, look into Sockets class for proper bi-directional communication.