LinuxQuestions.org
Help answer threads with 0 replies.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 12-25-2010, 08:01 AM   #1
golden_boy615
Member
 
Registered: Dec 2008
Distribution: Ubuntu Fedora
Posts: 445

Rep: Reputation: 18
blocking and non blocking TCP send/recv problem


Hello
I have a device that is working on modbus protocol and I have written a small program(with block TCP read method ) to read its registers via modbus protocol.
my program is working very well but except those times that I unplug the Ethernet cable or turning off the modbus gateway during programs work.
at this time my program stops on recv system call (if it reach this system call exacly when I unplug Ethernet cable or turning off the modbus gateway during programs work).
I changed my source to work in nonblock TCP method, at this time with the same situation my program does not stop/block on recv system call but after pluging back the Ethernet cable or resuming the connectivity situation back it reads data incorrectly .

this is my code:
Quote:

#define DEBUG
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/timeb.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <termios.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <time.h>

#define MAX_BUFFER_SIZE 256
#define SLAVE_TCP_ADDRESS "192.168.8.133"
#define TCP_PORT 502




#define MODBUS_DEFS_H



typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;

#define SEND_ERROR -200
#define RECIEVE_ERROR -210
#define PA_LENGTH_ERROR -220
#define WAIT_RECIEVE_ERROR -230

#define RA_MAX_LENGTH 256
#define ASCII_MAX_LENGTH 512
#define MAX_BUFFER_SIZE 256

#define F_REGISTERS 0x03
#define M_ERROR_FUNCTION -15
#define M_OK 0
#define ERROR_EXECPTION -10




#define GETU16( var, buf) { \
(var) = (*(buf) << 8) & 0xFF00; \
(var) |= (*((buf)+1) & 0xFF); \
}
#define PUTU16( buf, var) { \
(*(buf)) = ((var) >> 8); \
*((buf)+1) = ((var) & 0xFF); \
}









typedef struct _MBAP_Struct {
u16 TransactionIdentifier; ///< copy from server to client
u16 ProtocolIdentifier; ///< 0 for Modbus protocol
u16 Length; ///< following bytes number
u8 UnitIdentifier; ///< copy from server to client, maybe map serial address
} PStruct, *pPStruct;
int TCPClientInit( int *clientfd)
{
*clientfd = socket(PF_INET, SOCK_STREAM, 0);

return *clientfd;
}








int TCPClientConnect( const int clientfd, const char *addr, int port)
{
struct sockaddr_in dest;

/// initialize value in dest
bzero(&dest, sizeof(dest));
dest.sin_family = PF_INET;
dest.sin_port = htons( port);
inet_aton( addr, &dest.sin_addr);

/// Connecting to server
return connect(clientfd, (struct sockaddr*)&dest, sizeof(dest));
}
int TCPMakeMBAP( pPStruct mbap, u16 tid, u16 len, u8 uid)
{
mbap->TransactionIdentifier= tid;
mbap->ProtocolIdentifier= 0;
mbap->Length= len;
mbap->UnitIdentifier= uid;

return M_OK;
}

int TCPReadRegisters(int fd, u16 startreg, u16 no, u16 *value, pPStruct mbap,struct timeval recvwait)
{
u8 adu[ ASCII_MAX_LENGTH], pdu[ MAX_BUFFER_SIZE];
int pdu_len= 0, adu_len= 0, ret= 0;
int i= 0;
u8 ret_no;
int tid= 0;

u8 adu1[ RA_MAX_LENGTH];
int adu1_len= 0;
int sentlen = 0;

fd_set readfs_set,sendfs_set; /* file descriptor set */
int maxfd;

pdu[ pdu_len]= F_REGISTERS;
pdu_len++;
PUTU16( pdu+ pdu_len, startreg); // don't put ++ in the macro

pdu_len+= 2;
PUTU16( pdu+ pdu_len, no);
pdu_len+= 2;

mbap->Length= pdu_len+ 1; // plus 1 for address

pPStruct ret_mbap= (pPStruct)malloc(sizeof(PStruct));

tid= mbap->TransactionIdentifier;
PUTU16( adu1, mbap->TransactionIdentifier);
PUTU16( adu1+ 2, mbap->ProtocolIdentifier);
PUTU16( adu1+ 4, mbap->Length);
adu1[6]= mbap->UnitIdentifier;
adu1_len=7;

bcopy( (const char *)pdu, (char *)(adu1+ adu1_len), pdu_len);
adu1_len+= pdu_len;

maxfd = MAX(fd, 0)+1;
FD_SET(fd, &sendfs_set);

if(select(maxfd, NULL, &sendfs_set, NULL, &recvwait) <= 0)
{
free(ret_mbap);
return SEND_ERROR;
}


sentlen= send( fd, adu1, adu1_len, MSG_NOSIGNAL|MSG_DONTWAIT);

if(sentlen < 0 )
{
free(ret_mbap);
return SEND_ERROR;
}

maxfd = MAX(fd, 0)+1;
FD_SET(fd, &readfs_set);
if(select(maxfd, &readfs_set, NULL, NULL, &recvwait) <= 0)
{
free(ret_mbap);
return WAIT_RECIEVE_ERROR;
}

adu_len= recv(fd, adu, RA_MAX_LENGTH, 0);

if(adu_len < 0 )
{
free(ret_mbap);
return RECIEVE_ERROR;
}

GETU16( ret_mbap->TransactionIdentifier, adu);

if( ret_mbap->TransactionIdentifier != tid)
{
adu_len= ERROR_EXECPTION;
free(ret_mbap);
return adu_len;
}

free(ret_mbap);

if((adu_len-7) < 0)
{
pdu_len=adu_len-7;
}else
{
bcopy( (const char *)(adu+ 7), (char *)pdu, adu_len- 7);
pdu_len=adu_len- 7;
}

if( pdu_len < 0)
{
pdu_len=adu_len-7;
return PA_LENGTH_ERROR;

}

if( pdu[ 0] != F_REGISTERS)
{
ret= M_ERROR_FUNCTION;
return ret;
}
else
{
ret_no= pdu[ 1];
for( i= 0; i< ret_no/2; i++)
GETU16( value[ i], ( pdu+ 2+ i*2));
return ret_no;

}

}


int main()
{
int mbtcpinit,
mbtcpinit2,
socketfd,
startreg,
reg_no=1,
connectionfail= 0,
i=0;
int readcounter=0;


struct timeb tp1,tp2;
struct timeval recvwait;

recvwait.tv_sec = 3;
recvwait.tv_usec = 0;

int ret_reg_no;
u16 ret_value[120];

pPStruct mbap= (pPStruct)malloc(sizeof(PStruct));
fprintf(stdout,"enter first register number that you want:");
scanf("%i",&startreg);
fprintf(stdout,"how many registers you want to see:");
scanf("%i",&reg_no);
mbtcpinit=0;
mbtcpinit2=0;
while(TCPClientInit( &socketfd) < 0)
{
if (mbtcpinit == 0 )
{
printf("Initialization TCP Client unsuccessful \n");
}
mbtcpinit++;
sleep(2);
if(mbtcpinit >= 10 )
{
if (mbtcpinit2 == 0 )
{
printf("Some thing wrong can not initializing TCP Client \n");
}
sleep(2);
mbtcpinit=1;
mbtcpinit2++;
if (mbtcpinit2 >= 10 )
{
mbtcpinit2=1;
}
}
}
connectionfail=0;

while(TCPClientConnect( socketfd, SLAVE_TCP_ADDRESS, TCP_PORT) < 0)
{
if(connectionfail == 0)
{
perror("TCP client connection: ");
}
connectionfail++;
}

printf("Client: Connected ... \n");
TCPMakeMBAP( mbap, 1, 1, 1);
while(1)
{
ftime(&tp1);
ret_reg_no = TCPReadRegisters(socketfd, startreg, reg_no, ret_value, mbap,recvwait);
if(ret_reg_no!=(2*reg_no))
{
printf("error");
}

ftime(&tp2);
if( ret_reg_no == (2*reg_no))
{
for ( i=0;i<ret_reg_no/2;i++)
fprintf(stdout,"%i: %i\n",i,ret_value[i]);
readcounter++;
}
}

free( mbap);
return 0;
}

the place that I have problem with is:

Quote:
maxfd = MAX(fd, 0)+1;
FD_SET(fd, &sendfs_set);

if(select(maxfd, NULL, &sendfs_set, NULL, &recvwait) <= 0)
{
free(ret_mbap);
return SEND_ERROR;
}


sentlen= send( fd, adu1, adu1_len, MSG_NOSIGNAL|MSG_DONTWAIT);

if(sentlen < 0 )
{
free(ret_mbap);
return SEND_ERROR;
}

maxfd = MAX(fd, 0)+1;
FD_SET(fd, &readfs_set);
if(select(maxfd, &readfs_set, NULL, NULL, &recvwait) <= 0)
{
free(ret_mbap);
return WAIT_RECIEVE_ERROR;
}

adu_len= recv(fd, adu, RA_MAX_LENGTH, 0);

if(adu_len < 0 )
{
free(ret_mbap);
return RECIEVE_ERROR;
}
I want my program to read the device and if the cable is unpluged or the other side goes down or some other bad thing happened during my connection my program does not stop and it returns, for example:
Quote:
sending error
or
receiving error
or
receiving time out
or
...
...
Thanks a lot for any help.

Last edited by golden_boy615; 12-25-2010 at 08:09 AM.
 
Old 12-26-2010, 05:31 PM   #2
paulsm4
LQ Guru
 
Registered: Mar 2004
Distribution: SusE 8.2
Posts: 5,863
Blog Entries: 1

Rep: Reputation: Disabled
Suggestion:

1. A blocking recv() is good.

2. Write a new function, "set_alarm()". If it goes off before your I/O completes, it will:
a) interrupt the recv()
b) permit you to retry or fail

3. Otherwise, call a second new function, "clear_alarm()", if everything's OK.
EXAMPLE (stub):
Code:
#include <signal.h>

void
handler ()
{
  fprintf (stderr, "alarm fired!\n");
}

void
set_alarm (int sec)
{
  signal (SIGALRM, handler);
  alarm (sec);
}

void
clear_alarm ()
{
  alarm (0);
  signal (SIGALRM, NULL);
}
...

void
main ()
{
  ...
  set_alarm (30);
  iret = recv (...)
  if (iret < 0)
    ...
  else
    ...
  clear_alarm ();
 
Old 12-27-2010, 12:36 AM   #3
golden_boy615
Member
 
Registered: Dec 2008
Distribution: Ubuntu Fedora
Posts: 445

Original Poster
Rep: Reputation: 18
hello
thank you for your reply.
but I don't think that using signal is a good solution for this situation I think I missing something that is related to socket programming but I don't know what it is. because as much as I know blocking or non blocking read should work fine specially when using select with them to recognize if the buffer is ready to read or write or not. But I don't know why when the code pass the select system call(that seems buffer has data to receive) it stops on recv system call, this is really strange to me.

Thanks.
 
Old 12-27-2010, 06:18 AM   #4
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
Quote:
Originally Posted by paulsm4 View Post
1. A blocking recv() is good.
Well said, especially if combined with signals, as paulsm4 so ably demonstrates.

But I never use blocking. I make all my sockets nonblocking, even the accept()ing socket. And I rely entirely on select(), as golden_boy615 is doing. The only difference is that I use one select() call for the whole program, but there's no reason that it can't be split up into two such calls as we see here.

But I see several problems. It may comfort you to know that the following is not esoteric knowledge; it's all there in the man pages, so it's all doable. :)
  1. You need to reset the timeout value on each call to select(). Upon return from select(), Linux has modified the timeout value to show how much time was left. So in TCPReadRegisters(), copy the function's timeout parameter into a local copy each time you call select(). But see the complication in point V below. (I'm using a Roman numeral V, but the number is also pointing downward on the page. Get it? Get it? Sometimes I'm so funny I kill myself.)

  2. The macro FD_SET doesn't completely initialize the set; it simply turns one bit on. And since that set is on the stack (we know this because it's a local variable), you're starting with garbage. Before each call to select(), you need to use the FD_ZERO macro before doing any FD_SET.

  3. After each call to select(), test the bit in the fd set. select() will have modified each supplied fd set so that the only bits that are on are the ones (if any) which are ready to read (possibly with end of file or error) or write (possibly with ...). If you know that the bit must be on, test it anyway and blow up with a useful message if things aren't the way you expect.

  4. Do more careful checking of the result values from send() and recv(). You already do well by checking for result less than zero, and interpreting it as an error. But zero also has a special meaning for recv(). It means that the other end has done an orderly shutdown. (send() should never return a 0, but check for it anyway, and blow up with a useful message if that happens.) Very important: The result might be greater than zero and less than what you asked for. Why? Simply because your program might want to read faster than the data is available, and attempt this before all the data is available.

    So put the recv() call into a loop. If the number of bytes you get back isn't sufficient, repeat the loop with a length which indicates how many bytes remain to be received. (And don't forget to bump your data pointer to point past the bytes you've received already.) Same for send().

    And yes, the FD_ZERO and FD_SET and select() go inside the loop also.

  5. In this loop, consider what you want to do with the timeout value. Linux nicely reduces this value to show how much time is left, so you could leave it alone. But that's not portable; other systems don't necessarily do this. So you might want to recompute that value by hand. True, the chance of you running this program on a system which isn't Linux may be low. But if that ever happens, the cost of tracking down this bug might be rather high.
Hope this helps.
 
1 members found this post helpful.
Old 12-27-2010, 01:13 PM   #5
golden_boy615
Member
 
Registered: Dec 2008
Distribution: Ubuntu Fedora
Posts: 445

Original Poster
Rep: Reputation: 18
very nice step by step answer, thank you wje_lq.
but I really did not undrestand what to do from this part as you said:
Quote:
# Very important: The result might be greater than zero and less than what you asked for. Why? Simply because your program might want to read faster than the data is available, and attempt this before all the data is available.

So put the recv() call into a loop. If the number of bytes you get back isn't sufficient, repeat the loop with a length which indicates how many bytes remain to be received. (And don't forget to bump your data pointer to point past the bytes you've received already.) Same for send().

And yes, the FD_ZERO and FD_SET and select() go inside the loop also.

# In this loop, consider what you want to do with the timeout value. Linux nicely reduces this value to show how much time is left, so you could leave it alone. But that's not portable; other systems don't necessarily do this. So you might want to recompute that value by hand. True, the chance of you running this program on a system which isn't Linux may be low. But if that ever happens, the cost of tracking down this bug might be rather high.
you know may be I am not really good at non blocking concept you meant that I have to put recv in a loop to get data each time until it receives all data but I can not imagine its algorithmwith select system call because I thought select passes when all the data is ready in buffer and it does not need to do so.
would you please give me an example about what you said or change that part that you pointed to as an example for me.



Thanks a lot.
 
Old 12-27-2010, 03:27 PM   #6
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
Quote:
Originally Posted by golden_boy615 View Post
I thought select passes when all the data is ready in buffer
How can it? There is no way to tell select() how many bytes you want in the buffer. It comes back as soon as there's even one byte to read.

So throw the whole thing in a loop until the buffer is full. Keep track of where the new data should go each time; in the first time through the loop, of course, it should be at the beginning of the buffer, and you need to increment that "new data" pointer each time you've read some data.

Also keep track of how many bytes there are to read. You'll use that in your recv() call, and exit the loop as soon as you've read as many bytes as you need to.
 
1 members found this post helpful.
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
multiple send() and recv() in one TCP connection ramyasugu Linux - Networking 0 03-17-2009 06:44 AM
problems in multithreaded send()/recv() using tcp socket arslanali Programming 2 05-26-2007 09:00 AM
recv() is not coming out of blocking after closing the socket. arunka Linux - Networking 2 06-30-2006 09:52 PM
Blocking outgoing TCP ¿F M J¿ Linux - Networking 13 09-06-2005 12:59 AM
Red Hat 9 - How do I change TCP send/recv windows? oswald21 Linux - Networking 1 07-16-2003 05:21 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 07:50 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration