Other programming hints

Protocol/Address family

Generally with servers PF_INET6 should be used to accept both IPv4 and IPv6 connections but on client it is useful to use PF_UNSPEC and let the client application select the appropriate one – otherwise IPv4 address resolution usually fails (no minus for this).

fgets()

With fgets() you can specify how many bytes are read and it will not exceed this limit, however, if the given text is longer than the value you specify, it will return only that amount. When reading from standard input it will keep the rest that was read in the buffers. Remember that it reads until newline marker is reached or limit-1 bytes are read (or EOF is detected).

#define BSIZE 512
...
char buffer[BSIZE] = {0};
if(fgets(buffer, BSIZE, stdin) != NULL) {
  if (strlen(buffer) > BSIZE)) printf("this is never reached!\n");
  else printf("If there is a newline at the end, there is nothing more in the buffers (all read)\n"); 
}

You should check if there is a newline at the end (and the length is BSIZE-1 as in example), if there is not you can read the rest and concatenate the result for instance. This is because the select() will not detect the data in the stdio buffers (out of POSIX) and you'll have to check for it.

recvfrom() behavior

Note that if you have incoming more data than you can receive in your buffer the recvfrom() function will discard the rest. With basic text protocols this can be used to limit the incoming data amount to the server. But in other cases this might result in data loss. This is the reason why there is a usage for length variable in UDP protocol packets, too. You cannot recover that remaining data but you can at least check if we got all.

And also, there is no partial receiving/sending with UDP. It has strict packet boundaries. One write/read = one protocol packet.

Endianess

Remember that the network order concerns ONLY the multi byte variables, i.e. variables which are longer than 1 byte. One 8bit integer is the same length as one character, which is not expected to be in any other form. With 16/32/64bit integers for instance the byte ordering (big endian - network order vs. little endian - e.g. in linux) is significant as the order of bytes defines the value of the variable and if the other end is not using the same byte order as the sender - the received value is interpreted as different value as the sender intended. This is the reason network order is defined to follow a certain byte order: big endian. With htons/ntohs/htonl/ntohl() you can change the byte order of your variables, see the manual pages of these functions.

E.g. if you have a value 1001 in a 16bit integer, the binary form in little endian would be:

11101001  00000011

And in big endian (network order after htons()):

00000011 11101001

Which are the same number but with different endianess.

If you use only bit shifting to get the bytes of, e.g. 16bit integer into two 8bit integers, it restricts the usage of the application as all applications should then do the reverse operations based on system architecture (endianess), especially if you are not converting the integers into network order before the bit shifting. If some other applications following the same protocol handle the protocol packets differently and expect the, e.g. 16bit integer to be in network order and use internal functions to convert it to host endianess there will be problems as the order of bytes might change. Especially when endianess is different on sender and receiver.

Following the protocol

When the protocol is defined:

8 bit integer x char data
6 data
char buffer[255];
memset(&buffer,0,255);
uint8_t ppid = 6;
int position = 0;
 
//No endianess conversion as dealing with one byte
*(uint8_t*)&buffer[0] = ppid; 
//Copy some data from 'inputdata' after the integer
position += sizeof(ppid);
memcpy(&buffer[position],inputdata,200);
16 bit integer x char data
6
char buffer[255];
memset(&buffer,0,255);
uint16_t ppid = 6;
int position = 0;
 
//Convert 16 bit integer into network order
*(uint16_t*)&buffer[0] = htons(ppid); 
//Copy some data from 'inputdata' after the integer
position += sizeof(ppid);
memcpy(&buffer[position],inputdata,200);
char (id) x char data
6
char buffer[255];
memset(&buffer,0,255);
char ppid = '6';
int position = 0;
 
//Just put the character into buffer
buffer[0] = ppid;
//Copy some data from 'inputdata' after the character
position += sizeof(ppid);
memcpy(&buffer[position],inputdata,200);
 
//or with snprintf
snprintf(buffer,255,"%u%s",ppid,inputdata);

One major difference here is that when e.g. value 156 has to be sent over network, it takes:

  • 1 byte as unsigned 8bit integer
  • 2 bytes as unsigned 16bit integer
  • 3 bytes when sent as character digits

In UDP one write to socket, e.g. with sendto() equals one UDP packet! If you try to write a packet with, e.g. 3 parameters with separate calls to sendto() it will result in 3 different packets sent to the receiver. If the protocol states that one message consists of these three parameters then you are not following the protocol.

Remember the connection between characters and 8bit integers, test the following:

char buf[15];
memset(&buf,0,15);
uint8_t value_of_ASCII_A = 65; 
for(uint8_t i = 0; i < 15; i++) buf[i] = value_of_ASCII_A + i;
printf("buf: %s\n",buf);

System calls and networking functions

It would be a good way to always check and react to the return value of system calls (e.g. select() socket() bind() … etc.) as they might not always succeed. Especially with proper usage of select() as the return value tells whether there are errors, timeout or data on the reported amount of sockets.

With networking functions such as sendto(), send(), etc. it is important to always check the return value. It is the most reliable way to get the information about how many bytes were received and stored to the buffer.

Another thing is that most of the system calls are blocking (see manual pages to check this on the calls you're using). This means that the execution of code continues after the called function gets some event (e.g. recvfrom() gets incoming data).


CT30A5002 - Games and Networking