Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++ networking, using select do I first have to listen() and accept()?

I'm trying to allow multiple clients to connect to a host using select. Will I have to connect each one, tell them to move to a different port, and then reconnect on a new port? Or will select allow me to connect multiple clients to the same port?

This is the client code:

    int rv;
int sockfd, numbytes;

if ((rv = getaddrinfo(hostName, hostPort, &hints, &servinfo)) != 0) {
    cout << "Could not get server address.\n";
    exit(1);
}

// loop through all the results and connect to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
        perror("Client: no socket");
        continue;
    }

    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        close(sockfd);
        perror("Client: connect");
        continue;
    }

    break;
}

if (p == NULL) {
    fprintf(stderr, "Unable to connect to server.\n");
    exit(2);
}

FD_SET(sockfd, &masterSet);

This is the server code:

        int rv = getaddrinfo(NULL, port, &hints, &res);
    int yes = 1;//Not sure what this is for, found it in Beej's
    if(rv != 0){
            cout<< "Error, nothing matches criteria for file descriptor.\n";
            exit(1);
    }
    int fdInit;
    for(temp = res; temp != NULL; temp = temp->ai_next){
            if((fdInit = socket(temp->ai_family, temp->ai_socktype, temp->ai_protocol)) == -1){
                    cout << "This is not the fd you're looking for.  Move along.\n";
                    continue; //This is not the fd you're looking for, move along.
            }

            if(setsockopt(fdInit, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
                    cout << "Doom has fallen upon this set socket.\n";
                    perror("setsockopt");
                    exit(1); //Unable to set socket, exit program with code 1
            }

            if(bind(fdInit, temp->ai_addr, temp->ai_addrlen) == -1){
                    cout << "Could not bind fd\n";
                    close(fdInit);
                    continue; //Could not bind fd, continue looking for valid fd
            }
            break; //If a valid fd has been found, stop checking the list
    }
    if(temp==NULL){
            cout<<"Server failed to bind a socket\n";
            exit(2);
    }

    cout << fdInit << endl;
    //Setup the file descriptor for initial connections on specified port
    freeaddrinfo(res);
    FD_SET(fdInit, &masterSet);

Any help would be excellent! Thanks.

like image 225
Opieus Avatar asked Feb 17 '10 21:02

Opieus


People also ask

What does accept () do in C?

The accept() function shall extract the first connection on the queue of pending connections, create a new socket with the same socket type protocol and address family as the specified socket, and allocate a new file descriptor for that socket.

What is accept () in networking?

General description. The accept() call is used by a server to accept a connection request from a client.

Why does the accept () return a new socket for each accepted connection?

Because the initial socket is used to wait for communication while the second is used to communicate.

How do you use Select in socket programming?

You can use the select() call to pass a bit set containing the socket descriptors for the sockets you want checked. The bit set is fixed in size using one bit for every possible socket. Use the nfds parameter to force select() to check only a subset of the allocated socket bit set.


1 Answers

TCP connections are identified by the IP address and port number of both ends of the connection. So it's fine to have lots of clients (which will generally have randomly assigned port numbers) to connect to a single server port.

You create a socket and bind() it to a port on which to listen(), and then wait for clients to come knocking on it. If you don't mind blocking you can just call accept() on it directly, but you won't get to do any timeout looping or anything. Otherwise you can select() on the listening socket, which will become readable when a client is attempting to connect, and then call accept().

accept() will return a newly created socket, which is the actual socket to talk to the client on. The original listening socket continues to listen, and more connections can be accepted on it.

It's typical to use a select() loop to look for readability on the listening socket and any of the connected sockets. Then when select() returns you simply check whether the listening socket was readable, and if so, accept(); otherwise look for a readable connected socket and handle it.

fd_set fds;
int max = 0, reuse = 1;
struct timeval tv;
int server;
std::vector<int> connected;

// create server listening socket
server = socket(PF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)); // optional, but recommended
if (bind(server, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
    // error, could not bind server socket
}
if (listen(server, 8) < 0) {
    // error, could not listen on server port
}

// loop looking for connections / data to handle
while (running) {
    FD_ZERO(&fds);
    FD_SET(server, &fds);
    if (server >= max) max = server + 1;

    for (std::vector<int>::iterator it = connected.begin(); it != connected.end(); ++it) {
        FD_SET(*it, &fds);
        if (*it >= max) max = *it + 1;
    }

    tv.tv_sec = 2; tv.tv_usec = 0;
    if (select(max, &fds, NULL, NULL, &tv) > 0) {
        // something is readable
        if (FD_ISSET(server, &fds)) {
            // it's the listener
            connected.push_back(accept(server, (struct sockaddr *)&addr));
        }
        for (std::vector<int>::iterator it = connected.begin(); it != connected.end(); ++it) {
            if (FD_ISSET(*it, &fds)) {
                // handle data on this connection
            }
        }
    }
}
like image 131
Kieron Avatar answered Sep 20 '22 13:09

Kieron