Help needed with sockets

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
RoninOni
Forum Newbie
Posts: 2
Joined: Wed Feb 21, 2007 5:58 pm

Help needed with sockets

Post by RoninOni »

The Ninja Space Goat | Please use

Code: Select all

,

Code: Select all

and [syntax="..."] tags where appropriate when posting code. Your post has been edited to reflect how we'd like it posted. Please read:  [url=http://forums.devnetwork.net/viewtopic.php?t=21171]Posting Code in the Forums[/url] to learn how to do it too.[/color]


Ok, I have got the biggest migraine right now from trying to figure this out.

The programs purpose is to send a group of files (info stored in an array) through custom ports via PHP's socket functions.

So I have it split in 3 parts. I only have a problem with 2.

Server.php is the master socket server. Clients connect, login and get redirected to a different port to upload their file (or other purpose when I get them). This works fine.

Client.php first retrieves settings from a database (via a specifically constructed web page) then Logs in to the master socket server which redirects it to another port (and script clientbackup.php) to handle file upload

clientbackup.php first receives a confirmation login from the client (to make sure its the right computer on the right port) it then reads the socket in infinite loop (broken by null) an initialization string "--sending file|filename|filedata|etc|etc" which starts a second loop. While in this loop the client should (and is) sending the contents of the file and the server is writing the file packet by packet as they come in. The problem I have is properly escaping out of this loop without an error.

I am currently using socket_write and socket_read due to socket_send and socket_recv's incredibly poor documentation. (Write and Read are bad too, but they are a bit simpler). 

Is what is happening is that the EOF tag I created (because despite my greatest efforts I couldn’t find no single byte character that didn’t exist in any file)  is getting appended to the end of the file. Basically the last packet of the file is well below the socket_recv packet size and so it keeps reading until the eof is sent. I had, at one point, checked for the existence of the eof tag in string, and if exist, remove it then write the string to the file close, etc. however then the problem comes if the read ends part way through the tag (ie it reaches 1024 bytes at %%##%%##EO and F##%%##%% ends up as part of the next line)  

If anyone has any insight as to a work around (FTP is not an acceptable solution)  I would really appreciate it. I have a feeling that somehow the flags with send and recv might make the difference, but I can’t seem to pin down much information about them.

Here is the server code relevant to the file transfer.

Code: Select all

$fileTransfering = true;
while($fileTransfering)	{
	$input = socket_read($client,1024);
	$input = str_replace(chr(0),'',$input);
	echo $input."\n";
	if ($input == 'Finished Transfer')	{
		$fileTransfering = false;
	} elseif (strpos($input,"checkDB"))	{
		echo "enter Hash Check\n";
		$subPath = str_replace('--checkDB|','',$input);
		$filePath = $root.$subPath;
		$fID = 'fid_'.md5($filePath);
		$q = "select hash from scr_backuprecords where fileId = '$fID' && computerID = '$computerId'";
		$res = mysql_query($q) or die("Error:$q");
		$row = mysql_fetch_row($res);
		$chkHash = $row[0].chr(0);
		socket_write($client,$chkHash,1024);
	} elseif (strpos($input,"Sending File"))	{ //if File transfer
		//seperate file information
		$fileArray = explode('|',$input);
		$file = $fileArray[1];
		$path = $fileArray[2];
		$hash = $fileArray[3];
		$size = $fileArray[4];
		$time = $fileArray[5];
		$fileTransfer = true; //set fileTransfering to true
		$filePath = $root.$path.'/'.$file; //set filepath
		if(!is_dir($root.$path))	{ //check if destination directory exists
			$subPaths = explode('/',$path);  // seperate directories into an array
			$creationPath = $root; // don't override $root
			foreach($subPaths as $subPath)	{
				$creationPath .= $subPath.'/'; //setup next dir test/creation
		    if(!is_dir($creationPath)) mkdir($creationPath); //if subdir does not exist, create here
			}
		}
		$fp = fopen($filePath,'wb'); 
		socket_write($client,'--ready for file--',1000);
		while($fileTransfer)	{
			$data = socket_read($client , 1024, PHP_BINARY_READ);
			if ($data == '&&%%&&%%EOF%%&&%%&&'.chr(0) || !$data)	{
				echo "close $file\n";
				fclose($fp);
				$fileId = 'fid_'.md5($filePath); //Id is hashed filepath, 
				$localPath = substr($path,0,1).':'.substr($path,1);
				$checkHash = md5_file($filePath);
				socket_write($client,$checkHash,strlen($checkHash));
				if($hash == $checkHash)	{
					echo "File Transfer of $file from $name Good\n";
					$transferGood = 1;
				}	else	{
					echo "File Transfer of $file from $name Failed\n";
					echo "Original Hash=$hash\nUploaded Hash=$checkHash\n";
					$localPathMsg = strreplace('/','\\',$localPath);
					$failMsg .= "$localPathMsg\\$file\n";
					$transferGood = 0;
				}
				
						
				$q = "select fileId from scr_backuprecords where fileId = '$fileId';"; 
				$res = mysql_query($q) or die("bad query: $q\n".mysql_error());
				$row = mysql_fetch_row($res);
				$file = addslashes($file);
				$localPath = addslashes($localPath);
				$path = addslashes($path);
				if($row[0])	{ //run test if record exists for this file
					$q = "update scr_backuprecords set computerId = '$computerId', localPath = '$localPath', remotePath='$root$path', fileName = '$file', size = '$size', time = '$time', hash = '$hash', transferGood = $transferGood where fileId = '$fileId'";
				} else {
					$q = "insert into scr_backuprecords set fileId = '$fileId', computerId = '$computerId', localPath = '$localPath', remotePath='$root$path', fileName = '$file', size = '$size', time = '$time', hash = '$hash', transferGood = $transferGood";
				}
				mysql_query($q) or die("bad query:  $q\n".mysql_error());
				
				$fileTransfer = false;
				break;
			}	else	{
				fwrite($fp,$data);
			}
		}
	} elseif ($input == null)	{
		echo "null\n";
		$filetransfering = false;
		break 2;
		
	}
}


And here is the client side code that is relevant 

$sendingFile = true;
$sendingAttemptCount = 1;
while($sendingFile)	{
	$sendFile = "--Sending File|$file|$sendPath|$hash|$size|$time".chr(0); //set sending file tag
	echo "  $file\n";
	socket_write($upload_socket,$sendFile,strlen($sendFile)); //send file info to be parsed by server
	$response = socket_read($upload_socket,1000);
	if(!strpos($response,'ready')) break;
	//sleep(1); //required to prevent concatination
	$fp = fopen($path.$file,'rb'); //open file to send
	while (!feof($fp))	{
		$in = fread($fp,1024);
		socket_write($upload_socket, $in, strlen($in)); 
	} //end file reading loop
	sleep(1);	
	$eof =	'&&%%&&%%EOF%%&&%%&&'.chr(0);  //set eof tag
	socket_write($upload_socket,$eof,strlen($eof)); //send end of file		
	$checkHash = socket_read($upload_socket,1024);
	if ($hash == $checkHash)	{
		echo "Transfer of $file Good\n";
		$iniFile = fopen($path.'SageBackupSettings.ini','a');
		$writeText = "$file|$size|$time|$hash\n";
		fwrite($iniFile,$writeText);
		fclose($iniFile);
		$sendingFile = false;
		$process = "This is a special http request with a get variable that is used to update the database";
		$handle = fopen($process, "r");
	} else {
		echo "Transfer of $file Bad\n$hash\n$checkHash\n";
		$bitesSent = $sendingAttemptCount * $size;
		if ($bitesSent > 10737418240) $sendingFile = false;
		$sendingAttemptCount++;
	}
}

The Ninja Space Goat | Please use

Code: Select all

,

Code: Select all

and [syntax="..."] tags where appropriate when posting code. Your post has been edited to reflect how we'd like it posted. Please read:  [url=http://forums.devnetwork.net/viewtopic.php?t=21171]Posting Code in the Forums[/url] to learn how to do it too.[/color]
User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Post by Mordred »

I haven't read your code entirely, but based on your description of the problem, there are two solutions:

1. Change the protocol. Instead of FILE + EOF_MARKER send FILE_LENGTH + FILE.
2. Put the last two received chunks in a buffer and check inside this bigger buffer for you eof marker. This will solve the problem of receiving half the EOF marker in a chunk.


As it is, your current check also seems wrong:

Code: Select all

$data = socket_read($client , 1024, PHP_BINARY_READ);
if ($data == '&&%%&&%%EOF%%&&%%&&'.chr(0) || !$data)
It seems to expect that the EOF marker appears on a 1024 boundary, which is generally not true, unless you are sure the sizes of your files are multiples of 1024.

You should check the presense of the EOF marker with strpos().
RoninOni
Forum Newbie
Posts: 2
Joined: Wed Feb 21, 2007 5:58 pm

Problem solved... I think

Post by RoninOni »

I actually had rewritten the code to do just that (solution 2) last night before calling it a night.

however I had originally put a sleep(1) to try and force a break of packets, and cause the EOF marker to be by itself, and it seemed to work, most of the time, but not all of the time.

In any case, your solution #2 does work.

I had issues with trying to do it mathmatically however (based on filesize) a couple months ago when I first started the sript. I tried to determine how many packets there would be (filesize/packetsize) but any null character that was read from a file would cause a break and mess up the count.

Has anyone else written a file transfer socket server that they could share their methods, successes and failures?
User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Re: Problem solved... I think

Post by Mordred »

RoninOni wrote:I tried to determine how many packets there would be (filesize/packetsize) but any null character that was read from a file would cause a break and mess up the count.
This doesn't sound right. First, you use PHP_BINARY_READ which is just that - able to read any data, including null. Maybe in your previous attempt you didn't?

Second, there is no "packetsize" with TCP which can be predicted. You may get 1 byte or you may get 2K. The number of bytes - 1024 in your case - indicate how big is your buffer (which in the context of PHP doesn't make sense actually, don't ask me why its in the API :) ), not how much will be actually read!

The most reliable method by all means is 1 in my post above, you know beforehand how long the file is and then read up to that many bytes and then stop. Straightforward as a brick ;)
Post Reply