netlrts: replace some CMK_SHARED_VARS_UNAVAILABLE with not CMK_SMP
[charm.git] / src / arch / net / machine-tcp.c
1 /** @file
2  * TCP implementation of Converse NET version
3  * @ingroup NET
4  * contains only TCP specific code for:
5  * - CmiMachineInit()
6  * - CmiCommunicationInit()
7  * - CheckSocketsReady()
8  * - CmiNotifyIdle()
9  * - DeliverViaNetwork()
10  * - CommunicationServer()
11
12   written by 
13   Gengbin Zheng, 12/21/2001
14   gzheng@uiuc.edu
15
16   now also works with SMP version  //  Gengbin 6/18/2003
17 */
18
19 /**
20  * @addtogroup NET
21  * @{
22  */
23
24 #if !defined(_WIN32) || defined(__CYGWIN__)
25 #include <netinet/tcp.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #endif
29
30 #define NO_NAGLE_ALG            1
31 #define FRAGMENTATION           1
32
33 #if FRAGMENTATION
34 #define PACKET_MAX              32767
35 #else
36 #define PACKET_MAX              1000000000
37 #endif
38
39 void ReceiveDatagram(int node);
40 int TransmitDatagram(int pe);
41
42 /******************************************************************************
43  *
44  * CmiNotifyIdle()-- wait until a packet comes in
45  *
46  *****************************************************************************/
47 typedef struct {
48   int sleepMs; /*Milliseconds to sleep while idle*/
49   int nIdles; /*Number of times we've been idle in a row*/
50   CmiState cs; /*Machine state*/
51 } CmiIdleState;
52
53
54 static CmiIdleState *CmiNotifyGetState(void) 
55 {
56   CmiIdleState *s=(CmiIdleState *)malloc(sizeof(CmiIdleState));
57   s->sleepMs=0;
58   s->nIdles=0;
59   s->cs=CmiGetState();
60   return s;
61 }
62
63 static void CmiNotifyBeginIdle(CmiIdleState *s)
64 {
65   s->sleepMs=0;
66   s->nIdles=0;
67 }
68
69 static void CmiNotifyStillIdle(CmiIdleState *s)
70 {
71 #if  !CMK_SMP 
72   CommunicationServerNet(10, COMM_SERVER_FROM_SMP);
73 #else
74   int nSpins=20; /*Number of times to spin before sleeping*/
75   s->nIdles++;
76   if (s->nIdles>nSpins) { /*Start giving some time back to the OS*/
77     s->sleepMs+=2;
78     if (s->sleepMs>10) s->sleepMs=10;
79   }
80   /*Comm. thread will listen on sockets-- just sleep*/
81   if (s->sleepMs>0) {
82     MACHSTATE1(3,"idle lock(%d) {",CmiMyPe())
83     CmiIdleLock_sleep(&s->cs->idle,s->sleepMs);
84     CsdResetPeriodic();         /* check ccd callbacks when I am awakened */
85     MACHSTATE1(3,"} idle lock(%d)",CmiMyPe())
86   }
87 #endif
88 }
89
90 void CmiNotifyIdle(void) {
91   CmiIdleState s;
92   s.sleepMs=5; 
93   CmiNotifyStillIdle(&s);
94 }
95
96 /****************************************************************************
97  *                                                                          
98  * CheckSocketsReady
99  *
100  * Checks both sockets to see which are readable and which are writeable.
101  * We check all these things at the same time since this can be done for
102  * free with ``select.'' The result is stored in global variables, since
103  * this is essentially global state information and several routines need it.
104  *
105  ***************************************************************************/
106
107 /*
108   FIXME !
109   current tcp version only allow the program to run on <= 1000 nodes
110   due to the static fixed size array below.
111   This can be easily fixed, however, I suspect tcp version won't scale well
112   on large number of processors due to the checking of sockets, 
113   so I don't bother.
114 */
115
116 static char sockReadStates[1000] = {0};
117 static char sockWriteStates[1000] = {0};
118
119 #if CMK_USE_POLL
120
121 #undef CMK_PIPE_DECL
122 #define CMK_PIPE_DECL(delayMs)  \
123         struct pollfd  fds[1000];       \
124         int nFds_sto=0; int *nFds=&nFds_sto; \
125         int pollDelayMs=delayMs;
126
127 #define CMK_PIPE_ADDREADWRITE(afd)      \
128       CMK_PIPE_ADDREAD(afd);    \
129       if (nodes[i].send_queue_h) fds[(*nFds)-1].events |= POLLOUT;
130         
131 #undef CMK_PIPE_CHECKWRITE
132 #define CMK_PIPE_CHECKWRITE(afd)        \
133         fds[*nFds].revents&POLLOUT
134
135 #define CMK_PIPE_SETUP  \
136         CmiStdoutAdd(CMK_PIPE_SUB);     \
137         if (Cmi_charmrun_fd!=-1) { CMK_PIPE_ADDREAD(Cmi_charmrun_fd); } \
138         if (dataskt!=-1) {      \
139           for (i=0; i<CmiNumNodes(); i++)       \
140           {     \
141             if (i == CmiMyNode()) continue;     \
142             CMK_PIPE_ADDREADWRITE(nodes[i].sock);       \
143           }     \
144         }       
145
146 #else
147
148 #define CMK_PIPE_SETUP  \
149         CmiStdoutAdd(CMK_PIPE_SUB);     \
150         if (Cmi_charmrun_fd!=-1) { CMK_PIPE_ADDREAD(Cmi_charmrun_fd); } \
151         if (dataskt!=-1) {      \
152           for (i=0; i<CmiNumNodes(); i++)       \
153           {     \
154             if (i == CmiMyNode()) continue;     \
155             CMK_PIPE_ADDREAD(nodes[i].sock);    \
156             if (nodes[i].send_queue_h) CMK_PIPE_ADDWRITE(nodes[i].sock);\
157           }     \
158         }               
159
160 #endif
161
162 /* check data sockets and invoking functions */
163 static void CmiCheckSocks()
164 {
165   int node;
166   if (dataskt!=-1) {
167     for (node=0; node<CmiNumNodes(); node++)
168     {
169       if (node == CmiMyNode()) continue;
170       if (sockReadStates[node]) {
171         MACHSTATE1(2,"go to ReceiveDatagram %d", node)
172         ReceiveDatagram(node);
173       }
174       if (sockWriteStates[node]) {
175         MACHSTATE1(2,"go to TransmitDatagram %d", node)
176         TransmitDatagram(node);
177       }
178     }
179   }
180 }
181
182 /*
183   when output = 1, this function is not thread safe
184 */
185 int CheckSocketsReady(int withDelayMs, int output)
186 {   
187   int nreadable,i;
188   CMK_PIPE_DECL(withDelayMs);
189
190   CMK_PIPE_SETUP;
191   nreadable=CMK_PIPE_CALL();
192
193   ctrlskt_ready_read = 0;
194   dataskt_ready_read = 0;
195   dataskt_ready_write = 0;
196
197   if (nreadable == 0) {
198     MACHSTATE(1,"} CheckSocketsReady (nothing readable)")
199     return nreadable;
200   }
201   if (nreadable==-1) {
202 #if defined(_WIN32) && !defined(__CYGWIN__)
203 /* Win32 socket seems to randomly return inexplicable errors
204 here-- WSAEINVAL, WSAENOTSOCK-- yet everything is actually OK. 
205         int err=WSAGetLastError();
206         CmiPrintf("(%d)Select returns -1; errno=%d, WSAerr=%d\n",withDelayMs,errno,err);
207 */
208 #else
209         if (errno!=EINTR)
210                 KillEveryone("Socket error in CheckSocketsReady!\n");
211 #endif
212     MACHSTATE(2,"} CheckSocketsReady (INTERRUPTED!)")
213     return CheckSocketsReady(0, output);
214   }
215
216   if (output) {
217
218     CmiStdoutCheck(CMK_PIPE_SUB);
219     if (Cmi_charmrun_fd!=-1)
220         ctrlskt_ready_read = CMK_PIPE_CHECKREAD(Cmi_charmrun_fd);
221     if (dataskt!=-1) {
222       for (i=0; i<CmiNumNodes(); i++)
223       {
224         if (i == CmiMyNode()) continue;
225         if (nodes[i].send_queue_h) {
226           sockWriteStates[i] = CMK_PIPE_CHECKWRITE(nodes[i].sock);
227           if (sockWriteStates[i]) dataskt_ready_write = 1;
228           /* sockWriteStates[i] = dataskt_ready_write = 1; */
229         }
230         else
231           sockWriteStates[i] = 0;
232         sockReadStates[i] = CMK_PIPE_CHECKREAD(nodes[i].sock);
233         if (sockReadStates[i])  dataskt_ready_read = 1;
234       }
235     }
236   }
237   MACHSTATE(1,"} CheckSocketsReady")
238   return nreadable;
239 }
240
241 /***********************************************************************
242  * CommunicationServer()
243  * 
244  * This function does the scheduling of the tasks related to the
245  * message sends and receives. 
246  * It first check the charmrun port for message, and poll the gm event
247  * for send complete and outcoming messages.
248  *
249  ***********************************************************************/
250
251 static void CommunicationServerNet(int sleepTime, int where)
252 {
253   unsigned int nTimes=0; /* Loop counter */
254   CmiCommLockOrElse({
255     MACHSTATE(4,"Attempted to re-enter comm. server!") 
256     return;
257   });
258   LOG(GetClock(), Cmi_nodestart, 'I', 0, 0);
259   MACHSTATE2(sleepTime?3:2,"CommunicationsServer(%d,%d) {",
260              sleepTime,writeableAcks||writeableDgrams)  
261 #if CMK_SMP
262   if (sleepTime!=0) {/*Sleep *without* holding the comm. lock*/
263     MACHSTATE(2,"CommServer going to sleep (NO LOCK)");
264     if (CheckSocketsReady(sleepTime, 0)<=0) {
265       MACHSTATE(2,"CommServer finished without anything happening.");
266     }
267   }
268   sleepTime=0;
269 #endif
270   CmiCommLock();
271   /* in netpoll mode, only perform service to stdout */
272   if (Cmi_netpoll && where == COMM_SERVER_FROM_INTERRUPT) {
273     if (CmiStdoutNeedsService()) {CmiStdoutService();}
274     CmiCommUnlock();
275     return;
276   }
277   CommunicationsClock();
278   /*Don't sleep if a signal has stored messages for us*/
279   if (sleepTime&&CmiGetState()->idle.hasMessages) sleepTime=0;
280   while (CheckSocketsReady(sleepTime, 1)>0) {
281     int again=0;
282     sleepTime=0;
283     CmiCheckSocks();
284     if (ctrlskt_ready_read) {again=1;ctrl_getone();}
285     if (dataskt_ready_read || dataskt_ready_write) {again=1;}
286     if (CmiStdoutNeedsService()) {CmiStdoutService();}
287     if (!again) break; /* Nothing more to do */
288     if ((nTimes++ &16)==15) {
289       /*We just grabbed a whole pile of packets-- try to retire a few*/
290       CommunicationsClock();
291       break;
292     }
293   }
294   CmiCommUnlock();
295
296   /* when called by communication thread or in interrupt */
297   if (where == COMM_SERVER_FROM_SMP || where == COMM_SERVER_FROM_INTERRUPT)
298   {
299 #if CMK_IMMEDIATE_MSG
300   CmiHandleImmediate();
301 #endif
302   }
303
304   MACHSTATE(2,"} CommunicationServerNet") 
305 }
306
307
308 #if FRAGMENTATION
309 /* keep one buffer of PACKET_MAX size to ensure copy free operation 
310    1. for short message that is less than PACKET_MAX, 
311       buffer of that size is allocated and directly pass up
312    2. for long messages,
313       for first packet, buffer of PACKET_MAX is allocated and can be reused
314       as recv buffer, asm_msg of actual message size.
315       for afterwards packets, recv buffer will not allocated and the real 
316       message is used as recv buffer
317 */
318 static char * maxbuf = NULL;
319
320 static char * getMaxBuf() {
321   char *buf;
322   if (maxbuf == NULL)
323     buf = (char *)CmiAlloc(PACKET_MAX);
324   else {
325     buf = maxbuf; 
326     maxbuf = NULL;
327   }
328   return buf;
329 }
330
331 static void freeMaxBuf(char *buf) {
332   if (maxbuf) CmiFree(buf);
333   else maxbuf = buf;
334 }
335
336 #endif
337
338 static void IntegrateMessageDatagram(char **msg, int len)
339 {
340   char *newmsg;
341   int rank, srcpe, seqno, magic, broot, i;
342   int size;
343   
344   if (len >= DGRAM_HEADER_SIZE) {
345     DgramHeaderBreak(*msg, rank, srcpe, magic, seqno, broot);
346     if (magic == (Cmi_charmrun_pid&DGRAM_MAGIC_MASK)) {
347       OtherNode node = nodes_by_pe[srcpe];
348       newmsg = node->asm_msg;
349       if (newmsg == NULL) {
350         size = CmiMsgHeaderGetLength(*msg);
351         if (size < len) KillEveryoneCode(4559312);
352 #if FRAGMENTATION
353         if (size == len) {              /* whole message in one packet */
354           newmsg = *msg;                /* directly use the buffer */
355         }
356         else {
357           newmsg = (char *)CmiAlloc(size);
358           if (!newmsg)
359             fprintf(stderr, "%d: Out of mem\n", _Cmi_mynode);
360           memcpy(newmsg, *msg, len);
361           if (len == PACKET_MAX) 
362               freeMaxBuf(*msg);         /* free buffer, must be max size */
363           else 
364               CmiFree(*msg);
365         }
366 #else
367         newmsg = *msg;
368 #endif
369         node->asm_rank = rank;
370         node->asm_total = size;
371         node->asm_fill = len;
372         node->asm_msg = newmsg;
373       } else {
374 #if ! FRAGMENTATION
375         CmiAssert(0);
376 #else
377         size = len - DGRAM_HEADER_SIZE;
378         memcpy(newmsg + node->asm_fill, (*msg)+DGRAM_HEADER_SIZE, size);
379         node->asm_fill += size;
380         if (len == PACKET_MAX) 
381               freeMaxBuf(*msg);         /* free buffer, must be max size */
382         else 
383               CmiFree(*msg);
384 #endif
385       }
386       if (node->asm_fill > node->asm_total)
387          CmiAbort("\n\n\t\tLength mismatch!!\n\n");
388       if (node->asm_fill == node->asm_total) {
389         /* do it at integration - the following function may re-entrant */
390 #if CMK_BROADCAST_SPANNING_TREE
391         if (rank == DGRAM_BROADCAST
392 #if CMK_NODE_QUEUE_AVAILABLE
393           || rank == DGRAM_NODEBROADCAST
394 #endif
395            )
396           SendSpanningChildren(NULL, 0, node->asm_total, newmsg, broot, rank);
397 #elif CMK_BROADCAST_HYPERCUBE
398         if (rank == DGRAM_BROADCAST
399 #if CMK_NODE_QUEUE_AVAILABLE
400           || rank == DGRAM_NODEBROADCAST
401 #endif
402            )
403           SendHypercube(NULL, 0, node->asm_total, newmsg, broot, rank);
404 #endif
405         if (rank == DGRAM_BROADCAST) {
406           for (i=1; i<_Cmi_mynodesize; i++)
407             CmiPushPE(i, CopyMsg(newmsg, node->asm_total));
408           CmiPushPE(0, newmsg);
409         } else {
410 #if CMK_NODE_QUEUE_AVAILABLE
411            if (rank==DGRAM_NODEMESSAGE || rank==DGRAM_NODEBROADCAST) {
412              CmiPushNode(newmsg);
413            }
414            else
415 #endif
416              CmiPushPE(rank, newmsg);
417         }
418         node->asm_msg = 0;
419       }
420     } 
421     else {
422       CmiPrintf("message ignored1: magic not agree:%d != %d!\n", magic, Cmi_charmrun_pid&DGRAM_MAGIC_MASK);
423       CmiPrintf("recv: rank:%d src:%d mag:%d\n", rank, srcpe, magic);
424     }
425   } 
426   else CmiPrintf("message ignored2!\n");
427 }
428
429
430 void ReceiveDatagram(int node)
431 {
432   static char *buf = NULL;
433   int size;
434   DgramHeader *head, temp;
435   int newmsg = 0;
436
437   OtherNode nodeptr = &nodes[node];
438
439   SOCKET fd = nodeptr->sock;
440   if (-1 == skt_recvN(fd, &size, sizeof(int)))
441     KillEveryoneCode(4559318);
442
443 #if FRAGMENTATION
444   if (size == PACKET_MAX)
445       buf = getMaxBuf();
446   else
447       buf = (char *)CmiAlloc(size);
448 #if 0
449    /* buggy code */
450   CmiAssert(size<=PACKET_MAX);
451   if (nodeptr->asm_msg == NULL) {
452     if (size == PACKET_MAX)
453       buf = getMaxBuf();
454     else
455       buf = (char *)CmiAlloc(size);
456   }
457   else {
458       /* this is not the first packet of a message */
459     CmiAssert(nodeptr->asm_fill+size-DGRAM_HEADER_SIZE <= nodeptr->asm_total);
460       /* find the dgram header start and save the header to temp */
461     buf = (char*)nodeptr->asm_msg + nodeptr->asm_fill - DGRAM_HEADER_SIZE;
462     head = (DgramHeader *)buf;
463     temp = *head;
464     newmsg = 1;
465   }
466 #endif
467 #else
468   buf = (char *)CmiAlloc(size);
469 #endif
470
471   if (-1==skt_recvN(fd, buf, size))
472     KillEveryoneCode(4559319);
473
474   IntegrateMessageDatagram(&buf, size);
475
476 #if FRAGMENTATION
477     /* restore header */
478   if (newmsg) *head = temp;
479 #endif
480
481 }
482
483
484 /***********************************************************************
485  * DeliverViaNetwork()
486  *
487  * This function is responsible for all non-local transmission. It
488  * first allocate a send token, if fails, put the send message to
489  * penging message queue, otherwise invoke the GM send.
490  ***********************************************************************/
491
492 int TransmitImplicitDgram(ImplicitDgram dg)
493 {
494   ChMessageHeader msg;
495   char *data; DgramHeader *head; int len; DgramHeader temp;
496   OtherNode dest;
497   int retval;
498   
499   MACHSTATE2(2,"  TransmitImplicitDgram (%d bytes) [%d]",dg->datalen,dg->seqno)
500   len = dg->datalen+DGRAM_HEADER_SIZE;
501   data = dg->dataptr;
502   head = (DgramHeader *)(data - DGRAM_HEADER_SIZE);
503   temp = *head;
504   dest = dg->dest;
505   /* first int is len of the packet */
506   DgramHeaderMake(head, dg->rank, dg->srcpe, Cmi_charmrun_pid, len, dg->broot);
507   LOG(Cmi_clock, Cmi_nodestart, 'T', dest->nodestart, dg->seqno);
508   /*
509   ChMessageHeader_new("data", len, &msg);
510   if (-1==skt_sendN(dest->sock,(const char *)&msg,sizeof(msg))) 
511     CmiAbort("EnqueueOutgoingDgram"); 
512   if (-1==skt_sendN(dest->sock,head,len))
513     CmiAbort("EnqueueOutgoingDgram"); 
514   */
515   if (-1==skt_sendN(dest->sock,(const char *)&len,sizeof(len))) 
516     CmiAbort("EnqueueOutgoingDgram"); 
517   if (-1==skt_sendN(dest->sock,(const char *)head,len)) 
518     CmiAbort("EnqueueOutgoingDgram"); 
519     
520   *head = temp;
521   dest->stat_send_pkt++;
522   return 1;
523 }
524
525 int TransmitDatagram(int pe)
526 {
527   ImplicitDgram dg; OtherNode node;
528   int count;
529   unsigned int seqno;
530   
531   node = nodes+pe;
532   dg = node->send_queue_h;
533   if (dg) {
534     if (TransmitImplicitDgram(dg)) {
535       node->send_queue_h = dg->next;
536       if (node->send_queue_h == NULL) node->send_queue_t = NULL;
537       DiscardImplicitDgram(dg);
538     }
539   }
540   return 0;
541 }
542
543 void EnqueueOutgoingDgram
544         (OutgoingMsg ogm, char *ptr, int len, OtherNode node, int rank, int broot)
545 {
546   int seqno, dst, src; ImplicitDgram dg;
547   src = ogm->src;
548   dst = ogm->dst;
549   seqno = node->send_next;
550   node->send_next = ((seqno+1)&DGRAM_SEQNO_MASK);
551   MallocImplicitDgram(dg);
552   dg->dest = node;
553   dg->srcpe = src;
554   dg->rank = rank;
555   dg->seqno = seqno;
556   dg->broot = broot;
557   dg->dataptr = ptr;
558   dg->datalen = len;
559   dg->ogm = ogm;
560   ogm->refcount++;
561   dg->next = 0;
562   if (node->send_queue_h == 0) {
563     node->send_queue_h = dg;
564     node->send_queue_t = dg;
565   } else {
566     node->send_queue_t->next = dg;
567     node->send_queue_t = dg;
568   }
569 }
570
571 /* ignore copy, because it is safe to reuse the msg buffer after send */
572 void DeliverViaNetwork(OutgoingMsg ogm, OtherNode node, int rank, unsigned int broot, int copy)
573 {
574   int size; char *data;
575
576 /*CmiPrintf("DeliverViaNetwork to %d\n", node->nodestart);*/
577 /*CmiPrintf("send time: %fus\n", (CmiWallTimer()-t)*1.0e6); */
578  
579   size = ogm->size - DGRAM_HEADER_SIZE;
580   data = ogm->data + DGRAM_HEADER_SIZE;
581   while (size > Cmi_dgram_max_data) {
582     EnqueueOutgoingDgram(ogm, data, Cmi_dgram_max_data, node, rank, broot);
583     data += Cmi_dgram_max_data;
584     size -= Cmi_dgram_max_data;
585   }
586   EnqueueOutgoingDgram(ogm, data, size, node, rank, broot);
587 }
588
589 /***********************************************************************
590  * CmiMachineInit()
591  *
592  * This function intialize the GM board. Set receive buffer
593  *
594  ***********************************************************************/
595
596 void CmiMachineInit(char **argv)
597 {
598 #if FRAGMENTATION
599   Cmi_dgram_max_data = PACKET_MAX - DGRAM_HEADER_SIZE; 
600 #else
601   Cmi_dgram_max_data = PACKET_MAX;
602 #endif
603 }
604
605 void MachineExit()
606 {
607 }
608
609 static void open_tcp_sockets()
610 {
611   int i, ok, pe, flag;
612   int mype, numpes;
613   SOCKET skt;
614   int val;
615
616   mype = _Cmi_mynode;
617   numpes = _Cmi_numnodes;
618   MACHSTATE2(2,"  open_tcp_sockets (%d:%d)", mype, numpes);
619   for (i=0; i<mype; i++) {
620     unsigned int clientPort;
621     skt_ip_t clientIP;
622     skt = skt_accept(dataskt, &clientIP,&clientPort);
623     if (skt<0) KillEveryoneCode(98246554);
624 #if NO_NAGLE_ALG
625     skt_tcp_no_nagle(skt);
626 #endif
627     ok = skt_recvN(skt, &pe, sizeof(int));
628     if (ok<0) KillEveryoneCode(98246556);
629     nodes[pe].sock = skt;
630 #if FRAGMENTATION
631     skt_setSockBuf(skt, PACKET_MAX*4);
632 #endif
633 #if 0
634 #if !defined(_WIN32) || defined(__CYGWIN__)
635     if ((val = fcntl(skt, F_GETFL, 0)) < 0) KillEveryoneCode(98246557);
636     if (fcntl(skt, F_SETFL, val|O_NONBLOCK) < 0) KillEveryoneCode(98246558);
637 #endif
638 #endif
639   }
640   for (pe=mype+1; pe<numpes; pe++) {
641     skt = skt_connect(nodes[pe].IP, nodes[pe].dataport, 300);
642     if (skt<0) KillEveryoneCode(894788843);
643 #if NO_NAGLE_ALG
644     skt_tcp_no_nagle(skt);
645 #endif
646     ok = skt_sendN(skt, &mype, sizeof(int));
647     if (ok<0) KillEveryoneCode(98246556);
648     nodes[pe].sock = skt;
649 #if FRAGMENTATION
650     skt_setSockBuf(skt, PACKET_MAX*4);
651 #endif
652 #if 0
653 #if !defined(_WIN32) || defined(__CYGWIN__)
654     if ((val = fcntl(skt, F_GETFL, 0)) < 0) KillEveryoneCode(98246557);
655     if (fcntl(skt, F_SETFL, val|O_NONBLOCK) < 0) KillEveryoneCode(98246558);
656 #endif
657 #endif
658   }
659 }
660
661 void CmiCommunicationInit(char **argv)
662 {
663   open_tcp_sockets();
664 }
665
666 /*@}*/