how are you? I hope that you are good, I have a question, how can I do to the server consume multi clients? I put all my code
Server
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
namespace Consola
{
internal class Program
{
static void Main(string[] args)
{
string ip = "127.0.0.1";
int port = 8080;
var server = new TcpListener(IPAddress.Parse(ip), port);
server.Start();
Console.WriteLine("Server has started on {0}:{1}, Waiting for a connection…", ip, port);
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("A client connected.");
NetworkStream stream = client.GetStream();
// enter to an infinite cycle to be able to handle every change in stream
while (true)
{
while (!stream.DataAvailable) ;
while (client.Available < 3) ; // match against "get"
byte[] bytes = new byte[client.Available];
stream.Read(bytes, 0, client.Available);
string s = Encoding.UTF8.GetString(bytes);
if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase))
{
Console.WriteLine("=====Handshaking from client=====\n{0}", s);
// 1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
// 2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
// 3. Compute SHA-1 and Base64 hash of the new value
// 4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response
string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);
// HTTP/1.1 defines the sequence CR LF as the end-of-line marker
byte[] response = Encoding.UTF8.GetBytes(
"HTTP/1.1 101 Switching Protocols\r\n" +
"Connection: Upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");
stream.Write(response, 0, response.Length);
}
else
{
bool fin = (bytes[0] & 0b10000000) != 0,
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
offset = 2;
ulong msglen = (ulong)(bytes[1] & 0b01111111);
if (msglen == 126)
{
// bytes are reversed because websocket will print them in Big-Endian, whereas
// BitConverter will want them arranged in little-endian on windows
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
offset = 4;
}
else if (msglen == 127)
{
// To test the below code, we need to manually buffer larger messages — since the NIC's autobuffering
// may be too latency-friendly for this code to run (that is, we may have only some of the bytes in this
// websocket frame available through client.Available).
msglen = BitConverter.ToUInt64(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2] }, 0);
offset = 10;
}
if (msglen == 0)
{
Console.WriteLine("msglen == 0");
}
else if (mask)
{
byte[] decoded = new byte[msglen];
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
offset += 4;
for (ulong i = 0; i < msglen; ++i)
decoded[i] = (byte)(bytes[(ulong)offset + i] ^ masks[i % 4]);
string text = Encoding.UTF8.GetString(decoded);
Console.WriteLine("{0}", text);
}
else
Console.WriteLine("mask bit not set");
Console.WriteLine();
}
}
}
}
}
Client
<!DOCTYPE html>
<html lang="en">
<head>
<link href="Content/bootstrap.min.css" type="text/css" rel="stylesheet" />
<script src="Scripts/jquery-3.6.1.min.js" type="text/javascript"></script>
</head>
<body>
<div class="card">
<div class="card-header">
<h2>Prueba simple de WebSocket</h2>
</div>
<div class="card-body">
<p>
<textarea cols="60" rows="6" id="cajadetexto"></textarea>
</p>
<p>
<button id="boton" class="btn btn-primary">Enviar</button>
</p>
<p>
<div id="salida"></div>
</p>
</div>
</div>
<style type="text/css">
textarea {
vertical-align: bottom;
}
#salida {
overflow: auto;
}
#salida > p {
overflow-wrap: break-word;
}
#salida span {
color: blue;
}
#salida span.error {
color: red;
}
</style>
<script type="text/javascript">
$(document).ready(function () {
const wsUri = "ws://127.0.0.1:8080/";
const websocket = new WebSocket(wsUri);
$(document).on("click", "#boton", onClickButton);
websocket.onopen = (e) => {
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
};
websocket.onclose = (e) => {
writeToScreen("DISCONNECTED");
};
websocket.onmessage = (e) => {
writeToScreen(`<span>RESPONSE: ${e.data}</span>`);
};
websocket.onerror = (e) => {
writeToScreen(`<span class="error">ERROR:</span> ${e.data}`);
};
function doSend(message) {
writeToScreen(`SENT: ${message}`);
websocket.send(message);
}
function writeToScreen(message) {
$("#salida").append("<p>" + message + "</p>");
}
function onClickButton() {
var text = $("#cajadetexto").val();
text && doSend(text);
$("#cajadetexto").val("");
$("#cajadetexto").focus();
}
});
</script>
</body>
</html>
I based my code on this link:
https://github.com/mdn/content/blob/main/files/en-us/web/api/websockets_api/writing_websocket_server/index.md?plain=1
Thanks and I wait your answers.
UPGRADE 23/12/2022 12:05
Hi, finally i could implement the threads to the multi clients in the server, this is the code:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace Consola
{
public class Program
{
private static TcpListener tcpListener = new TcpListener(
IPAddress.Parse("127.0.0.1"),
8080
);
public static void Main(string[] args)
{
tcpListener.Start();
while(true)
{
Thread nuevoHilo = new Thread(new ThreadStart(Listeners));
nuevoHilo.Start();
}
}
private static void Listeners()
{
Socket client = tcpListener.AcceptSocket();
if (client.Connected)
{
Console.WriteLine("Client:" + client.RemoteEndPoint + " now connected to server.");
NetworkStream stream = new NetworkStream(client);
while (true)
{
while (!stream.DataAvailable) ;
while (client.Available < 3) ; // match against "get"
byte[] bytes = new byte[client.Available];
stream.Read(bytes, 0, client.Available);
string s = Encoding.UTF8.GetString(bytes);
if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase))
{
Console.WriteLine("=====Handshaking from client=====\n{0}", s);
// 1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
// 2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
// 3. Compute SHA-1 and Base64 hash of the new value
// 4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response
string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);
// HTTP/1.1 defines the sequence CR LF as the end-of-line marker
byte[] response = Encoding.UTF8.GetBytes(
"HTTP/1.1 101 Switching Protocols\r\n" +
"Connection: Upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");
stream.Write(response, 0, response.Length);
}
else
{
bool fin = (bytes
[o]& 0b10000000) != 0,
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
int opcode = bytes
[o]& 0b00001111, // expecting 1 - text message
offset = 2;
ulong msglen = (ulong)(bytes[1] & 0b01111111);
if (msglen == 126)
{
// bytes are reversed because websocket will print them in Big-Endian, whereas
// BitConverter will want them arranged in little-endian on windows
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
offset = 4;
}
else if (msglen == 127)
{
// To test the below code, we need to manually buffer larger messages — since the NIC's autobuffering
// may be too latency-friendly for this code to run (that is, we may have only some of the bytes in this
// websocket frame available through client.Available).
msglen = BitConverter.ToUInt64(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2] }, 0);
offset = 10;
}
if (msglen == 0)
{
Console.WriteLine("msglen == 0");
}
else if (mask)
{
byte[] decoded = new byte[msglen];
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
offset += 4;
for (ulong i = 0; i < msglen; ++i)
decoded[i] = (byte)(bytes[(ulong)offset + i] ^ masks[i % 4]);
string text = Encoding.UTF8.GetString(decoded);
Console.WriteLine("{0}", text);
}
else
Console.WriteLine("mask bit not set");
Console.WriteLine();
}
}
}
client.Close();
}
}
}
Now I need that the client view can notify the other clients messages and prevent the OutOfMemoryException when I leave the system very quiet.
This image shows my situation
enter image description here
Related
I found TCPListener code from here.
In MainWindow class Im starting server.
Then opening websocket from html page
let socket = new WebSocket("ws://192.168.1.149:1112");
Right after this CPU loads to 25% even if nothing sent to server. Further data sending to server going normal. What to do to prevent CPU load? Please help.
class TcpServer
{
public string ip;
public int port;
private Thread bgThread;
public void StartListen()
{
bgThread = new Thread(new ThreadStart(Start))
{
IsBackground = true
};
bgThread.Start();
}
public void Start()
{
TcpListener server = new TcpListener(IPAddress.Parse(ip), port);
server.Start();
TcpClient client = server.AcceptTcpClient();
NetworkStream stream = client.GetStream();
while (true)
{
while (!stream.DataAvailable) ;
while (client.Available < 3) ; // match against "get"
byte[] bytes = new byte[client.Available];
stream.Read(bytes, 0, client.Available);
string strbytes = Encoding.UTF8.GetString(bytes);
if(strbytes.StartsWith("GET"))
{
Console.WriteLine("=====Handshaking from client=====\n{0}", strbytes);
string swk = Regex.Match(strbytes, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);
// HTTP/1.1 defines the sequence CR LF as the end-of-line marker
byte[] response = Encoding.UTF8.GetBytes(
"HTTP/1.1 101 Switching Protocols\r\n" +
"Connection: Upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");
stream.Write(response, 0, response.Length);
}
else
{
bool mask = (bytes[1] & 0b10000000) != 0;
int msglen = bytes[1] - 128,
offset = 2;
if (msglen == 126)
{
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
offset = 4;
}
else if (msglen == 127)
{
Console.WriteLine("TODO: msglen == 127, needs qword to store msglen");
}
if (msglen == 0)
Console.WriteLine("msglen == 0");
else if (mask)
{
try
{
byte[] decoded = new byte[msglen];
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
offset += 4;
for (int i = 0; i < msglen; ++i)
decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);
string text = Encoding.UTF8.GetString(decoded);
// other code
}
catch(Exception exc)
{
Console.WriteLine(exc.Message + "\n--------\n" + exc.StackTrace);
}
}
else
Console.WriteLine("mask bit not set");
}
}
}
}
private void startServer()
{
tcpserver = new TcpServer
{
ip = ipbox.Text,
port = 1112
};
tcpserver.StartListen();
}
startServer();
P.S: I have not to say anymore but SO says "It looks like your post is mostly code; please add some more details.". So: some words
Faced a problem. There is a 0x03 function, but I need to redo it in 0x06, I don’t understand how to do it.
I know that function 06 does not have a variable part. The value of one register is always transferred to it. But I can not understand what needs to be corrected. Please, help.
Here I create a package for functions:
private void BuildMessage(byte address, byte type, ushort start, ushort registers, ref byte[] message)
{
if (type == 3 || type == 16)
{
//Array to receive CRC bytes:
byte[] CRC = new byte[2];
message[0] = address;
message[1] = type;
message[2] = (byte)(start >> 8);
message[3] = (byte)start;
message[4] = (byte)(registers >> 8);
message[5] = (byte)registers;
GetCRC(message, ref CRC);
message[message.Length - 2] = CRC[0];
message[message.Length - 1] = CRC[1];
}
else if (type == 6)
{
byte[] CRC = new byte[2];
message[0] = address;
message[1] = type;
message[2] = (byte)(start >> 8);
message[3] = (byte)start;
message[4] = (byte)(registers >> 8);
message[5] = (byte)registers;
GetCRC(message, ref CRC);
message[6] = CRC[0];
message[7] = CRC[1];
}
}
This is my function number 3:
public bool SendFunc(int funcNumer, string connectType, byte address, ushort start, ushort registers, ref short[] values)
{
#region №3
if (funcNumer == 3)
{
#region serial-port
if (connectType.Equals("COM"))
{
//Ensure port is open:
if (sp.IsOpen)
{
//Clear in/out buffers:
sp.DiscardOutBuffer();
sp.DiscardInBuffer();
//Function 3 request is always 8 bytes:
byte[] message = new byte[8];
//Function 3 response buffer:
byte[] response = new byte[5 + 2 * registers];
//Build outgoing modbus message:
BuildMessage(address, (byte)3, start, registers, ref message);
//Send modbus message to Serial Port:
try
{
sp.Write(message, 0, message.Length);
GetResponse("COM", ref response);
}
catch (Exception err)
{
modbusStatus = "" + err.Message;
return false;
}
//Evaluate message:
if (CheckResponse(response))
{
//Return requested register values:
for (int i = 0; i < (response.Length - 5) / 2; i++)
{
values[i] = response[2 * i + 3];
values[i] <<= 8;
values[i] += response[2 * i + 4];
}
modbusStatus = "";
return true;
}
else
{
modbusStatus = "";
return false;
}
}
else
{
modbusStatus = "";
return false;
}
}
And here I am trying to implement function number 6:
if (funcNumer == 6)
{
#region serial-port
if (connectType.Equals("COM"))
{
//Ensure port is open:
if (sp.IsOpen)
{
//Clear in/out buffers:
sp.DiscardOutBuffer();
sp.DiscardInBuffer();
//Function 3 request is always 8 bytes:
byte[] message = new byte[8];
//Function 3 response buffer:
byte[] response = new byte[5 + 2 * registers];
//Build outgoing modbus message:
BuildMessage(address, (byte)6, start, registers, ref message);
//Send modbus message to Serial Port:
try
{
sp.Write(message, 0, message.Length);
GetResponse("COM", ref response);
}
catch (Exception err)
{
modbusStatus = "" + err.Message;
return false;
}
//Evaluate message:
if (CheckResponse(response))
{
//Return requested register values:
for (int i = 0; i < (response.Length - 5) / 2; i++)
{
values[i] = response[2 * i + 3];
values[i] <<= 8;
values[i] += response[2 * i + 4];
}
modbusStatus = "";
return true;
}
else
{
modbusStatus = "";
return false;
}
}
else
{
modbusStatus = "";
return false;
}
}
This is my function to check the response:
private bool CheckResponse(byte[] response)
{
//Perform a basic CRC check:
byte[] CRC = new byte[2];
GetCRC(response, ref CRC);
if (CRC[0] == response[response.Length - 2] && CRC[1] == response[response.Length - 1])
return true;
else
return false;
}
This is my function for get response:
private void GetResponse(string connect, ref byte[] response)
{
if (connect.Equals("COM"))
{
for (int i = 0; i < response.Length; i++)
{
response[i] = (byte)(sp.ReadByte());
}
}
else if (connect.Equals("TCP"))
{
NetworkStream stream = tcpClient.GetStream();
for (int i = 0; i < response.Length; i++)
{
response[i] = (byte)(stream.ReadByte());
}
}
else
{
}
}
The difference between Modbus function 0x03 and 0x06 is actually the part of the code you did not show on your question: CheckResponse(response).
Function 0x03 reads a number of registers, and the values in those registers (coming from the slave) are included in the response.
Function 0x06 writes a single register and gives an echo back. So the response is the same as the query.
With that information, it should be easy to modify your code: remove the for loop where you retrieve the register values.
Other than that you might need to modify your CheckResponse() function too, but that should also be quite straight forward: just check the response is exactly the same as the query (message).
EDIT: if your CheckResponse()` function only checks for the correct CRC on the responses I suppose you can keep it as it is.
I try to create a simple calculator with client-server diagram in c# using System.Net.Socket. Everything working, but in server side, when I try to convert values receive from client, it's always convert the values to decimal, not integer but I try many time, still not solve.
Example, when client input a=5 and b=5 value, in a server side it's turn to 53 and 53.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace Server_Fix
{
class Program
{
private static int SendVarData(Socket s, byte[] data)
{
int total = 0;
int size = data.Length;
int dataleft = size;
int sent;
byte[] datasize = new byte[4];
datasize = BitConverter.GetBytes(size);
sent = s.Send(datasize);
while (total < size)
{
sent = s.Send(data, total, dataleft, SocketFlags.None);
total += sent;
dataleft -= sent;
}
return total;
}
private static byte[] ReceiveVarData(Socket s)
{
int total = 0;
int recv;
byte[] datasize = new byte[4];
recv = s.Receive(datasize, 0, 4, 0);
int size = BitConverter.ToInt32(datasize,0);
int dataleft = size;
byte[] data = new byte[size];
while (total < size)
{
recv = s.Receive(data, total, dataleft, 0);
if (recv == 0)
{
data = Encoding.ASCII.GetBytes("exit ");
break;
}
total += recv;
dataleft -= recv;
}
return data;
}
public static void Main()
{
byte[] data = new byte[1024];
byte[] data1 = new byte[1024];
byte[] data2 = new byte[1024];
byte[] data3 = new byte[1024];
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);
Socket newsock = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
newsock.Bind(ipep);
newsock.Listen(10);
Console.WriteLine("Waiting for a client...");
Socket client = newsock.Accept();
IPEndPoint newclient = (IPEndPoint)client.RemoteEndPoint;
Console.WriteLine("Connected with {0} at port {1}",
newclient.Address, newclient.Port);
string welcome = "CALCULATOR CLIENT-SERVER DIAGRAM!";
data = Encoding.ASCII.GetBytes(welcome);
int sent = SendVarData(client, data);
string phepToan;
int result=0;
int a = 0, b = 0;
while(true)
{
sent = SendVarData(client, Encoding.ASCII.GetBytes("Nhap vao so a: "));
data1 = ReceiveVarData(client);
//Console.WriteLine("Client: " + Encoding.ASCII.GetString(data));
sent = SendVarData(client, Encoding.ASCII.GetBytes("Nhap vao so b: "));
data2 = ReceiveVarData(client);
//b = Convert.ToInt32(data2);
sent = SendVarData(client, Encoding.ASCII.GetBytes("Cho biet phep tinh can dung la | + | - | * | / |: "));
data3 = ReceiveVarData(client);
phepToan = Encoding.ASCII.GetString(data3);
//a = Convert.ToString(Encoding.ASCII.GetString(data1));
if (phepToan=="+")
{
foreach (byte byteValue in data1)
{
a = Convert.ToChar(byteValue); //It's always turn to Decimal values
}
foreach (byte byteValue in data2)
{
b = Convert.ToChar(byteValue); //It's always turn to Decimal values
}
result = a + b;
sent = SendVarData(client, Encoding.ASCII.GetBytes("Ket qua phep tinh: "+Convert.ToString(result)));
}
if (phepToan == "-")
{
}
if (phepToan == "*")
{
}
if (phepToan == "/")
{
}
}
Console.WriteLine("Disconnected from {0}", newclient.Address);
client.Close();
newsock.Close();
Console.ReadLine();
}
}
}
================================================================
Code of client side
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace Client_Fix
{
class Program
{
private static int SendVarData(Socket s, byte[] data)
{
int total = 0;
int size = data.Length;
int dataleft = size;
int sent;
byte[] datasize = new byte[4];
datasize = BitConverter.GetBytes(size);
sent = s.Send(datasize);
while (total < size)
{
sent = s.Send(data, total, dataleft, SocketFlags.None);
total += sent;
dataleft -= sent;
}
return total;
}
private static byte[] ReceiveVarData(Socket s)
{
int total = 0;
int recv;
byte[] datasize = new byte[4];
recv = s.Receive(datasize, 0, 4, 0);
int size = BitConverter.ToInt32(datasize,0);
int dataleft = size;
byte[] data = new byte[size];
while (total < size)
{
recv = s.Receive(data, total, dataleft, 0);
if (recv == 0)
{
data = Encoding.ASCII.GetBytes("exit ");
break;
}
total += recv;
dataleft -= recv;
}
return data;
}
public static void Main()
{
byte[] data = new byte[1024];
int sent;
IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);
Socket server = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
try
{
server.Connect(ipep);
}
catch (SocketException e)
{
Console.WriteLine("Unable to connect to server.");
Console.WriteLine(e.ToString());
return;
}
string input;
data = ReceiveVarData(server);
string stringData = Encoding.ASCII.GetString(data);
Console.WriteLine(stringData);
while (true)
{
data = ReceiveVarData(server);
Console.Write("Server: " + Encoding.ASCII.GetString(data));
Console.WriteLine("You: " + input);
sent = SendVarData(server, Encoding.ASCII.GetBytes(input));
data = ReceiveVarData(server);
Console.Write("Server: " + Encoding.ASCII.GetString(data));
input = Console.ReadLine();
//Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine("You: " + input);
sent = SendVarData(server, Encoding.ASCII.GetBytes(input));
data = ReceiveVarData(server);
Console.Write("Server: " + Encoding.ASCII.GetString(data));
input = Console.ReadLine();
//Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine("You: " + input);
sent = SendVarData(server, Encoding.ASCII.GetBytes(input));
data = ReceiveVarData(server);
Console.WriteLine("Server: " + Encoding.ASCII.GetString(data));
}
Console.WriteLine("Disconnecting from server...");
server.Shutdown(SocketShutdown.Both);
server.Close();
Console.ReadLine();
}
}
}
Let's assume byteValue is 53, the codepoint of the character '5'.
Then
Convert.ToChar(byteValue)
will give 53. That's fine, chars in C# have a numeric value, which is their ordinal in the character table.
One way to solve your problem would be this:
var a = int.Parse(ASCIIEncoding.ASCII.GetString(new byte[] { bytevalue }));
Or, again with some speculation, more likely:
var a = int.Parse(ASCIIEncoding.ASCII.GetString(data1));
Here, the numeric representation of the character digit '5' as it comes over the wire (as 53), will be looked up in the ASCII table, giving "5", and then parsed into the desired integer.
But it does not fix the root problems with the whole code, which would require a more thorough planning of how bits of information are to be encoded, transported, and subsequently decoded in a reliable fashion.
I have a working chat -server application in C++ on a raspberry pi that listens for clients and sends messages from one client to the others and vice versa using pthreads in for loops. I am using C# as the clients.
The C# clients are sending and receiving data (single byte) to the RPi server continuously, and logging the times when they send and receive data.
Looking at the logged times, I can see that there is a delay of 100 ms or so between when one client sends and the second one receives the data. This kind of delay is unacceptable for my application. I need to get it under 15 ms consistently.
In my C++ program, the time delay between receiving and sending the byte back to the client is 1-2 ms.
I am not sure if there is a problem in how I have coded the C# clients or the C++ server. I upgraded my kernel with the RT PREEMPT patch but this has not affected the delay times.
If I put in a random delay on the order of seconds before sending a byte to the server in the C# program, then the delay times improve significantly - down to 1-2 ms.
Is there a way to optimize it so even when sending continuously, the delay times are very small? I can post the codes if needed.
**EDIT: Here is the server side code on the RPi.
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string.h> // memset
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <vector>
#include <string>
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <sys/time.h>
#include <sched.h>
#include <time.h>
#include <sys/mman.h>
using namespace std;
int BACKLOG;
#define IP_ADDR "192.168.137.99"
#define PORT "8888"
#define MAXLEN 1
#define MY_PRIORITY (49) /* we use 49 as the PRREMPT_RT use 50
as the priority of kernel tasklets
and interrupt handler by default */
#define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is
guaranteed safe to access without
faulting */
#define NSEC_PER_SEC (1000000000) /* The number of nsecs per sec. */
static unsigned int cli_count = 0;
vector<int> cliarray;
vector<vector<unsigned long long> > data;
pthread_attr_t custom_sched_attr;
int fifo_max_prio, fifo_min_prio;
struct sched_param fifo_param;
void stack_prefault(void) {
unsigned char dummy[MAX_SAFE_STACK];
memset(dummy, 0, MAX_SAFE_STACK);
return;
}
void send_message(char *s, int sock){
int i;
for(i=0;i<BACKLOG;i++){
if(cliarray[i]){
if(cliarray[i] != sock){
send(cliarray[i], s, 1,0);
}
}
}
}
/* Send message to all clients */
void send_message_all(char *s){
int k;
for(k=0;k<BACKLOG;k++){
if(cliarray[k]){
send(cliarray[k], s, 1,0);
}
}
}
void *handle_conn(void *pnewsock)
{
int sock = *(int*)pnewsock;
char client_msg[MAXLEN];
int read_size;
struct timeval tv;
bool looprun = true;
int clientint;
vector<unsigned long long> row(4);
while(looprun ){
read_size = recv(sock, client_msg, 1, 0);
gettimeofday(&tv, NULL);
unsigned long long milliseconds_recv =(unsigned long long)(tv.tv_sec) * 1000 +(unsigned long long)(tv.tv_usec) / 1000;
clientint = int(*client_msg);
client_msg[read_size] = '\0';
/* cout << "length of client message: " << strlen(client_msg) << endl;
cout << "# bytes is : " << read_size << endl;
cout << clientint << " received" << endl;*/
send_message(client_msg,sock);
gettimeofday(&tv, NULL);
unsigned long long milliseconds_sent =(unsigned long long)(tv.tv_sec) * 1000 +(unsigned long long)(tv.tv_usec) / 1000;
row = {clientint, milliseconds_recv, milliseconds_sent, strlen(client_msg)};
data.push_back(row);
if (clientint == 100)
{
looprun = false;
break;
}
}
cout << "exit handle -conn " << endl;
pthread_exit(NULL);
}
int main (int argc, char **argv)
{
struct timespec t;
struct sched_param param;
int interval = 50000; /* 50us*/
/* Declare ourself as a real time task */
param.sched_priority = MY_PRIORITY;
if(sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
exit(-1);
}
/* Lock memory */
if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
perror("mlockall failed");
exit(-2);
}
/* Pre-fault our stack */
stack_prefault();
int connfd =0, n = 0;
int *new_sock, sock;
struct addrinfo hints, *res;
int reuseaddr = 1; // True
// Get the address info
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(IP_ADDR, PORT, &hints, &res) != 0) {
perror("getaddrinfo");
exit (EXIT_FAILURE);
//return 1;
}
// Create the socket
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock == -1) {
perror("socket");
exit (EXIT_FAILURE);
// return 1;
}
// Enable the socket to reuse the address
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1) {
perror("setsockopt");
::close(sock);
exit (EXIT_FAILURE);
//shutdown(sock,2);
// return 1;
}
// Bind to the address
if (bind(sock, res->ai_addr, res->ai_addrlen) == -1) {
perror("bind");
::close(sock);
exit (EXIT_FAILURE);
//shutdown(sock,2);
//return 0;
}
freeaddrinfo(res);
// Listen
if (listen(sock, BACKLOG) == -1) {
perror("listen");
exit (EXIT_FAILURE);
// return 0;
}
cout << "Enter # clients: " ;
cin >> BACKLOG;
cout << "Enter name of text file (num clients - trial #).txt:" << endl;
string filename;
cin >> filename;
cout << "listening for connections" << endl;
// Main loop
// Main loop
bool running = true;
// Initialize clients
while (running)
{
size_t size = sizeof(struct sockaddr_in);
struct sockaddr_in their_addr;
int clilen = sizeof(their_addr);
int newsock = accept(sock, (struct sockaddr*)&their_addr, &size);
if (newsock == -1)
{
perror("accept");
exit (EXIT_FAILURE);
// return -1;
}
cli_count++;
printf("Got a connection from %s on port %d\n", inet_ntoa(their_addr.sin_addr), htons(their_addr.sin_port));
cliarray.push_back(newsock);
if (cli_count == BACKLOG)
{
cout << "Max clients reached" << endl;
running = false;
break;
}
}
ofstream frout("/home/pi/cplus/"+filename,ios::app);
frout << "recv \t" << "time recv (ms) \t" << "time sent (ms) \t" << "length of msg" << endl;
/* Send message to all clients that server is ready to accept data */
char r = char(cli_count);
char *mesg = &r;
send_message_all(mesg);
cout << "length of mesg: " << strlen(mesg) << endl;
//pthread_t from_ard_t, *ptr;
pthread_attr_init(&custom_sched_attr);
pthread_attr_setinheritsched(&custom_sched_attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&custom_sched_attr, SCHED_FIFO);
fifo_max_prio = sched_get_priority_max(SCHED_FIFO);
fifo_param.sched_priority = fifo_max_prio;
pthread_attr_setschedparam(&custom_sched_attr, &fifo_param);
pthread_t *ptr;
ptr =static_cast<pthread_t*>(malloc(sizeof(pthread_t)*cli_count));
int i;
for (i=0;i<BACKLOG;i++)
{
if (pthread_create(&ptr[i], &custom_sched_attr, handle_conn, (void *)&cliarray[i]) != 0)//was newsock
{
fprintf(stderr, "Failed to create thread\n");
exit (EXIT_FAILURE);
}
}
/*if (pthread_create(&from_ard_t, NULL, from_ard, NULL)!=0)
{
fprintf(stderr, "Failed to create thread\n");
}*/
//pthread_join(from_ard_t, NULL);
cout << "Created threads" << endl;
for(i = 0; i < BACKLOG; i++)
{
pthread_join(ptr[i], NULL);
}
cout << "joined send/recv threads" << endl;
close(sock);
/* array for timestamp data */
int numrows = data.size();
for (int k = 0; k < numrows; k++)
{
for (int j = 0; j < 4; j++)
{
frout << data[k][j] << "\t";
}
frout << endl;
}
}
C# client code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.IO;
//make command line possible to save time info in file
namespace sockclient_cs
{
public class Program
{
private System.Object lockThis = new System.Object();
const int MAXLEN = 1;
public bool recvrun = true;
StringBuilder sb = new StringBuilder();
public NetworkStream stream;
string fnrecv;
string fnsend;
public int clicount;
DateTime centuryBegin = new DateTime(1970, 1, 1);
Random rndseed;
public Program(NetworkStream streamer, int clinum, string pathsend, string pathrecv, Random rand)
{
stream = streamer;
clicount = clinum;
fnrecv = pathrecv;
fnsend = pathsend;
rndseed = rand;
}
public void SendData()
{
int[] numarray = new int[] { 70, 80, 90, 100, 60, 50, 40, 30}; // coressponds to %, A, P, _, d
bool looprun = true;
while (looprun)
{
int rnd1 = rndseed.Next(0, numarray.Length);
byte[] writebyte = new byte[] { BitConverter.GetBytes(numarray[rnd1])[0] };
int delay = rndseed.Next(2000,6000);
Thread.Sleep(delay);
Array.Reverse(writebyte);
stream.Write(writebyte, 0, writebyte.Length);
DateTime currentDate = DateTime.Now;
long elapsedTicks = currentDate.Ticks - centuryBegin.Ticks;
Decimal milliseconds = elapsedTicks / (Decimal)TimeSpan.TicksPerMillisecond;
using (StreamWriter sw = File.AppendText(fnsend))
{
sw.WriteLine(numarray[rnd1] + "\t" + milliseconds + "\n");
}
Console.Write("sent: " + numarray[rnd1] + "\n");
if (numarray[rnd1] == 100)
{
looprun = false;
break;
}
}
}
public void ReceiveData()
{
bool recvrun = true;
int numenders = 0;
while (recvrun)
{
String responseData = String.Empty;
byte[] bb = new byte[1]; //1 byte of data coming in
ASCIIEncoding ascii = new ASCIIEncoding();
int bytes;
bytes = stream.Read(bb, 0, bb.Length);
DateTime currentDate = DateTime.Now;
long elapsedTicks = currentDate.Ticks - centuryBegin.Ticks;
Decimal milliseconds = elapsedTicks / (Decimal)TimeSpan.TicksPerMillisecond;
int numback = BitConverter.ToInt16(new byte[] { bb[0], 0x00 }, 0);
using (StreamWriter sw = File.AppendText(fnrecv))
{
sw.WriteLine(numback + "\t" + milliseconds + "\n");
}
//responseData = ascii.GetString(bb, 0, bytes);
Console.WriteLine("Received: " + bb[0] + "\n");
if (numback == 100)
{
numenders++;
if (numenders == clicount-1)
{
recvrun = false;
break;
}
}
}
Console.Write("Exiting receive");
}
}
public class Simple
{
public static void Main()
{
Console.Write("Enter name of recv data file (ex. cli1recv_1.txt):\n");
string recvfile = Console.ReadLine();
string pathrecv = #"C:\Users\Neha\Documents\Ayaz Research\" + recvfile;
Console.Write("Enter name of send data file (ex. cli4send_1.txt):\n");
string sendfile = Console.ReadLine();
string pathsend = #"C:\Users\Neha\Documents\Ayaz Research\" + sendfile;
using (StreamWriter sw = File.CreateText(pathrecv))
{
sw.WriteLine("Received \t Recv time (ms) \n");
}
using (StreamWriter sw = File.CreateText(pathsend))
{
sw.WriteLine("Sent \t Sent time (ms) \n");
}
//SerialPort Serial1 = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
Random seed = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
try
{
TcpClient tcpclnt = new TcpClient();
Console.WriteLine("Connecting...");
tcpclnt.Connect("192.168.137.99", 8888); //address of RPi on arbitrary non privileged port
Console.WriteLine("Connected");
NetworkStream stream = tcpclnt.GetStream();
/*Receive the welcome from server */
String responseData = String.Empty;
Byte[] bb = new byte[2]; //1 byte of data coming in
ASCIIEncoding ascii = new ASCIIEncoding();
int bytes = stream.Read(bb, 0, bb.Length);
int numback = BitConverter.ToInt16(new byte[] { bb[0], 0x00 }, 0);
Console.Write("Received initial message from server: " + bb[0] + "\n");
/*byte[] writebyte = new byte[] { BitConverter.GetBytes(82)[0] };
Console.Write("writebyte length is " + writebyte.Length + "\n");
Array.Reverse(writebyte);
stream.Write(writebyte, 0, writebyte.Length);
bytes = stream.Read(bb, 0,bb.Length);
// convert to string info
Console.Write("reading byte length is " + bb.Length + "\n");
responseData = ascii.GetString(bb, 0, bytes);
Console.WriteLine("bb[0] is: " + bb[0] + "and bb[1] is: " + bb[1] + "\n");
int numback = BitConverter.ToInt16(new byte[] { bb[0], 0x00 }, 0);
Console.WriteLine("Received: " + responseData + "\n");
Console.WriteLine("Received: " + numback + "\n");*/
Program clientObject = new Program(stream,numback,pathsend, pathrecv, seed);
//non loop format - for cppserv
ThreadStart sending = new ThreadStart(clientObject.SendData);
Thread sendThread = new Thread(sending);
sendThread.Start();
ThreadStart receiving = new ThreadStart(clientObject.ReceiveData);
Thread recvThread = new Thread(receiving);
recvThread.Start();
sendThread.Join();
recvThread.Join();
tcpclnt.Close();
}
catch (Exception e)
{
Console.WriteLine("Error...." + e.StackTrace);
}
}
}
}
Here is what Client 2 is sending to Client 1 and the timestamp.
Sent Sent time (ms)
70 1467720189893.1576
80 1467720189912.1587
60 1467720189926.1595
60 1467720189937.1602
50 1467720189949.1608
60 1467720189959.1614
40 1467720189969.162
100 1467720190006.1641
Here is what Client 1 is receiving from Client 2 and the timestamp.
Received Recv time (ms)
70 1467720190016.1647
80 1467720190063.1674
60 1467720190079.1683
60 1467720190109.17
50 1467720190126.171
60 1467720190137.1716
40 1467720190149.1723
100 1467720190161.173
Turn off the Nagle algorithm at the sender
Socket.NoDelay = true
I am trying to retrieve an uploaded file from my webserver. As the client sends its files through a webform (random files), I need to parse the request to get the file out and to process it further on.
Basically, the code goes as:
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
StreamReader r = new StreamReader(request.InputStream, System.Text.Encoding.Default);
// this is the retrieved file from streamreader
string file = null;
while ((line = r.ReadLine()) != null){
// i read the stream till i retrieve the filename
// get the file data out and break the loop
}
// A byststream is created by converting the string,
Byte[] bytes = request.ContentEncoding.GetBytes(file);
MemoryStream mstream = new MemoryStream(bytes);
// do the rest
As a result, i am able to retrieve textfiles, but for all other files, they are corrupted.
Could someone tell me how to parse these HttplistnerRequests properly (or providing a lightweighted alternative)?
I think you are making things harder on yourself than necessary by doing this with an HttpListener rather than using the built in facilities of ASP.Net. But if you must do it this way here is some sample code. Note: 1) I'm assuming you're using enctype="multipart/form-data" on your <form>. 2) This code is designed to be used with a form containing only your <input type="file" /> if you want to post other fields or multiple files you'll have to change the code. 3) This is meant to be a proof of concept/example, it may have bugs, and is not particularly flexible.
static void Main(string[] args)
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8080/ListenerTest/");
listener.Start();
HttpListenerContext context = listener.GetContext();
SaveFile(context.Request.ContentEncoding, GetBoundary(context.Request.ContentType), context.Request.InputStream);
context.Response.StatusCode = 200;
context.Response.ContentType = "text/html";
using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, Encoding.UTF8))
writer.WriteLine("File Uploaded");
context.Response.Close();
listener.Stop();
}
private static String GetBoundary(String ctype)
{
return "--" + ctype.Split(';')[1].Split('=')[1];
}
private static void SaveFile(Encoding enc, String boundary, Stream input)
{
Byte[] boundaryBytes = enc.GetBytes(boundary);
Int32 boundaryLen = boundaryBytes.Length;
using (FileStream output = new FileStream("data", FileMode.Create, FileAccess.Write))
{
Byte[] buffer = new Byte[1024];
Int32 len = input.Read(buffer, 0, 1024);
Int32 startPos = -1;
// Find start boundary
while (true)
{
if (len == 0)
{
throw new Exception("Start Boundaray Not Found");
}
startPos = IndexOf(buffer, len, boundaryBytes);
if (startPos >= 0)
{
break;
}
else
{
Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
len = input.Read(buffer, boundaryLen, 1024 - boundaryLen);
}
}
// Skip four lines (Boundary, Content-Disposition, Content-Type, and a blank)
for (Int32 i = 0; i < 4; i++)
{
while (true)
{
if (len == 0)
{
throw new Exception("Preamble not Found.");
}
startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos);
if (startPos >= 0)
{
startPos++;
break;
}
else
{
len = input.Read(buffer, 0, 1024);
}
}
}
Array.Copy(buffer, startPos, buffer, 0, len - startPos);
len = len - startPos;
while (true)
{
Int32 endPos = IndexOf(buffer, len, boundaryBytes);
if (endPos >= 0)
{
if (endPos > 0) output.Write(buffer, 0, endPos-2);
break;
}
else if (len <= boundaryLen)
{
throw new Exception("End Boundaray Not Found");
}
else
{
output.Write(buffer, 0, len - boundaryLen);
Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen;
}
}
}
}
private static Int32 IndexOf(Byte[] buffer, Int32 len, Byte[] boundaryBytes)
{
for (Int32 i = 0; i <= len - boundaryBytes.Length; i++)
{
Boolean match = true;
for (Int32 j = 0; j < boundaryBytes.Length && match; j++)
{
match = buffer[i + j] == boundaryBytes[j];
}
if (match)
{
return i;
}
}
return -1;
}
To help you better understand what the code above is doing, here is what the body of the HTTP POST looks like:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9lcB0OZVXSqZLbmv
------WebKitFormBoundary9lcB0OZVXSqZLbmv
Content-Disposition: form-data; name="my_file"; filename="Test.txt"
Content-Type: text/plain
Test
------WebKitFormBoundary9lcB0OZVXSqZLbmv--
I've left out the irrelevant headers. As you can see, you need to parse the body by scanning through to find the beginning and ending boundary sequences, and drop the sub headers that come before the content of your file. Unfortunately you cannot use StreamReader because of the potential for binary data. Also unfortunate is the fact that there is no per file Content-Length (the Content-Length header for the request specifies the total length of the body including boundaries, sub-headers, and spacing.
You can't use StreamReader because it is meant to read streams in which the bytes are in the UTF8. Instead, you want to read the contents of the stream to a receive buffer, remove all the stuff you don't need, get the file extension of the uploaded file, extract the contents of the uploaded file, then save the file contents to a new file. The code I show in this post assumes your form looks like this:
<form enctype="multipart/form-data" method="POST" action="/uploader">
<input type="file" name="file">
<input type="submit">
</form>
As you can see, the code is only meant to handle a form that only has the file. Since there is no way to extract the contents of a file on the server from a application/x-www-form-urlencoded form, so you have to include the "multipart/form-data".
First, for this method of handling uploaded files, you will first need this little bit of code:
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Collections.Generic;
Second, you need to read the contents of the request.InputStream to a receive buffer, or a byte[]. We do this by making a byte[] buffer with the length of the Content-Length header sent by the browser. Then, we read the contents of the request.InputStream to the buffer. The code would look like this:
int len = int.Parse(request.Headers["Content-Length"]);
byte[] buffer = new byte[len];
request.InputStream.Read(buffer, 0, len);
The stream will look somewhat like this:
------WebKitFormBoundary9lcB0OZVXSqZLbmv
Content-Disposition: form-data; name="file"; filename="example-file.txt"
Content-Type: text/plain
file contents here
------WebKitFormBoundary9lcB0OZVXSqZLbmv--
Next, you need to get the file extension of the uploaded file. We can do this using this code:
string fileExtension = Encoding.UTF8.GetString(bytes).Split("\r\n")[1].Split("filename=\"")[1].Replace("\"", "").Split('.')[^1];
Then, we need to get the contents of the file. We do this by removing the stuff at the beginning (the -----WebKitFormBoundary, the Content-Disposition, the Content-Type, and a blank line), then removing the last line of the request body, plus an extra \r\n at the end. Here is the code that does just that:
// note that the variable buffer is the byte[], and the variable bytes is the List<byte>
string stringBuffer = Encoding.UTF8.GetString(buffer);
List<byte> bytes = new List<byte>(buffer);
string[] splitString = stringBuffer.Split('\n');
int lengthOfFourLines = splitString[0].Length + splitString[1].Length +
splitString[2].Length + splitString[3].Length + 4;
bytes.RemoveRange(0, lengthOfFourLines);
int lengthOfLastLine = splitString[^2].Length+2;
bytes.RemoveRange(bytes.Count - lengthOfLastLine, lengthOfLastLine);
buffer = bytes.ToArray();
Finally, we need to save the contents to a file. The code below generates a random file name with the user-specified file extension, and saves the files contents to it.
string fname = "";
string[] chars = "q w e r t y u i o p a s d f g h j k l z x c v b n m Q W E R T Y U I O P A S D F G H J K L Z X C V B N M 1 2 3 4 5 6 7 8 9 0".Split(" ");
for (int i = 0; i < 10; i++)
{
fname += chars[new Random().Next(chars.Length)];
}
fname += fileExtension;
FileStream file = File.Create(fname);
file.Write(buffer);
file.Close();
Here is the whole code put together:
public static void Main()
{
var listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8080/");
listener.Start();
while(true)
{
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
if(request.HttpMethod=="POST") SaveFile(request);
response.OutputStream.Write(Encoding.UTF8.GetBytes("file successfully uploaded"));
response.OutputStream.Close();
}
}
void SaveFile(HttpListenerRequest request)
{
int len = (int)request.ContentLength64;
Console.WriteLine(len);
byte[] buffer = new byte[len];
request.InputStream.Read(buffer, 0, len);
string stringBuffer = Encoding.UTF8.GetString(buffer);
Console.WriteLine(stringBuffer.Replace("\r\n","\\r\\n\n"));
string fileExtension = stringBuffer.Split("\r\n")[1]
.Split("filename=\"")[1]
.Replace("\"", "")
.Split('.')[^1]
;
List<byte> bytes = new List<byte>(buffer);
string[] splitString = stringBuffer.Split('\n');
int lengthOfFourLines = splitString[0].Length + splitString[1].Length + splitString[2].Length + splitString[3].Length + 4;
bytes.RemoveRange(0, lengthOfFourLines);
int lengthOfLastLine = splitString[^2].Length+2;
bytes.RemoveRange(bytes.Count - lengthOfLastLine, lengthOfLastLine);
buffer = bytes.ToArray();
string fname = "";
string[] chars = "q w e r t y u i o p a s d f g h j k l z x c v b n m Q W E R T Y U I O P A S D F G H J K L Z X C V B N M 1 2 3 4 5 6 7 8 9 0".Split(" ");
for (int i = 0; i < 10; i++)
{
fname += chars[new Random().Next(chars.Length)];
}
fname += "." + fileExtension;
FileStream file = File.Create(fname);
file.Write(buffer);
file.Close();
}
Also, if you want to send an uploaded file to the client, here is a useful function that sends the file to the client.
// Make sure you are using System.IO, and System.Net when making this function.
// Also make sure you set the content type of the response before calling this function.
// fileName is the name of the file you want to send to the client, and output is the response.OutputStream.
public static void SendFile(string fileName, Stream output)
{
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
fs.CopyTo(output);
fs.Close();
output.Close();
}
The problem is you are reading the file as text.
You need to read the file as a bytearray instead and using the BinaryReader is better and easier to use than StreamReader:
Byte[] bytes;
using (System.IO.BinaryReader r = new System.IO.BinaryReader(request.InputStream))
{
// Read the data from the stream into the byte array
bytes = r.ReadBytes(Convert.ToInt32(request.InputStream.Length));
}
MemoryStream mstream = new MemoryStream(bytes);
May have bugs, test thoroughly. This one gets all post, get, and files.
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
namespace DUSTLauncher
{
class HttpNameValueCollection
{
public class File
{
private string _fileName;
public string FileName { get { return _fileName ?? (_fileName = ""); } set { _fileName = value; } }
private string _fileData;
public string FileData { get { return _fileData ?? (_fileName = ""); } set { _fileData = value; } }
private string _contentType;
public string ContentType { get { return _contentType ?? (_contentType = ""); } set { _contentType = value; } }
}
private NameValueCollection _get;
private Dictionary<string, File> _files;
private readonly HttpListenerContext _ctx;
public NameValueCollection Get { get { return _get ?? (_get = new NameValueCollection()); } set { _get = value; } }
public NameValueCollection Post { get { return _ctx.Request.QueryString; } }
public Dictionary<string, File> Files { get { return _files ?? (_files = new Dictionary<string, File>()); } set { _files = value; } }
private void PopulatePostMultiPart(string post_string)
{
var boundary_index = _ctx.Request.ContentType.IndexOf("boundary=") + 9;
var boundary = _ctx.Request.ContentType.Substring(boundary_index, _ctx.Request.ContentType.Length - boundary_index);
var upper_bound = post_string.Length - 4;
if (post_string.Substring(2, boundary.Length) != boundary)
throw (new InvalidDataException());
var raw_post_strings = new List<string>();
var current_string = new StringBuilder();
for (var x = 4 + boundary.Length; x < upper_bound; ++x)
{
if (post_string.Substring(x, boundary.Length) == boundary)
{
x += boundary.Length + 1;
raw_post_strings.Add(current_string.ToString().Remove(current_string.Length - 3, 3));
current_string.Clear();
continue;
}
current_string.Append(post_string[x]);
var post_variable_string = current_string.ToString();
var end_of_header = post_variable_string.IndexOf("\r\n\r\n");
if (end_of_header == -1) throw (new InvalidDataException());
var filename_index = post_variable_string.IndexOf("filename=\"", 0, end_of_header);
var filename_starts = filename_index + 10;
var content_type_starts = post_variable_string.IndexOf("Content-Type: ", 0, end_of_header) + 14;
var name_starts = post_variable_string.IndexOf("name=\"") + 6;
var data_starts = end_of_header + 4;
if (filename_index == -1) continue;
var filename = post_variable_string.Substring(filename_starts, post_variable_string.IndexOf("\"", filename_starts) - filename_starts);
var content_type = post_variable_string.Substring(content_type_starts, post_variable_string.IndexOf("\r\n", content_type_starts) - content_type_starts);
var file_data = post_variable_string.Substring(data_starts, post_variable_string.Length - data_starts);
var name = post_variable_string.Substring(name_starts, post_variable_string.IndexOf("\"", name_starts) - name_starts);
Files.Add(name, new File() { FileName = filename, ContentType = content_type, FileData = file_data });
continue;
}
}
private void PopulatePost()
{
if (_ctx.Request.HttpMethod != "POST" || _ctx.Request.ContentType == null) return;
var post_string = new StreamReader(_ctx.Request.InputStream, _ctx.Request.ContentEncoding).ReadToEnd();
if (_ctx.Request.ContentType.StartsWith("multipart/form-data"))
PopulatePostMultiPart(post_string);
else
Get = HttpUtility.ParseQueryString(post_string);
}
public HttpNameValueCollection(ref HttpListenerContext ctx)
{
_ctx = ctx;
PopulatePost();
}
}
}
I like #paul-wheeler answer. However I needed to modify their code to include some additional data (In this case, the directory structure).
I'm using this code to upload files:
var myDropzone = $("#fileDropZone");
myDropzone.dropzone(
{
url: "http://" + self.location.hostname + "/Path/Files.html,
method: "post",
createImageThumbnails: true,
previewTemplate: document.querySelector('#previewTemplateId').innerHTML,
clickable: false,
init: function () {
this.on('sending', function(file, xhr, formData){
// xhr is XMLHttpRequest
var name = file.fullPath;
if (typeof (file.fullPath) === "undefined") {
name = file.name;
}
formData.append('fileNameWithPath', name);
});
}
});
Here is #paul-wheeler modified code. Thanks #paul-wheeler.
public class FileManager
{
public static void SaveFile(HttpListenerRequest request, string savePath)
{
var tempFileName = Path.Combine(savePath, $"{DateTime.Now.Ticks}.tmp");
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
var (res, fileName) = SaveTmpFile(request, tempFileName);
if (res)
{
var filePath = Path.Combine(savePath, fileName);
var fileDir = filePath.Substring(0, filePath.LastIndexOf(Path.DirectorySeparatorChar));
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
if (File.Exists(filePath))
{
File.Delete(filePath);
}
File.Move(tempFileName, filePath);
}
}
private static (bool, string) SaveTmpFile(HttpListenerRequest request, string tempFileName)
{
var enc = request.ContentEncoding;
var boundary = GetBoundary(request.ContentType);
var input = request.InputStream;
byte[] boundaryBytes = enc.GetBytes(boundary);
var boundaryLen = boundaryBytes.Length;
using (FileStream output = new FileStream(tempFileName, FileMode.Create, FileAccess.Write))
{
var buffer = new byte[1024];
var len = input.Read(buffer, 0, 1024);
var startPos = -1;
// Get file name and relative path
var strBuffer = Encoding.Default.GetString(buffer);
var strStart = strBuffer.IndexOf("fileNameWithPath") + 21;
if (strStart < 21)
{
Logger.LogError("File name not found");
return (false, null);
}
var strEnd = strBuffer.IndexOf(boundary, strStart) - 2;
var fileName = strBuffer.Substring(strStart, strEnd - strStart);
fileName = fileName.Replace('/', Path.DirectorySeparatorChar);
// Find start boundary
while (true)
{
if (len == 0)
{
Logger.LogError("Find start boundary not found");
return (false, null);
}
startPos = IndexOf(buffer, len, boundaryBytes);
if (startPos >= 0)
{
break;
}
else
{
Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
len = input.Read(buffer, boundaryLen, 1024 - boundaryLen);
}
}
// Advance to data
var foundData = false;
while (!foundData)
{
while (true)
{
if (len == 0)
{
Logger.LogError("Preamble not Found");
return (false, null);
}
startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos);
if (startPos >= 0)
{
startPos++;
break;
}
else
{
// In case read in line is longer than buffer
len = input.Read(buffer, 0, 1024);
}
}
var currStr = Encoding.Default.GetString(buffer).Substring(startPos);
if (currStr.StartsWith("Content-Type:"))
{
// Go past the last carriage-return\line-break. (\r\n)
startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos) + 3;
break;
}
}
Array.Copy(buffer, startPos, buffer, 0, len - startPos);
len = len - startPos;
while (true)
{
var endPos = IndexOf(buffer, len, boundaryBytes);
if (endPos >= 0)
{
if (endPos > 0) output.Write(buffer, 0, endPos - 2);
break;
}
else if (len <= boundaryLen)
{
Logger.LogError("End Boundaray Not Found");
return (false, null);
}
else
{
output.Write(buffer, 0, len - boundaryLen);
Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen;
}
}
return (true, fileName);
}
}
private static int IndexOf(byte[] buffer, int len, byte[] boundaryBytes)
{
for (int i = 0; i <= len - boundaryBytes.Length; i++)
{
var match = true;
for (var j = 0; j < boundaryBytes.Length && match; j++)
{
match = buffer[i + j] == boundaryBytes[j];
}
if (match)
{
return i;
}
}
return -1;
}
private static string GetBoundary(string ctype)
{
return "--" + ctype.Split(';')[1].Split('=')[1];
}
}
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
var contentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
var multipartReader = new MultipartReader(boundary, context.Request.InputStream);
var section = (await multipartReader.ReadNextSectionAsync()).AsFileSection();
var fileName = section.FileName;
var fileStream = section.FileStream;
for a given HttpListenerRequest req and a string path the following code saves a file (worked for text file but also png file)
int len = (int)req.ContentLength64;
byte[] buffer = new byte[len];
int totalRead = 0;
while(totalRead < len){
// InputStream.Read does not read always read full stream (so loop until it has)
totalRead += req.InputStream.Read(buffer, totalRead, len - totalRead);
}
string stringBuffer = Encoding.UTF8.GetString(buffer);
string startTag = stringBuffer.Substring(0, stringBuffer.IndexOf("\r\n\r\n") + 4);
string endTag = stringBuffer.Substring(stringBuffer.IndexOf("\r\n------WebKitFormBoundary"));
List<byte> bytes = new List<byte>(buffer);
bytes = bytes.GetRange(startTag.Length, len - (startTag.Length + endTag.Length));
buffer = bytes.ToArray();
File.WriteAllBytes(path, buffer);
(build on top of copee moo solution)