Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/trunk/src/lib/network/network_stream.cc @ 8148

Last change on this file since 8148 was 8068, checked in by patrick, 19 years ago

trunk: merged the network branche back to trunk

File size: 21.3 KB
Line 
1/*
2   orxonox - the future of 3D-vertical-scrollers
3
4   Copyright (C) 2004 orx
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11### File Specific:
12   main-programmer: claudio
13   co-programmer:
14*/
15
16
17/* this is for debug output. It just says, that all calls to PRINT() belong to the DEBUG_MODULE_NETWORK module
18   For more information refere to https://www.orxonox.net/cgi-bin/trac.cgi/wiki/DebugOutput
19*/
20#define DEBUG_MODULE_NETWORK
21
22
23#include "base_object.h"
24#include "network_protocol.h"
25#include "udp_socket.h"
26#include "udp_server_socket.h"
27#include "connection_monitor.h"
28#include "synchronizeable.h"
29#include "network_game_manager.h"
30#include "shared_network_data.h"
31#include "message_manager.h"
32#include "preferences.h"
33#include "zip.h"
34
35#include "src/lib/util/loading/resource_manager.h"
36
37#include "network_log.h"
38
39
40#include "lib/util/loading/factory.h"
41
42#include "debug.h"
43#include "class_list.h"
44#include <algorithm>
45
46/* include your own header */
47#include "network_stream.h"
48
49/* probably unnecessary */
50using namespace std;
51
52
53#define PACKAGE_SIZE  256
54
55
56NetworkStream::NetworkStream()
57    : DataStream()
58{
59  this->init();
60  /* initialize the references */
61  this->type = NET_CLIENT;
62}
63
64
65NetworkStream::NetworkStream( std::string host, int port )
66{
67  this->type = NET_CLIENT;
68  this->init();
69  this->peers[0].socket = new UdpSocket( host, port );
70  this->peers[0].userId = 0;
71  this->peers[0].isServer = true;
72  this->peers[0].connectionMonitor = new ConnectionMonitor( 0 );
73}
74
75
76NetworkStream::NetworkStream( int port )
77{
78  this->type = NET_SERVER;
79  this->init();
80  this->serverSocket = new UdpServerSocket(port);
81  this->bActive = true;
82}
83
84
85void NetworkStream::init()
86{
87  /* set the class id for the base object */
88  this->setClassID(CL_NETWORK_STREAM, "NetworkStream");
89  this->bActive = false;
90  this->serverSocket = NULL;
91  this->networkGameManager = NULL;
92  myHostId = 0;
93  currentState = 0;
94 
95  remainingBytesToWriteToDict = Preferences::getInstance()->getInt( "compression", "writedict", 0 );
96 
97  assert( Zip::getInstance()->loadDictionary( "testdict" ) );
98}
99
100
101NetworkStream::~NetworkStream()
102{
103  if ( this->serverSocket )
104  {
105    serverSocket->close();
106    delete serverSocket;
107  }
108
109  for ( PeerList::iterator i = peers.begin(); i!=peers.end(); i++)
110  {
111    if ( i->second.socket )
112    {
113      i->second.socket->disconnectServer();
114      delete i->second.socket;
115      i->second.socket = NULL;
116    }
117   
118    if ( i->second.handshake )
119    {
120      delete i->second.handshake;
121      i->second.handshake = NULL;
122    }
123  }
124 
125  if ( serverSocket )
126  {
127    delete serverSocket;
128    serverSocket = NULL;
129  }
130
131}
132
133
134void NetworkStream::createNetworkGameManager()
135{
136  this->networkGameManager = NetworkGameManager::getInstance();
137  // setUniqueID( maxCon+2 ) because we need one id for every handshake
138  // and one for handshake to reject client maxCon+1
139  this->networkGameManager->setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() );
140  MessageManager::getInstance()->setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() );
141}
142
143
144void NetworkStream::startHandshake()
145{
146  Handshake* hs = new Handshake(false);
147  hs->setUniqueID( 0 );
148  assert( peers[0].handshake == NULL );
149  peers[0].handshake = hs;
150//   peers[0].handshake->setSynchronized( true );
151  //this->connectSynchronizeable(*hs);
152  //this->connectSynchronizeable(*hs);
153  PRINTF(0)("NetworkStream: Handshake created: %s\n", hs->getName());
154}
155
156
157void NetworkStream::connectSynchronizeable(Synchronizeable& sync)
158{
159  this->synchronizeables.push_back(&sync);
160  sync.setNetworkStream( this );
161
162  this->bActive = true;
163}
164
165
166void NetworkStream::disconnectSynchronizeable(Synchronizeable& sync)
167{
168  // removing the Synchronizeable from the List.
169  std::list<Synchronizeable*>::iterator disconnectSynchro = std::find(this->synchronizeables.begin(), this->synchronizeables.end(), &sync);
170  if (disconnectSynchro != this->synchronizeables.end())
171    this->synchronizeables.erase(disconnectSynchro);
172 
173  oldSynchronizeables[sync.getUniqueID()] = SDL_GetTicks();
174}
175
176
177void NetworkStream::processData()
178{
179  int tick = SDL_GetTicks();
180 
181  currentState++;
182 
183  if ( this->type == NET_SERVER )
184  {
185    if ( serverSocket )
186      serverSocket->update();
187   
188    this->updateConnectionList();
189  }
190  else
191  {
192    if ( peers[0].socket && ( !peers[0].socket->isOk() || peers[0].connectionMonitor->hasTimedOut() ) )
193    {
194      PRINTF(1)("lost connection to server\n");
195
196      peers[0].socket->disconnectServer();
197      delete peers[0].socket;
198      peers[0].socket = NULL;
199
200      if ( peers[0].handshake )
201        delete peers[0].handshake;
202      peers[0].handshake = NULL;
203    }
204  }
205
206  cleanUpOldSyncList();
207  handleHandshakes();
208 
209  // order of up/downstream is important!!!!
210  // don't change it
211  handleDownstream( tick );
212  handleUpstream( tick );
213
214}
215
216void NetworkStream::updateConnectionList( )
217{
218  //check for new connections
219
220  NetworkSocket* tempNetworkSocket = serverSocket->getNewSocket();
221
222  if ( tempNetworkSocket )
223  {
224    int clientId;
225    if ( freeSocketSlots.size() >0 )
226    {
227      clientId = freeSocketSlots.back();
228      freeSocketSlots.pop_back();
229      peers[clientId].socket = tempNetworkSocket;
230      peers[clientId].handshake = new Handshake(true, clientId, this->networkGameManager->getUniqueID(), MessageManager::getInstance()->getUniqueID() );
231      peers[clientId].connectionMonitor = new ConnectionMonitor( clientId );
232      peers[clientId].handshake->setUniqueID(clientId);
233      peers[clientId].userId = clientId;
234      peers[clientId].isServer = false;
235    } else
236    {
237      clientId = 1;
238     
239      for ( PeerList::iterator it = peers.begin(); it != peers.end(); it++ )
240        if ( it->first >= clientId )
241          clientId = it->first + 1;
242     
243      peers[clientId].socket = tempNetworkSocket;
244      peers[clientId].handshake = new Handshake(true, clientId, this->networkGameManager->getUniqueID(), MessageManager::getInstance()->getUniqueID());
245      peers[clientId].handshake->setUniqueID(clientId);
246      peers[clientId].connectionMonitor = new ConnectionMonitor( clientId );
247      peers[clientId].userId = clientId;
248      peers[clientId].isServer = false;
249     
250      PRINTF(0)("num sync: %d\n", synchronizeables.size());
251    }
252
253    if ( clientId > MAX_CONNECTIONS )
254    {
255      peers[clientId].handshake->doReject( "too many connections" );
256      PRINTF(0)("Will reject client %d because there are to many connections!\n", clientId);
257    }
258    else
259
260    PRINTF(0)("New Client: %d\n", clientId);
261
262    //this->connectSynchronizeable(*handshakes[clientId]);
263  }
264
265  //check if connections are ok else remove them
266  for ( PeerList::iterator it = peers.begin(); it != peers.end(); it++ )
267  {
268    if ( 
269          it->second.socket &&
270          ( 
271            !it->second.socket->isOk()  ||
272            it->second.connectionMonitor->hasTimedOut()
273          )
274       )
275    {
276      std::string reason = "disconnected";
277      if ( it->second.connectionMonitor->hasTimedOut() )
278        reason = "timeout";
279      PRINTF(0)("Client is gone: %d (%s)\n", it->second.userId, reason.c_str());
280     
281      //assert(false);
282
283      it->second.socket->disconnectServer();
284      delete it->second.socket;
285      it->second.socket = NULL;
286
287      if ( it->second.handshake )
288        delete it->second.handshake;
289      it->second.handshake = NULL;
290     
291      for ( SynchronizeableList::iterator it2 = synchronizeables.begin(); it2 != synchronizeables.end(); it2++ )
292      {
293        (*it2)->cleanUpUser( it->second.userId );
294      }
295
296      NetworkGameManager::getInstance()->signalLeftPlayer(it->second.userId);
297
298      freeSocketSlots.push_back( it->second.userId );
299
300    }
301  }
302
303
304}
305
306void NetworkStream::debug()
307{
308  if( this->isServer())
309    PRINT(0)(" Host ist Server with ID: %i\n", this->myHostId);
310  else
311    PRINT(0)(" Host ist Client with ID: %i\n", this->myHostId);
312
313  PRINT(0)(" Got %i connected Synchronizeables, showing active Syncs:\n", this->synchronizeables.size());
314  for (SynchronizeableList::iterator it = synchronizeables.begin(); it!=synchronizeables.end(); it++)
315  {
316    if( (*it)->beSynchronized() == true)
317      PRINT(0)("  Synchronizeable of class: %s::%s, with unique ID: %i, Synchronize: %i\n", (*it)->getClassName(), (*it)->getName(),
318               (*it)->getUniqueID(), (*it)->beSynchronized());
319  }
320  PRINT(0)(" Maximal Connections: %i\n", MAX_CONNECTIONS );
321
322}
323
324
325int NetworkStream::getSyncCount()
326{
327  int n = 0;
328  for (SynchronizeableList::iterator it = synchronizeables.begin(); it!=synchronizeables.end(); it++)
329    if( (*it)->beSynchronized() == true)
330      ++n;
331
332  //return synchronizeables.size();
333  return n;
334}
335
336/**
337 * check if handshakes completed
338 */
339void NetworkStream::handleHandshakes( )
340{
341  for ( PeerList::iterator it = peers.begin(); it != peers.end(); it++ )
342  {
343    if ( it->second.handshake )
344    {
345      if ( it->second.handshake->completed() )
346      {
347        if ( it->second.handshake->ok() )
348        {
349          if ( !it->second.handshake->allowDel() )
350          {
351            if ( type != NET_SERVER )
352            {
353              SharedNetworkData::getInstance()->setHostID( it->second.handshake->getHostId() );
354              myHostId = SharedNetworkData::getInstance()->getHostID();
355
356              this->networkGameManager = NetworkGameManager::getInstance();
357              this->networkGameManager->setUniqueID( it->second.handshake->getNetworkGameManagerId() );
358              MessageManager::getInstance()->setUniqueID( it->second.handshake->getMessageManagerId() );
359            }
360             
361
362            PRINT(0)("handshake finished id=%d\n", it->second.handshake->getNetworkGameManagerId());
363
364            it->second.handshake->del();
365          }
366          else
367          {
368            if ( it->second.handshake->canDel() )
369            {
370              if ( type == NET_SERVER )
371              {
372                handleNewClient( it->second.userId );
373              }
374             
375              PRINT(0)("handshake finished delete it\n");
376              delete it->second.handshake;
377              it->second.handshake = NULL;
378            }
379          }
380
381        }
382        else
383        {
384          PRINT(1)("handshake failed!\n");
385          it->second.socket->disconnectServer();
386        }
387      }
388    }
389  }
390}
391
392/**
393 * handle upstream network traffic
394 */
395void NetworkStream::handleUpstream( int tick )
396{
397  int offset;
398  int n;
399 
400  for ( PeerList::reverse_iterator peer = peers.rbegin(); peer != peers.rend(); peer++ )
401  {
402    offset = INTSIZE; //make already space for length
403   
404    if ( !peer->second.socket )
405      continue;
406   
407    n = Converter::intToByteArray( currentState, buf + offset, UDP_PACKET_SIZE - offset );
408    assert( n == INTSIZE );
409    offset += n;
410   
411    n = Converter::intToByteArray( peer->second.lastAckedState, buf + offset, UDP_PACKET_SIZE - offset );
412    assert( n == INTSIZE );
413    offset += n;
414   
415    n = Converter::intToByteArray( peer->second.lastRecvedState, buf + offset, UDP_PACKET_SIZE - offset );
416    assert( n == INTSIZE );
417    offset += n;
418   
419    for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
420    {
421      int oldOffset = offset;
422      Synchronizeable & sync = **it;
423     
424      if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
425        continue;
426
427      //if handshake not finished only sync handshake
428      if ( peer->second.handshake && sync.getLeafClassID() != CL_HANDSHAKE )
429        continue;
430     
431      if ( isServer() && sync.getLeafClassID() == CL_HANDSHAKE && sync.getUniqueID() != peer->second.userId )
432        continue;
433     
434      //do not sync null parent
435      if ( sync.getLeafClassID() == CL_NULL_PARENT )
436        continue;
437
438      assert( offset + INTSIZE <= UDP_PACKET_SIZE );
439     
440      //server fakes uniqueid=0 for handshake
441      if ( this->isServer() && sync.getUniqueID() < MAX_CONNECTIONS - 1 )
442        n = Converter::intToByteArray( 0, buf + offset, UDP_PACKET_SIZE - offset );
443      else
444        n = Converter::intToByteArray( sync.getUniqueID(), buf + offset, UDP_PACKET_SIZE - offset );
445      assert( n == INTSIZE );
446      offset += n;
447     
448      //make space for size
449      offset += INTSIZE;
450
451      n = sync.getStateDiff( peer->second.userId, buf + offset, UDP_PACKET_SIZE-offset, currentState, peer->second.lastAckedState, -1000 );
452      offset += n;
453      //NETPRINTF(0)("GGGGGEEEEETTTTT: %s (%d) %d\n",sync.getClassName(), sync.getUniqueID(), n);
454     
455      assert( Converter::intToByteArray( n, buf + offset - n - INTSIZE, INTSIZE ) == INTSIZE );
456     
457      //check if all bytes == 0 -> remove data
458      //TODO not all synchronizeables like this maybe add Synchronizeable::canRemoveZeroDiff()
459      bool allZero = true; 
460      for ( int i = 0; i < n; i++ ) 
461      { 
462         if ( buf[i+oldOffset+2*INTSIZE] != 0 ) 
463           allZero = false; 
464      } 
465
466      if ( allZero ) 
467      { 
468        //NETPRINTF(n)("REMOVE ZERO DIFF: %s (%d)\n", sync.getClassName(), sync.getUniqueID());
469        offset = oldOffset; 
470      } 
471
472     
473    }
474   
475    for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
476    {
477      Synchronizeable & sync = **it;
478     
479      if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
480        continue;
481     
482      sync.handleSentState( peer->second.userId, currentState, peer->second.lastAckedState );
483    }
484   
485    assert( Converter::intToByteArray( offset, buf, INTSIZE ) == INTSIZE );
486   
487    int compLength = Zip::getInstance()->zip( buf, offset, compBuf, UDP_PACKET_SIZE );
488   
489    if ( compLength < 0 )
490    {
491      PRINTF(1)("compression failed!\n");
492      continue;
493    }
494   
495    assert( peer->second.socket->writePacket( compBuf, compLength ) );
496   
497    if ( this->remainingBytesToWriteToDict > 0 )
498      writeToNewDict( buf, offset );
499   
500    peer->second.connectionMonitor->processUnzippedOutgoingPacket( tick, buf, offset, currentState );
501    peer->second.connectionMonitor->processZippedOutgoingPacket( tick, compBuf, compLength, currentState );
502   
503    //NETPRINTF(n)("send packet: %d userId = %d\n", offset, peer->second.userId);
504  }
505}
506
507/**
508 * handle downstream network traffic
509 */
510void NetworkStream::handleDownstream( int tick )
511{
512  int offset = 0;
513 
514  int length = 0;
515  int packetLength = 0;
516  int compLength = 0;
517  int uniqueId = 0;
518  int state = 0;
519  int ackedState = 0;
520  int fromState = 0;
521  int syncDataLength = 0;
522 
523  for ( PeerList::iterator peer = peers.begin(); peer != peers.end(); peer++ )
524  {
525   
526    if ( !peer->second.socket )
527      continue;
528
529    while ( 0 < (compLength = peer->second.socket->readPacket( compBuf, UDP_PACKET_SIZE )) )
530    {
531      peer->second.connectionMonitor->processZippedIncomingPacket( tick, compBuf, compLength );
532     
533      //PRINTF(0)("GGGGGOOOOOOOOOOTTTTTTTT: %d\n", compLength);
534      packetLength = Zip::getInstance()->unZip( compBuf, compLength, buf, UDP_PACKET_SIZE );
535     
536      if ( packetLength < 4*INTSIZE )
537      {
538        if ( packetLength != 0 )
539          PRINTF(1)("got too small packet: %d\n", packetLength);
540        continue;
541      }
542     
543      if ( this->remainingBytesToWriteToDict > 0 )
544        writeToNewDict( buf, packetLength );
545   
546      assert( Converter::byteArrayToInt( buf, &length ) == INTSIZE );
547      assert( Converter::byteArrayToInt( buf + INTSIZE, &state ) == INTSIZE );
548      assert( Converter::byteArrayToInt( buf + 2*INTSIZE, &fromState ) == INTSIZE );
549      assert( Converter::byteArrayToInt( buf + 3*INTSIZE, &ackedState ) == INTSIZE );
550      //NETPRINTF(n)("ackedstate: %d\n", ackedState);
551      offset = 4*INTSIZE;
552     
553      peer->second.connectionMonitor->processUnzippedIncomingPacket( tick, buf, offset, state, ackedState );
554
555      //NETPRINTF(n)("got packet: %d, %d\n", length, packetLength);
556   
557    //if this is an old state drop it
558      if ( state <= peer->second.lastRecvedState )
559        continue;
560   
561      if ( packetLength != length )
562      {
563        PRINTF(1)("real packet length (%d) and transmitted packet length (%d) do not match!\n", packetLength, length);
564        peer->second.socket->disconnectServer();
565        continue;
566      }
567     
568      while ( offset + 2*INTSIZE < length )
569      {
570        assert( offset > 0 );
571        assert( Converter::byteArrayToInt( buf + offset, &uniqueId ) == INTSIZE );
572        offset += INTSIZE;
573     
574        assert( Converter::byteArrayToInt( buf + offset, &syncDataLength ) == INTSIZE );
575        offset += INTSIZE;
576       
577        assert( syncDataLength > 0 );
578        assert( syncDataLength < 10000 );
579     
580        Synchronizeable * sync = NULL;
581       
582        for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
583        { 
584        //                                        client thinks his handshake has id 0!!!!!
585          if ( (*it)->getUniqueID() == uniqueId || ( uniqueId == 0 && (*it)->getUniqueID() == peer->second.userId ) )
586          {
587            sync = *it;
588            break;
589          }
590        }
591       
592        if ( sync == NULL )
593        {
594          PRINTF(0)("could not find sync with id %d. try to create it\n", uniqueId);
595          if ( oldSynchronizeables.find( uniqueId ) != oldSynchronizeables.end() )
596          {
597            offset += syncDataLength;
598            continue;
599          }
600         
601          if ( !peers[peer->second.userId].isServer )
602          {
603            offset += syncDataLength;
604            continue;
605          }
606         
607          int leafClassId;
608          if ( INTSIZE > length - offset )
609          {
610            offset += syncDataLength;
611            continue;
612          }
613
614          Converter::byteArrayToInt( buf + offset, &leafClassId );
615         
616          assert( leafClassId != 0 );
617       
618          BaseObject * b = NULL;
619          /* These are some small exeptions in creation: Not all objects can/should be created via Factory */
620          /* Exception 1: NullParent */
621          if( leafClassId == CL_NULL_PARENT || leafClassId == CL_SYNCHRONIZEABLE || leafClassId == CL_NETWORK_GAME_MANAGER )
622          {
623            PRINTF(1)("Can not create Class with ID %x!\n", (int)leafClassId);
624            offset += syncDataLength;
625            continue;
626          }
627          else
628            b = Factory::fabricate( (ClassID)leafClassId );
629
630          if ( !b )
631          {
632            PRINTF(1)("Could not fabricate Object with classID %x\n", leafClassId);
633            offset += syncDataLength;
634            continue;
635          }
636
637          if ( b->isA(CL_SYNCHRONIZEABLE) )
638          {
639            sync = dynamic_cast<Synchronizeable*>(b);
640            sync->setUniqueID( uniqueId );
641            sync->setSynchronized(true);
642 
643            PRINTF(0)("Fabricated %s with id %d\n", sync->getClassName(), sync->getUniqueID());
644          }
645          else
646          {
647            PRINTF(1)("Class with ID %x is not a synchronizeable!\n", (int)leafClassId);
648            delete b;
649            offset += syncDataLength;
650            continue;
651          }
652        }
653
654        int n = sync->setStateDiff( peer->second.userId, buf+offset, syncDataLength, state, fromState ); 
655        offset += n;
656        //NETPRINTF(0)("SSSSSEEEEETTTTT: %s %d\n",sync->getClassName(), n);
657
658      }
659     
660      if ( offset != length )
661      {
662        PRINTF(0)("offset (%d) != length (%d)\n", offset, length);
663        peer->second.socket->disconnectServer();
664      }
665     
666      //TODO REMOVE THIS
667      int saveOffset = offset;
668     
669      for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
670      {
671        Synchronizeable & sync = **it;
672     
673        if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
674          continue;
675     
676        sync.handleRecvState( peer->second.userId, state, fromState );
677      }
678     
679      assert( peer->second.lastAckedState <= ackedState );
680      peer->second.lastAckedState = ackedState;
681     
682      assert( peer->second.lastRecvedState < state );
683      peer->second.lastRecvedState = state;
684     
685      assert( saveOffset == offset );
686     
687    }
688 
689  }
690 
691}
692
693/**
694 * is executed when a handshake has finished
695 * @todo create playable for new user
696 */
697void NetworkStream::handleNewClient( int userId )
698{
699  MessageManager::getInstance()->initUser( userId );
700 
701  networkGameManager->signalNewPlayer( userId );
702}
703
704/**
705 * removes old items from oldSynchronizeables
706 */
707void NetworkStream::cleanUpOldSyncList( )
708{
709  int now = SDL_GetTicks();
710 
711  for ( std::map<int,int>::iterator it = oldSynchronizeables.begin(); it != oldSynchronizeables.end();  )
712  {
713    if ( it->second < now - 10*1000 )
714    {
715      std::map<int,int>::iterator delIt = it;
716      it++;
717      oldSynchronizeables.erase( delIt );
718      continue;
719    }
720    it++;
721  }
722}
723
724/**
725 * writes data to DATA/dicts/newdict
726 * @param data pointer to data
727 * @param length length
728 */
729void NetworkStream::writeToNewDict( byte * data, int length )
730{
731  if ( remainingBytesToWriteToDict <= 0 )
732    return;
733 
734  if ( length > remainingBytesToWriteToDict )
735    length = remainingBytesToWriteToDict;
736 
737  std::string fileName = ResourceManager::getInstance()->getDataDir();
738  fileName += "/dicts/newdict";
739 
740  FILE * f = fopen( fileName.c_str(), "a" );
741 
742  if ( !f )
743  {
744    PRINTF(2)("could not open %s\n", fileName.c_str());
745    remainingBytesToWriteToDict = 0;
746    return;
747  }
748 
749  if ( fwrite( data, 1, length, f ) != length )
750  {
751    PRINTF(2)("could not write to file\n");
752    fclose( f );
753    return;
754  }
755 
756  fclose( f );
757 
758  remainingBytesToWriteToDict -= length; 
759}
760
761
762
763
764
765
Note: See TracBrowser for help on using the repository browser.