I’ve opened a socket between two machines. On both ends, they call BeginReceive and wait for each other to send data. Either one could send data first, it depends on the user.
I’m finding that I cannot send data both ways, and only the app that initiated the connection can send data.
Edit: Added code for File Tunnel program; added comment showing where I messed up
//-----------Main.cs file------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;
public Socket listen_socket = null; //Listening socket (activated by clicking a button)
public List<Socket> connections = new List<Socket>(); //Stores all active connections to other server/client hybrids
public List<FileReceiver> receivers = new List<FileReceiver>(); //One FileReceiver for each active connection, handles receiving files on the connection
public List<FileSender> senders = new List<FileSender>(); //One FileSender per connection when a file is dropped in the interface, handles sending the file to each connected instance
public Queue<string> files_to_send = new Queue<string>(); //Queues files to send, if multiple files are dropped in the interface at once
public Main()
{
InitializeComponent();
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
lstFiles.DragEnter += new DragEventHandler(lstFiles_DragEnter);
lstFiles.DragDrop += new DragEventHandler(lstFiles_DragDrop);
}
private void lstFiles_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.All;
}
void lstFiles_DragDrop(object sender, DragEventArgs e)
{
string[] filepaths = e.Data.GetData( DataFormats.FileDrop ) as string[];
if (filepaths != null)
{
lock (files_to_send)
{
foreach (string filepath in filepaths)
files_to_send.Enqueue( filepath );
SendNextFile();
}
}
}
void SendNextFile()
{
lock (files_to_send)
{
if (senders.Count == 0 && files_to_send.Count > 0)
{
string filepath = files_to_send.Dequeue();
FileInfo fi = new FileInfo( filepath );
string filename = fi.Name;
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter( ms, Encoding.Unicode );
writer.Write( (int)filename.Length ); //known-length (4 bytes); serves as hint to protocol so it doesn't try to read string until it knows enough data has been received
writer.Write( filename ); //known length once previous int has been read
writer.Write( fi.CreationTimeUtc.ToBinary() ); //known-length (8 bytes)
writer.Write( fi.LastWriteTimeUtc.ToBinary() ); //known-length (8 bytes)
writer.Write( fi.LastAccessTimeUtc.ToBinary() ); //known-length (8 bytes)
writer.Write( (long)fi.Length ); //known-length (8 bytes)
byte[] header = ms.ToArray();
foreach (Socket socket in connections)
{
FileSender filesender = new FileSender( fi, header, socket );
filesender.SendComplete += new FileSenderCompletedEventHandler(filesender_SendComplete);
senders.Add( filesender );
}
}
}
}
void filesender_SendComplete(FileSender sender)
{
lock (files_to_send)
{
senders.Remove( sender );
if (senders.Count == 0)
SendNextFile();
}
if (!sender.CompletedSuccessfully)
MessageBox.Show( "Failed to send " + sender.fi.FullName + " to " + sender.socket.RemoteEndPoint.ToString() );
}
void Application_ApplicationExit(object sender, EventArgs e)
{
lock (connections)
{
foreach (Socket socket in connections)
{
try
{
socket.Shutdown( SocketShutdown.Both );
socket.Close();
}
catch (Exception err)
{
}
}
connections.Clear();
}
}
private void btnListen_Click(object sender, EventArgs e)
{
try
{
btnListen.Enabled = false;
if (listen_socket != null)
listen_socket.Close();
listen_socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
listen_socket.Bind( new IPEndPoint( IPAddress.Any, int.Parse( txtListenPort.Text.Trim() ) ) );
listen_socket.Listen( 1 );
listen_socket.BeginAccept( new AsyncCallback( handleAccept ), listen_socket );
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
btnListen.Enabled = true;
if (listen_socket != null)
listen_socket.Close();
}
}
//INITIALIZES CONNECTION ON LISTENING END
private void BeginAccept_Callback( IAsyncResult result )
{
Socket listen_socket = result.AsyncState as Socket;
try
{
Socket connection = listen_socket.EndAccept( result );
lock (connections)
{
ConfigureSocket( connection );
FileReceiver receiver = AddSocketConnection( connection );
receiver.receiveData(); //<--WAS MISSING THIS!!! Initiates asynchronous BeginReceive call; FileReceiver handles processing the incoming stream as it arrives.
}
}
catch (Exception err)
{
MessageBox.Show( err.Message );
}
listen_socket.Close();
}
private void btnConnect_Click(object sender, EventArgs e)
{
string[] a = txtConnectIP.Text.Split( '.' );
IPAddress address = new IPAddress( new byte[] {byte.Parse( a[0] ), byte.Parse( a[1] ), byte.Parse( a[2] ), byte.Parse( a[3] ) } );
int port = int.Parse( txtConnectPort.Text );
Connect( address, port );
}
//INITIALIZES CONNECTION ON CONNECTING END
private void Connect( IPAddress address, int port )
{
Socket connection = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
lock (connections)
{
try
{
ConfigureSocket( connection );
connection.Connect( address, port );
FileReceiver receiver = AddSocketConnection( connection );
receiver.receiveData(); //<--REMEMBERED IT HERE!!! So I could receive files on the connecting end.
}
catch (Exception err)
{
MessageBox.Show( err.Message );
}
}
}
private void DisplayConnectionsCount( int count )
{
txtConnections.Text = count.ToString();
}
private void ConfigureSocket( Socket connection )
{
connection.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true );
connection.UseOnlyOverlappedIO = true;
connection.LingerState = new LingerOption( false, 0 );
}
private FileReceiver AddSocketConnection( Socket connection )
{
lock (connections)
{
connections.Add( connection );
Invoke( new Action<string>( DisplayConnectionCount ), connections.Count );
FileReceiver receiver = new FileReceiver( connection );
receivers.Add( receiver );
receiver.ReceiverShutdown += new FileReceiverShutdownEventHandler( receiver_Shutdown );
receiver.FileReceived += new FileReceiverReceiveEventHandler(receiver_FileReceived);
receiver.ReceiverDownloading += new FileReceiverDownloadingEventHandler(receiver_ReceiverDownloading);
return receiver;
}
}
private void RemoveSocketConnection( FileReceiver receiver )
{
lock (connections)
{
try
{
receiver.ReceiverShutdown -= new FileReceiverShutdownEventHandler( receiver_Shutdown );
receiver.FileReceived -= new FileReceiverReceiveEventHandler(receiver_FileReceived);
receiver.ReceiverDownloading -= new FileReceiverDownloadingEventHandler(receiver_ReceiverDownloading);
receivers.Remove( receiver );
connections.Remove( receiver.connection );
Invoke( new Action<int>( DisplayConnectionsCount ), connections.Count );
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
}
}
}
void receiver_ReceiverDownloading(FileReceiver receiver)
{
if (InvokeRequired)
Invoke( new FileReceiverDownloadingEventHandler( receiver_ReceiverDownloading ), receiver );
else
Text = "File Tunnel - Received " + receiver.BytesReceived + " bytes...";
}
void receiver_FileReceived(FileInfo fi)
{
if (lstFiles.InvokeRequired)
lstFiles.BeginInvoke( new FileReceiverReceiveEventHandler( receiver_FileReceived ), fi );
else
{
lstFiles.Items.Add( fi );
Text = "File Tunnel";
}
}
public void receiver_Shutdown( FileReceiver receiver )
{
RemoveSocketConnection( receiver );
}
//----FileSender.cs file------
public delegate void FileSenderCompletedEventHandler( FileSender sender );
public class FileSender
{
public Socket socket;
public FileInfo fi;
public FileStream fs;
public event FileSenderCompletedEventHandler SendComplete;
private byte[] header;
public bool CompletedSuccessfully = false;
public FileSender( FileInfo fi, byte[] header, Socket socket )
{
this.fi = fi;
this.socket = socket;
this.header = header;
try
{
socket.BeginSendFile( fi.FullName, header, null, TransmitFileOptions.UseSystemThread, new AsyncCallback( send_File ), this );
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
CompleteCallback();
}
}
private void send_File( IAsyncResult result )
{
try
{
socket.EndSendFile( result );
CompletedSuccessfully = true;
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
}
finally
{
CompleteCallback();
}
}
private void CompleteCallback()
{
try
{
if (SendComplete != null)
SendComplete( this );
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
}
}
}
//----FileReceiver.cs file------
public delegate void FileReceiverReceiveEventHandler( FileInfo fi );
public delegate void FileReceiverShutdownEventHandler( FileReceiver receiver );
public delegate void FileReceiverDownloadingEventHandler( FileReceiver receiver );
public enum FileReceiverProtocolStep
{
ReadFilenameLength,
ReadFilename,
ReadTimestamps,
ReadFileLength,
ReadFile
}
public class FileReceiver
{
public const int BUFFER_SIZE = 1024 * 1024;
public Socket connection;
private byte[] buffer = new byte[BUFFER_SIZE];
private long last_read_position = 0;
private FileReceiverProtocolStep protocol_step = FileReceiverProtocolStep.ReadFilenameLength;
private MemoryStream received_data = new MemoryStream();
public event FileReceiverReceiveEventHandler FileReceived;
public event FileReceiverShutdownEventHandler ReceiverShutdown;
public event FileReceiverDownloadingEventHandler ReceiverDownloading;
public FileReceiver( Socket connection )
{
this.connection = connection;
}
public long BytesReceived
{
get {return received_data.Length;}
}
public void receiveData()
{
connection.BeginReceive( buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback( receiveDataCallback ), connection );
}
private void receiveDataCallback( IAsyncResult result )
{
Socket connection = result.AsyncState as Socket;
int bytes_read;
try
{
bytes_read = connection.EndReceive( result );
if (bytes_read == 0)
{
if (received_data.Length > 0)
ProcessMemoryStream();
else
{
ShutDown(); //Nothing recieved... socket probably closed
return;
}
}
else
{
lock (received_data)
received_data.Write( buffer, 0, bytes_read );
ProcessMemoryStream(); //Process as much of the memory stream as possible
if (ReceiverDownloading != null)
ReceiverDownloading( this );
}
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
ShutDown();
return;
}
receiveData();
}
private void ShutDown()
{
try
{
connection.Shutdown( SocketShutdown.Both );
connection.Close();
}
catch (Exception err)
{
MessageBox.Show( err.Message + "\r\n" + err.StackTrace );
}
if (ReceiverShutdown != null)
ReceiverShutdown( this );
}
private int filename_length;
private string filename;
private DateTime timestamp_creation;
private DateTime timestamp_modified;
private DateTime timestamp_lastaccess;
long file_length;
private void ProcessMemoryStream()
{
//Prepare binary reader
lock (received_data)
{
BinaryReader reader = new BinaryReader( received_data, Encoding.Unicode );
received_data.Position = last_read_position;
next_step:
long bytes_available = received_data.Length - received_data.Position;
switch (protocol_step)
{
case FileReceiverProtocolStep.ReadFilenameLength:
//Read filename
if (bytes_available >= 4)
{
filename_length = reader.ReadInt32();
protocol_step = FileReceiverProtocolStep.ReadFilename;
goto next_step;
}
break;
case FileReceiverProtocolStep.ReadFilename:
if (bytes_available >= filename_length)
{
filename = reader.ReadString();
protocol_step = FileReceiverProtocolStep.ReadTimestamps;
goto next_step;
}
break;
case FileReceiverProtocolStep.ReadTimestamps:
if (bytes_available >= 24)
{
//Read timestamps
timestamp_creation = DateTime.FromBinary( reader.ReadInt64() );
timestamp_modified = DateTime.FromBinary( reader.ReadInt64() );
timestamp_lastaccess = DateTime.FromBinary( reader.ReadInt64() );
protocol_step = FileReceiverProtocolStep.ReadFileLength;
goto next_step;
}
break;
case FileReceiverProtocolStep.ReadFileLength:
if (bytes_available >= 8)
{
file_length = reader.ReadInt64();
protocol_step = FileReceiverProtocolStep.ReadFile;
goto next_step;
}
break;
case FileReceiverProtocolStep.ReadFile:
if (bytes_available >= file_length)
{
FileInfo fi = new FileInfo( filename.Replace( '\\', '_' ).Replace( '/', '_' ) ); //Disable relative path specification
FileStream fs = fi.Open( FileMode.Create, FileAccess.Write, FileShare.Read );
long bytes_to_save = file_length;
while (bytes_to_save > 0)
{
int bytes_to_read = (int)Math.Min( (long)BUFFER_SIZE, bytes_to_save );
bytes_to_save -= bytes_to_read;
fs.Write( reader.ReadBytes( bytes_to_read ), 0, bytes_to_read );
}
fs.Close();
fi.CreationTimeUtc = timestamp_creation;
fi.LastWriteTimeUtc = timestamp_modified;
fi.LastAccessTimeUtc = timestamp_lastaccess;
received_data.Position = 0;
received_data.SetLength( 0 );
//Reset protocol for next file
protocol_step = FileReceiverProtocolStep.ReadFilenameLength; //Ready for next file
filename_length = 0;
filename = String.Empty;
timestamp_creation = DateTime.MinValue;
timestamp_modified = DateTime.MinValue;
timestamp_lastaccess = DateTime.MinValue;
file_length = 0;
if (FileReceived != null)
FileReceived( fi );
}
break;
}
//Backup the last read position, and set the position to the end of the stream for subsquent write operations
last_read_position = received_data.Position;
received_data.Seek( 0, SeekOrigin.End );
}
}
}
So that’s the code for now. Excuse any sloppyness in this code, I wrote it as fast as possible, but that’s the entire program at this point, and it’s fully functional (Main.cs, FileReceiver.cs, and FileSender.cs). The Form consists of a listBox, and a few buttons and text boxes. The feedback is extremely basic at this point (displays bytes received in the titlebar as it’s receiving the file). Eventually, I’ll have it parse the IP addresses with RegExes with realtime feedback, accept hostnames with DNS resolvers, I’ll replace the listbox with an explorer-style list view, have it store things in a special directory, allow files to be dragged/moved into explorer, etc., etc.
I was not calling BeginReceive in the code that accepts the connection on the listening end (see comment in BeginAccept_Callback method in posted code.)
I can say with certainty now, that one can call BeginReceive on a socket and call the BeginSend* methods on it as well at any time, so one can receive and send data simultaneously over a single Socket.