Einheri
Einheri

Reputation: 985

TCP server using select()

I need to write a TCP server that can handle multiple connections; I followed this guide and wrote up the following program:

static void _handle_requests(char* cmd,int sessionfd){
    //TODO: extend
    printf("RECEIVED: %s\n",cmd);
    if (!strcmp(cmd,BAR)){
        barrier_hit(&nodebar,sessionfd);

    }else if (!strcmp(cmd, BYE)){


    }else if (!strcmp(cmd, HI)){

    }
}

void handle_requests(void){
    listen(in_sock_fd,QUEUELEN);
    fd_set read_set, active_set;
    FD_ZERO(&active_set);
    FD_SET(in_sock_fd, &active_set);
    int numfd = 0;
    char cmd[INBUFLEN];
    for (;;){
        read_set = active_set;

        numfd = select(FD_SETSIZE,&read_set,NULL,NULL,NULL);
        for (int i = 0;i < FD_SETSIZE; ++i){
            if (FD_ISSET(i,&read_set)){ 
                if (i == in_sock_fd){
                    //new connection
                    struct sockaddr_in cliaddr;
                    socklen_t socklen = sizeof cliaddr;
                    int newfd = accept(in_sock_fd,(struct sockaddr*)&cliaddr, &socklen);
                    FD_SET(newfd,&active_set);  
                }else{
                    //already active connection
                    read(i,cmd,INBUFLEN);
                    _handle_requests(cmd,i);
                }
            }


        }
    }
}

..and a single client that connect() to the server and does two consecutive write() calls to the socket file descriptor.

n = write(sm_sockfd, "hi", 3);

    if (n < 0) {
        perror("SM: ERROR writing to socket");
        return 1;
    }

//...later

 n = write(sm_sockfd, "barrier", 8);


    if (n < 0) {
        perror("SM: 'barrier msg' failed");
        exit(1);
    }

The thing is, the server only picks up the first message ("hi"); afterwards, the select call hangs. Since the write ("barrier") on the client's end succeeded, shouldn't that session file descriptor be ready for reading? Have I made any glaring mistakes?

Thanks; sorry if this is something obvious, I'm completely unfamiliar with C's networking library, and the project is due very soon!

Upvotes: 0

Views: 2964

Answers (2)

juhist
juhist

Reputation: 4314

You have a misunderstanding of how TCP sockets work. There is no message boundary in TCP, i.e. if you send first "hi" and then "barrier", you can't expect the corresponding receives to return "hi" and "barrier". It's possible that they return "hibarrier". It's also in theory possible (although very rare) that they would return "h", "i", "b", "a", "r", "r", "i", "e", "r".

You really need to consider how you delimit your messages. One possibility is to send the length of a message as 32-bit integer in network byte order (4 bytes) prior to the message. Then when you receive the message, you first read 4 bytes and then read as many bytes as the message length indicates.

Do note that TCP may return partial reads, so you need to somehow handle those. One possibility is to have a buffer which holds the bytes read, and then append to this buffer when more bytes are read and handle the contents of the buffer when the first four bytes of the buffer (i.e. the message length) indicate that you have the full message.

If you want a sequential packet protocol that preserves packet boundaries, you may want to consider SCTP. However, it's not widely supported by operating system kernels currently so what I would do is the 32-bit length trick to have a packet-oriented layer on top of TCP.

Upvotes: 3

Philip Stuyck
Philip Stuyck

Reputation: 7447

Do this :

int nbrRead = read(i,cmd,INBUFLEN);

and print out the value of nbrRead. You will see that you received everything in one go. TCP is a streaming protocol, if you do 3 or more sequential sends the chance is very high that you will receive them all at once.

Also make sure that INBUFLEN is large enough 2048 will be more than enough for your example.

Upvotes: 0

Related Questions