I'm working on a server/client application in C
Actually I'm trying to allow the server to accept new clients and receiving data at (almost) the same time. I send data two times,the first time I send a login and it works.
The second times I send some string data and it's like the client send them again and again but I've checked and they're sent only once.
Can someone please help me ?
I use gcc -Wall -pedantic to compile them.
Here is the client code : An argument is needed and it can be any text
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#define PATH "soquette"
#define BACKLOG 2
#define TAILLE_BUFFER 256
#define TIME_SLEEP 10
int main(int argc,char ** argv){
if(argc == 2){
struct sockaddr_un addr;
int serveur_socket;
ssize_t taille_lue;
char buffer[TAILLE_BUFFER];
char * buffer2;
if((serveur_socket = socket(PF_UNIX,SOCK_STREAM,0))<0){
perror("socket \n");
exit(EXIT_FAILURE);
}
memset(&addr,0,sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path,PATH,sizeof(addr.sun_path)-1);
if(connect(serveur_socket,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))<0){
perror("connect \n");
exit(EXIT_FAILURE);
}
printf("pseudo %s \n",argv[1]);
if(write(serveur_socket,argv[1],strlen(argv[1])*sizeof(char))<0){
perror("1 st write \n");
exit(EXIT_FAILURE);
}
sleep(5);
taille_lue = read(STDIN_FILENO,buffer,TAILLE_BUFFER);
buffer2 = malloc(sizeof(int) + taille_lue * sizeof(char));
sprintf(buffer2,"%ld",taille_lue);
strcat(buffer2,buffer);
if(write(serveur_socket,buffer2,sizeof(buffer2))<0){
perror("write \n");
exit(EXIT_FAILURE);
}
printf("message envoyé %s \n",buffer2);
free(buffer2);
exit(EXIT_SUCCESS);
}
else{
printf("bad arguments number \n");
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
And here is the server side.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#define PATH "soquette"
#define NB_MAX_CONNECTION 10
#define TAILLE_BUFFER 256
#define NB_BOUCLE 10
#define TIME_WAIT 10
int socket_server;
void signal_handler(){
printf("signal handler \n");
if(close(socket_server)==-1){
perror("close \n");
}
if(unlink(PATH)==-1){
perror("unlink \n");
}
exit(EXIT_SUCCESS);
}
int main(){
int i,retval,j,fd_max,new_fd;
ssize_t taille_recue;
struct sockaddr_un addr;
char buffer[TAILLE_BUFFER];
struct timeval tv;
fd_set rfds,active_fd_set;
if(signal(SIGINT,signal_handler)==SIG_ERR){
perror("signal \n");
}
tv.tv_sec=TIME_WAIT;
tv.tv_usec=0;
FD_ZERO(&rfds);
FD_ZERO(&active_fd_set);
printf("server launch \n");
if((socket_server = socket(PF_UNIX,SOCK_STREAM,0))<0){
perror("socket \n");
exit(EXIT_FAILURE);
}
memset(&addr,0,sizeof(struct sockaddr_un));
addr.sun_family = PF_UNIX;
strncpy(addr.sun_path,PATH,sizeof(addr.sun_path)-1);
if((bind(socket_server,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))==-1)){
perror("bind \n");
exit(EXIT_FAILURE);
}
if(listen(socket_server,NB_MAX_CONNECTION)==-1){
perror("listen \n");
exit(EXIT_FAILURE);
}
FD_SET(socket_server,&active_fd_set);
fd_max = socket_server;
for(i=0;i<NB_BOUCLE;i++){
FD_ZERO(&rfds);
rfds = active_fd_set;
printf("tour number %d \n",i);
if((retval = select(fd_max+1,&rfds,NULL,NULL,&tv))<0){
perror("select \n");
}
for(j=0;j<=fd_max;j++){
if(FD_ISSET(j,&rfds)){
if(j == socket_server){
if((new_fd = accept(socket_server,NULL,NULL))<0){
perror("accept \n");
signal_handler();
exit(EXIT_FAILURE);
}
printf("new client \n");
FD_SET(new_fd,&active_fd_set);
if(read(new_fd,buffer,TAILLE_BUFFER)<0){
perror("read 1\n");
}
else{
printf("read from buffer %s \n",buffer);
fd_max = new_fd;
}
}
else{
printf("client already in the list \n");
if((taille_recue = read(j,buffer,sizeof(int)))<0){
if(taille_recue == 0){
close(j);
FD_CLR(j,&rfds);
}
else{
signal_handler();
perror("read server 2 \n");
exit(EXIT_FAILURE);
}
}
else{
printf("read from buffer %s \n",buffer);
FD_CLR(j,&rfds);
}
}
}
}
}
printf("fermeture du serveur \n");
close(socket_server);
unlink(PATH);
exit(EXIT_SUCCESS);
}
Here is the client output
/client 1
pseudo 1
salut
message envoyé 6salut
/0
and here is the server output
MacBook-Pro-de-Kevin:tp10 kevin$ ./server
server launch
tour number 0
new client
read from buffer 1
tour number 1
client already in the list
read from buffer 6sal
tour number 2
client already in the list
read from buffer ut
/
tour number 3
client already in the list
read from buffer ut
/
tour number 4
client already in the list
read from buffer ut
/
tour number 5
client already in the list
read from buffer ut
/
tour number 6
client already in the list
read from buffer ut
/
tour number 7
client already in the list
read from buffer ut
/
tour number 8
client already in the list
read from buffer ut
/
tour number 9
client already in the list
read from buffer ut
/
fermeture du serveur
In the first place, when it accepts a new connection, the server immediately tries to read data from the socket. There may be no data available at that point, so the read can block. Although that does not explain the problem you asked about, it conflicts with your objective.
The server assumes that the fd of any newly-accepted connection must be the maximum fd in the set. Although it is not impacting you yet, that assumption is not safe. File descriptors are freed up and made available for reuse when they are closed.
The server does not update fd_max
when it closes a connection. However, although this may result in subsequent select()
calls not conforming strictly to that function's specification, it probably does not cause any actual misbehavior.
You appear to assume that the client's write()
calls always write the full number of bytes specified to them, and that up to all bytes written will be read by the the server's next read()
. These assumptions are not safe in general, though you do have a good chance of them being met for unix-domain sockets. In general, for both read()
and write()
you must consider the return value not only to spot errors / end-of-file, but also to ensure that all expected bytes are written / read. You must be prepared to loop in order to transfer all needed bytes.
In a select()
-based scenario, the looping described in the previous point needs to be via the select()
loop, else you likely introduce blocking. You may therefore need to do per-connection accounting of how many more bytes you expect to read / write at any given time. Indeed, unless your server does nothing but shuffle bytes from sources to sinks as fast as it can, it very likely will need to maintain some per-connection state.
it's odd that for an established connection, you attempt to read only the number of bytes in an int
on any given read, when in fact more bytes than that may be available and the buffer can accommodate more. That's here:
if((taille_recue = read(j,buffer,sizeof(int)))<0){
Now consider the above quoted line carefully: only when read()
returns a negative value is the if
block executed. In particular, that block is not executed when read()
returns 0
to indicate end-of-file, but it is in that block, not the else
block, where you test for the end-of-file condition. This is what causes the behavior you asked about. An open file positioned at EOF is always ready to read, but you mishandle the EOF signal from the read()
, treating it as if data had been read instead of recognizing it for what it is.
Additionally, if you want to print the buffer contents via printf()
and a %s
field descriptor then you must be certain either to insert a null character ('\0'
) into the buffer after the valid data, or to use a maximum field width that limits the output to the number of valid bytes in the buffer.