Gå til innhold

C#: Socket: En for sending og en for mottak?


Anbefalte innlegg

I programmet mitt kan klient-siden sende forespørsler til serveren om å laste opp eller ned filer, chat meldinger, ++.

Når klienten kobler til får den en egen tråd på serveren som tar imot dataene den sender.

 

Problemer oppstår hvis klienten vil sende en chat melding mens det pågår en opp- eller nedlasting fra samme klient. Den blir da mixet inn i fildataene på serversiden fordi serveren da forventer data tilhørende filen.

 

Har lagd et system hvor klienten starter en ny socket og sender port til serveren som da kobler til og hele overføring skjer via en ny socket, men dette skaper problemer med brannmurer og port forwardinger etc.

 

Har kommet fram til at det beste kanskje er at det blir det slik at klienten ikke KAN sende noe andre data før filen(e) er ferdig ned- eller opplastet. Har også sett på om WebClient.DownloadFile/UploadFile kan være noe; og det er mulig.

 

Håper det hele er forståelig.

Trenger innspill fra noen mer erfarne utviklere! :)

Lenke til kommentar
Videoannonse
Annonse

Ja, har gjort dette, men syns "merkingen" generer mye data.

Har et system som sender forskjellige objekter(serialiserte) avhengig av forespørselen og det funker forsåvidt greit.

Men hvor mye data er tålelig å sende over nettet bare for å levere en kort chat melding liksom?

 

Hvis jeg skal merke hver eneste pakke med fildata med fil referanse og "offset index", blir det kanskje litt mye overhead? Filene sendes i 4096 byte chunks; men dette er muligens litt for finkornet.

Lenke til kommentar

struct StreamHeader
{
 public int stream_id;
 public int num_packets;
 public int total_size;
 public dictionary<string, string> stream_info;
}

struct StreamPacket
{
 public int stream_id;
 public int packet;
 public int size;
 public byte[] data;
}

 

Da slipper du flere sockets og porter. Bare sorter ut pakkene basert på stream_id.

Det er mulig du får til slik også med litt triksing:

 

public unsafe struct StreamPacket
{
 public int stream_id;
 public int packet;
 public int size; // Always 4096
 public fixed byte[4096] data;
}

 

Det kan kanskje være fornufig å øke fra 4k store pakker (dette er ingenting i dag)

 

edit: Jeg skrev dette i litt sykdom, så jeg tenkte meg for dårlig om. Ikke bruke struct, ikke bruk arrays slik, og ikke bruk fixed. Det er bortkastet alt. Bruk heller class, og List<T> e.l. fordi det fører til mindre minnekopiering.

Endret av GeirGrusom
Lenke til kommentar

Tror ikke jeg skjønte det opplegget du skisserte.

Hva mente du med å sortere på stream_id? Hva slags id skal dette være?

 

edit:

Hvordan ser du for deg at dette skal sendes? Skal det være objekter som serialiseres?

Endret av Techster
Lenke til kommentar

id-en kan være hva som helst, bare den er unik. For eksempel en hash av filnavnet, eller innholdet i hele meldingen. Poenget er bare at den skal identifisere hver "kanal" i alle meldingene som kommer.

Du kan skrive en Socket klasse som brukes for å lese eller skrive til en stream. For eksempel har du en tråd som sitter og leser pakker, og sorterer dem ut til riktig socket, som deretter leser ut kun dataene og som igjen kan leses av en annen del av programmet (meldingsklienten, eller den delen som lagrer filer)

Når du sender og mottar kan det være greit å bare sende dataene manuelt. Men objekter kan godt serialiseres til 4k store pakker for eksempel.

Endret av GeirGrusom
Lenke til kommentar

Hmm. Virker som et smart opplegg, men vet fortsatt ikke om jeg helt skjønner konseptet.

 

Sånn som det virker i dag da har hver klient sin Socket på serveren hvor all kommunikasjon til den klienten går igjennom(begge veier), tenker du at man skal bruke èn Socket for all kommunikasjon til alle klientene?

 

"Poenget er bare at den skal identifisere hver "kanal" i alle meldingene som kommer."

...IDen identifiserer da klienten som sendte meldingen?

Lenke til kommentar

På en måte. Man åpner en stream (og gir den en unik ID) mottaker finner ut at en ny stream som ikke er registrert ble mottatt, leser ut data, og finner ut hva som skal gjøres med alle nye pakker som inneholder den stream ID-en. For eksempel kan den første pakken alltid starte med et integer som forteller hva som skal gjøres med streamen. Dette blir da dirigert videre til en funksjon eller lignende som behandler streamen korrekt, og alle påfølgende pakker blir sendt til enten en Stream klasse (som du har skrevet). Fordelen med å bruke en stream klasse, er at funksjonen i andre enden ikke lenger er klar over at dataene er blitt segmentert, fordi klassen setter dem sammen igjen.

 

Jeg kan prøve å lage et enkelt eksempelprogram på dette.

Lenke til kommentar

Ah ok. Men da poster jeg klientsiden som jeg skrev, så slutter jeg å kaste bort noe mer tid på det :)

Klikk for å se/fjerne innholdet nedenfor

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace StreamTutorial
{
public class StreamInitializerPackage : Package
{
	public long SizeOfStream
	{
		get { return BitConverter.ToInt64(new byte[] { data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15] }, 0); }
	}
	public int NumberOfPackets
	{
		get
		{
			return BitConverter.ToInt32(
				new byte[] { data[16], data[17], data[18], data[19] }
				, 0);
		}
	}
	public int CheckSum
	{
		get
		{
			return BitConverter.ToInt32(
				new byte[] { data[20], data[21], data[22], data[23] }
				, 0);
		}
	}
	public int StreamType
	{
		get
		{
			return BitConverter.ToInt32(
				new byte[] { data[24], data[25], data[26], data[27] }
				, 0);
		}
	}
	internal StreamInitializerPackage(Package original)
		: base(original.StreamID, original.Packet, original.Data)
	{

	}
}
public class Package : ICloneable
{
	protected int stream_id;
	protected int packet;
	protected List<byte> data;
	public int StreamID { get { return stream_id; } }
	public int Packet { get { return packet; } }
	public int Size { get { return data.Count; } }
	public System.Collections.ObjectModel.ReadOnlyCollection<byte> Data { get { return data.AsReadOnly(); } }
	internal static readonly byte[] init_stream = Encoding.ASCII.GetBytes("Init_Stm");

	public Package(Stream src)
	{
		System.IO.BinaryReader rd = new BinaryReader(src);
		stream_id = rd.ReadInt32();
		packet = rd.ReadInt32();
		int size = rd.ReadInt32();
		data = new List<byte>(rd.ReadBytes(size));
	}

	public StreamInitializerPackage GetInitializerPackage()
	{
		// Check if stream-header is well-formed.
		if (BitConverter.ToInt64(data.GetRange(0, 8).ToArray(), 0) == BitConverter.ToInt64(init_stream, 0) && data.Count == 28)
			return new StreamInitializerPackage(this);
		return null;
	}

	public object Clone()
	{
		Package ret = new Package(stream_id, packet, data);
	}

	public Package(int id, int pack, IEnumerable<byte> data)
	{
		stream_id = id;
		packet = pack;
		this.data = new List<byte>(data);
	}
}
public class PacketStream : System.IO.Stream
{
	public void WritePacket(Package pack)
	{
		Write(pack.Data.ToArray(), 0, pack.Size);
	}
}
public delegate Stream NewStream(StreamInitializerPackage init);

public class StreamInfo
{
	private StreamInitializerPackage init;
	private Stream dst;

	internal StreamInfo(StreamInitializerPackage init_pack, Stream stream)
	{
		init = init_pack;
		dst = stream;
	}

	public int StreamID { get { return init.StreamID; } }
	public int StreamType { get { return init.StreamType; } }
	public int StreamPackets { get { return init.NumberOfPackets; } }
	public long StreamSize { get { return init.SizeOfStream; } }
	public int StreamCheckSum { get { return init.CheckSum; } }
	public Stream Stream { get { return dst; } }
}

public class Client
{
	TcpClient client;
	Dictionary<int, StreamInfo> stream_handlers;
	NetworkStream stream;

	public event NewStream StreamInitializing;
	public event Action<Stream, int> EndOfStream;

	System.Threading.Thread read_thread;
	volatile bool reading;

	public Client(IPEndPoint endpoint)
	{
		client = new TcpClient();
		client.Connect(endpoint);
	}
	public void BeginListen()
	{
		read_thread = new System.Threading.Thread(new System.Threading.ThreadStart(DoRead));
		read_thread.Start();
	}
	public void DoRead()
	{
		reading = true;
		while (client.Connected && reading)
		{
			if (client.Available > 0)
				PerformRead();
			System.Threading.Thread.Sleep(0);				
		}
		reading = false;
	}
	public void EndListen()
	{
		reading = false;
		read_thread.Join();
	}
	private void PerformRead()
	{
		var pack = new Package(stream);
		if (pack != null)
		{
			// Check if stream is already initialized.
			if (stream_handlers.ContainsKey(pack.StreamID))
			{
				var info = stream_handlers[pack.StreamID];
				info.Stream.Write(pack.Data.ToArray(), 0, pack.Size);
				// Check if this is the last packet in the stream
				if (pack.Packet == info.StreamPackets)
				{
					if (EndOfStream != null)
						EndOfStream(info.Stream, info.StreamID);
					// Remove stream handler
					stream_handlers.Remove(pack.StreamID);
				}
			}
			else
			{
				// Check if this is an initializer package
				var init = pack.GetInitializerPackage();
				if (init != null)
				{
					// Use callback to register a new stream
					if (StreamInitializing != null)
					{
						var s = StreamInitializing(init);
						if (s != null)
							stream_handlers.Add(pack.StreamID, new StreamInfo(init, s));
					}
				}
			}
		}
	}
}

}

Lenke til kommentar

Da har jeg noe som virker.

 

Klientene sender nå all dataene i formen: [header][packet] hvor header har en 'id' og 'totalt_antall_packets' og packet har en 'index' og 'data(byte[])' property.

 

Serveren mottar pakkene, sjekker om header.id allerede har blitt mottatt og hvis ikke lager den et 'transfer' objekt og legger til packet.data til det. Hvis header.id allerede er mottatt legger den til packet.data til transfer objektet som allerede er opprettet for denne headeren.

 

Hver gang en packet.data blir lagt til sjekker den om packet.index == header.totaltAntallPackets og fyrer en event hvis true.

 

 

Funker ganske bra egentlig. Takker for inspirasjonen og hjelpen. :)

Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
×
×
  • Opprett ny...