/***************************************************************************/ /* Client-side application */ /***************************************************************************/ #include #include #include #include #include using namespace std; #define PORT 1234 /* values < 1024 are privileged ports */ #define MAXMSG 1024 /* 1K might be a typical network buffer */ int main() { int client_socket = 0; struct sockaddr_in srv_addr; char sendbuf[MAXMSG], responsebuf[MAXMSG]; /* int socket(int domain, int type, int protocol) * * socket() creates an endpoint for communication and returns a descriptor * (aka, a socket). you will use it in calls to connect(), send(), recv(). * * arguments: * int domain - communication domains are defined in . you * will use: PF_INET -- protocol family IPv4 Internet protocols * * int type - for TCP/IP use SOCK_STREAM, for UDP use SOCK_DGRAM * * int protocol - specifies a particular protocol to be used for the socket. * Normally only a single protocol exists to support a particular socket * type within a given protocol family, in which a case protocol can be * specified as 0. * */ if ((client_socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { cout << "Error creating client socket end!\n"; exit(-1); } bzero(&srv_addr, sizeof(srv_addr)); /* Now we specify information about the server. * sin_family: AF_INET -- address family IPv4 Internet * sin_port: specifies the port on which server is listening on. * The htons() function converts the unsigned short integer hostshort from * host byte order to network byte order. * sin_addr: specifies the location of the server. In this example, we use * inet_pton() that takes in a string containing an IPv4 network address * and converts it into a network address stracture in the "AF" address * family. Alternatively, you could do something like: * struct hostend *hp; * hp = gethostbyname("ruby.engin.umich.edu"); * memcpy(&srv_addr.sin_addr, hp->h_addr, hp->h_length); */ srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(PORT); inet_pton(AF_INET, "127.0.0.1", &srv_addr.sin_addr); /* The client-side doesn't need to bind() the socket to a name, the system * automatically selects and bind a name to the socket */ /* int connect(int sd, const struct sockaddr * srv_addr, socklen_t addrlen) * call to connect() can fail for many reasons but commons would be if * the server is not running (no valid socket to connect to). * the upper bound on the server's queue of connections is reached. */ if(connect(client_socket, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) { cout << "connect failed\n"; exit(-1); } bzero(sendbuf, MAXMSG); /* Fill-in the buffer to send over the network */ sprintf(sendbuf, "The client says hello!"); /* int send(int sd, const void *msg, size_t len, int flag) */ if(send(client_socket, (void*)sendbuf, strlen(sendbuf)+1, 0) < 0) { cout << "send failed\n"; exit(-1); } /* and wait for the response from the server */ bzero(responsebuf, MAXMSG); /* int recv(int sd, void *msg, size_t len, int flags) */ if(recv(client_socket, (void*)responsebuf, MAXMSG, 0) < 0) { cout << "recv failed\n"; exit(-1); } cout << "Received response: [" << responsebuf << "]\n"; /* Don't forget to close your sockets! */ close(client_socket); cout << "Client exiting successfully.\n"; return 0; } /***************************************************************************/ /* Server-side application */ /***************************************************************************/ #include #include #include #include using namespace std; #define PORT 1234 #define MAXMSG 1024 #define MAX_SIMULTANEOUS_CONNS 10 int main() { int server_socket = 0, client_socket = 0, size = 1; socklen_t len = 0; struct sockaddr_in srv_addr; char recvbuf[MAXMSG], responsebuf[MAXMSG]; /* 1. read in port number if present * 2. read in the password file * 3. initialize the file system (leave for later) * 4. setup file server part of the network connection */ /* int socket(int domain, int type, int protocol) * * socket() creates an endpoint for communication and returns a descriptor * (aka, a socket). you will use it in calls to connect(), send(), recv(). * In general, a connection is composed of local and remote address&port * which we will build below. Connection establishment is asymmetric. * * arguments: * int domain - communication domains are defined in . you * will use: PF_INET -- protocol family IPv4 Internet protocols * * int type - for TCP/IP use SOCK_STREAM, for UDP use SOCK_DGRAM * * int protocol - specifies a particular protocol to be used for the socket. * Normally only a single protocol exists to support a particular socket * type within a given protocol family, in which a case protocol can be * specified as 0. * */ if((server_socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { cout << "socket failed\n"; exit(-1); } /* man setsockopt */ if(setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &size, sizeof(size)) < 0) { cout << "setsockopt failed\n"; exit(-1); } bzero(&srv_addr, sizeof(srv_addr)); /* Now we specify information about the server. * sin_family: AF_INET -- address family IPv4 Internet * sin_port: specifies the port on which server is listening on. * The htons() function converts the unsigned short integer hostshort from * host byte order to network byte order. * sin_addr.s_addr: is bound to a wildcard address (INADDR_ANY). the system * bind the socket to the (valid) local address. For the server, it is * important to bind to a specific port number. For the client, it is * important to bind to the server's address and port number. * Note: I've seen an alternative assignment (both work as far as i know): * srv_addr.sin_addr.s_addr = INADDR_ANY; */ srv_addr.sin_family = AF_INET; srv_addr.sin_addr.s_addr = htonl( INADDR_ANY ); srv_addr.sin_port = htons( PORT ); /* int bind(int sd, struct sockaddr *srv_addr, socklen_t addrlen) * bind "assigns a name to a socket". When a socket is created with socket(), * it exists in a name space (address family) but has no name assigned. * You need to assign a name to a socket before SOCK_STREAM socket may * receive connections. A call to bind() will return -1 if the some other * socket is already bound to an address. Therefore, you need to check the * return code of the function! That's is why we recommend that you add the * setsockopt() call to allow for reuse of the name to socket binding. */ if(bind(server_socket, (struct sockaddr*)&srv_addr, sizeof(struct sockaddr_in)) < 0) { cout << "bind failed\n"; exit (-1); } /* int listen(int s, int backlog) * "backlog" number of connect()'s will be queue-ed up. once the limit is * reached, other connections will be declined. client's connect() will * return an error at that point. */ if(listen(server_socket, MAX_SIMULTANEOUS_CONNS) < 0) { cout << "listen failed\n"; exit(-1); } /* Also, you will find getsockname() useful to find out the port number * the socket is listening/bound to. */ len = sizeof(srv_addr); getsockname(server_socket, (struct sockaddr*)&srv_addr, &len ); /* REMEMBER, as per the spec, you need to print out the port number, the * file server is listening on. */ cout << "\n@@@ port" << ntohs(srv_addr.sin_port) << endl; /* The server is bound and listening on the port. * Try to accept a connection. This is a blocking call (waiting for a client * to call connect()). */ /* 5. Like the disk scheduler, the server should be in a while(1) waiting * for requests and servicing them. * while(1) { * client_socket = accept(); * service(); * } * As per specification, your file server has to handle any number of * concurrent client requests (use threads)... (for later discussions). * accept() returns a new descriptor (socket) for every incoming * connection (eg., you can print the value of the socket, it's just * an int). this specific socket should be used to reply back to * the client (via send(client_socket, .. , .., ..); * * int accept(int sd, struct sockaddr *addr, socklen_t *addrlen) * if addr and addlen are not NULL, accept() will return information * about the client-side computer. Alternatively, in general you would do: * struct sockaddr client_addr; * socklen_t size; * size = sizeof(client_addr); * accept(server_socket, &client_addr, &size); */ if((client_socket = accept(server_socket, NULL, NULL)) < 0) { cout << "accept failed\n"; exit(-1); } /* At this point, client and server established a network connection and * ready to transfer data */ bzero(recvbuf, MAXMSG); /* int recv(int s, void *buf, size_t len, int flags) * recv() returns the length of the message it actually read but it will * never be greater than MAXMSG. If no messages are available at the socket, * the receive calls wait for a message to arrive (it's a blocking call)!. * remember to allocate memory to "recvbuf" before asking recv() to write * into it! */ if ((size = recv(client_socket, (void*)recvbuf, MAXMSG, 0)) < 0) { cout << "Error receiving from client!\n"; exit(-1); } cout << "Received message: [" << recvbuf << "] from client.\n"; /* Send a response to the client */ bzero(responsebuf, MAXMSG); /* Fill-in the buffer to send over the network */ sprintf(responsebuf, "Got the message!"); /* int send(int sd, const void *msg, size_t len, int flags) * like recv(), send() returns the number of bytes it sent. */ if (send(client_socket, (void*)responsebuf, strlen(responsebuf)+1, 0) < 0) { cout << "Error sending response to client.\n"; exit(-1); } /* Don't forget to close your sockets! */ /* Close the client_socket after you are done processing client's request */ close(client_socket); /* In practice, "server_socket" should not be closed unless an error occurred * and you are gracefully shutting down the server. For instance, * while(1) { client_socket = accept(); service_request(client_socket); close(client_socket); } close(server_socket); */ close(server_socket); cout << "Server exiting successfully.\n"; }