Here's a link to a program I made for Dr. Fujinoki's OS class.
It sets up a socket that listens for connections, and makes a thread for each user who connects.
Should be useful for writing the owner agent programs.
I should have a little more time to talk about the Sockets code I posted now.
I'll write a tutorial here that should hopefully explain most of the questions you might have about writing, compiling, and running a C++ program that uses sockets and threads in Linux.
Compile w/ g++ & Include the pthread library
Basically the file I posted was my code from an assignment that Dr. Fujinoki gave in his OS class last semester. It's a simple HTTP server using sockets + threads.
When you open the file you'll probably notice this near the top:
//Usage: Compile with -lpthread option and run
What does this mean?
Normally, whenever you compile a c++ application on Unix, you run this command:
g++ name_of_file.cpp
Note: g++ is the GNU c++ compiler. Running this command is similar to pressing the build/compile/run buttons in Visual Studio; it turns your .cpp into an actual program.
However, since we're compiling an application that uses the pthread library, we need some way of telling g++ to use the pthread library, or else the compiler will fail when trying to build that program. So, to do this, we'll run this command instead:
g++ -lpthread name_of_file.cpp
Note: -l makes g++ include code from a library when it's building your program. By doing -lpthread you're asking it to "include library pthread"
Includes section
So, now, on to the actual code.
This is the includes section at the top of the file:
After each include I made a comment saying why I included it. Like, for example, my program needed the htons(), htonl(), ntohl(), and ntohs() functions, which are declared inside of <arpa inet.h="">. So, this part's pretty self explanatory. I'll explain what these functions do a little further down.</arpa>
Typedefs section
Next thing you'll see is this ( note: socket_t means socket type ):
//Typedefstypedefintsocket_t;
All I'm doing here is saying "socket_t is the same thing as an int". So, why? Basically, it's because all of the UNIX socket functions like socket(), accept(), listen(), and so on take or return ints. These ints are socket ID #s. I felt like "socket_t mysocket;" made this more obvious than "int mysocket;", so I made that typedef and used it on all the vars I was storing socket ID #s in.
Unnecessary HTTP stuff
Next part of the file:
//HTTP response messages#define OK_GIF "HTTP/1.0 200 OK\nContent-Type:image/gif\n\n" //200 (OK) response header for GIF#define OK_JPEG "HTTP/1.0 200 OK\nContent-Type:image/jpeg\n\n" //200 (OK) response header for JPEG#define OK_TEXT "HTTP/1.0 200 OK\nContent-Type:text/html\n\n" //200 (OK) response header for text#define NOTOK_404 "HTTP/1.0 404 Not Found\nContent-Type:text/html\n\n" //404 response header#define MESS_404 "<html><body><h1>FILE NOT FOUND</h1></body></html>" //404 response page
This program used to be a simple webserver. These are HTTP response headers (and the HTML for the 404, in the case of MESS_404). Since we're not making a webserver, it's safe to get rid of these.
Socket constants
Next:
//Socket-related#define PORT_NUM 5110 //Web server listens for incoming connections on this TCP port#define BUF_SIZE 1024 //Buffer size in bytes#define PEND_CONNECTIONS 100 //Pending connections to hold
This section contains a few constants I set up related to the sockets the program uses.
Like the comments say, PORT_NUM is the port our server is listening for connections on. Feel free to change this to whatever port you want.
BUF_SIZE - Basically, Unix sockets use a char array[size] to store received data. char arrays are also used to store data the server sends to other users. The way I had it set up, BUF_SIZE is how big these receive and send arrays are. So, for instance, if BUF_SIZE was 1024, that means I can receive a max of 1024 characters from a single call to read() (I'll talk a little more about read() later).
(note: in C/C++, a char is a byte, so we can make these char arrays hold any kind of binary data, not just text).
PEND_CONNECTIONS - So, imagine you're at a checkout lane at a grocery store or something waiting to be checked out. While you're waiting in line, people come in behind you, and now the line's now full. While the line is full, customers who want to check out there are turned away. Basically, in terms of UNIX sockets, the checkout lane is a socket. The people waiting in line are pending connections - in other words, people who want to connect to that socket, but haven't been accept()ed yet. Just like how the line at a checkout station can become full, so can the queue that holds these pending connections. The PEND_CONNECTIONS constant sets how long this queue is.
I set it at 100, which is more than enough since connections are accepted automatically.
Main function
Alright, cool. We know what the constants are now. On to the main():
//Name: main//Desc: Root of the program. The program starts here.//Params: iArgC - Count of how many arguments were given to the program.// apszArgs - Array of C-style strings containing the command line arguments.//Ret: int - Returns 0 on successful execution, 1 otherwise.intmain(constintiArgC,constchar*apszArgs[]){//code...return0;}
First, I'd like to talk about the main function itself. We'll talk about the code inside of it later.
You've probably noticed whenever you're writing C++ in Visual Studio you can get away with a main function that looks like this:
voidmain(){//code...}
Turns out this doesn't work when you try to compile that code with g++. Whenever you write a main function in g++ it should look like the one from my program that I showed above that.
Status code
For starters, notice that main() returns an int. What's that about?
Basically, this is a status code. Whenever the program ends, the # your program returns in main is given back to the OS. This is used to tell the OS whether the program exited successfully ( usually indicated by a 0 ) or failed for some reason (usually indicated by a 1, or really, anything other than 0 ).
If you were writing a shell script or something, you could use the status code to detect whether an application failed or succeeded when it's done. Even though we probably won't be using it on any of the C++ apps we're making, it's still useful to know.
Passing & handling command line arguments
Next, notice the main() function has some arguments:
const int iArgC: Count of how many arguments were given to the program.
const char* apszArgs[]: Array of C-style strings containing the command line arguments.
Lets say you run a program by doing this:
./myprogram
When you run the program:
iArgC will be 1
apszArgs[0] will be "./myprogram" or something like that.
But, lets say I want to give the program some extra information prior to running it. It's possible to do this by giving the program arguments in the command line:
./myprogram arg1 arg2 "this is arg3"
This time when the program runs:
iArgC will be 4
apszArgs[0] will be "./myprogram" or something like that.
apszArgs[1] will be "arg1"
apszArgs[2] will be "arg2"
apszArgs[3] will be "this is arg3"
Making a server socket
Alright, so hopefully this clears up the weird shit with the main(). Onto the socket code!
This is the first line in main():
//Create IPv4 streaming socket using TCP/IPsocket_tserver_s=socket(AF_INET,SOCK_STREAM,0);
So, what does this line even do. If you recall I mentioned above that socket_t means int, but I just use it to show that a variable will hold a socket id#. So basically, I'm making a variable called server_s, then calling the socket() function to make a new socket. The new socket's ID then gets stored in server_s.
So, about the args we gave to socket():
AF_INET is a constant that tells socket() to give us an IPv4 socket (meaning we use IP addresses like 127.0.0.1 with this type of socket)
SOCK_STREAM is another constant. This tells socket() we want TCP/IP sockets, instead of UDP (also called datagram) sockets.
The 0 is supposed to be given as the third arg when using AF_INET and SOCK_STREAM.
Binding a socket
So, we've made our socket, but it's not ready to accept connections yet. We have to do a few things before we can get to that point.
Next up is this code:
//Bind this socket to the given address / portsockaddr_inserver_addr;server_addr.sin_family=AF_INET;server_addr.sin_port=htons(PORT_NUM);//Bind to a particular port (htons translates a short int from host to network endianness)server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//Bind to any available addressif(bind(server_s,(sockaddr*)&server_addr,sizeof(server_addr))<0){std::cerr<<"Couldn't bind server socket to port "<<PORT_NUM<<"."<<std::endl;return1;}
Here we're binding the socket to an IP address and a port. The first part of doing this is to make a sockaddr_in object and fill it with data. In the code above, I've called my sockaddr_in server_addr. Then I set a few things in it:
sin_family: In this case, family refers to a family of IP addresses. I gave AF_INET since the socket is set up to use IPv4 addresses.
sin_port: This is where you specify the port the socket will listen for connections on.
sin_addr.s_addr: This is where you specify what IP address you want the socket to accept connections on. This could be 127.0.0.1 to only accept connections from other programs on the same machine, or the machine's LAN address (e.g. 192.168.1.100) to only accept connections on the local network, or the machine's WAN (internet) address (e.g. 96.35.9.243) to accept connections from anyone on the internet. If you give INADDR_ANY, then it's basically the same as giving the machine's WAN address.
Note: Notice that when I set sin_port I don't just give PORT_NUM - I give htons( PORT_NUM ). Basically, htons stands for "host to network short". A full explanation of what htons() is doing could get a little complicated. But the short explanation is, it rearranges the order of the bytes inside of the short integer given, making sure they're in "network" (big endian) order. It's basically the same way for sin_addr.s_addr, but using htonl(), or "host to network long".
Next, you'll notice the if. For the if's condition, we're trying to bind the socket with the bind() function, and then check if there was an error (by checking if the error code that bind returned was less than 0). If this if check passes, an error has occurred. In that case, I put out an error message using cerr, and then return 1 (remember from above, returning 1 in the main() function is used to indicate a program has failed).
The first argument is the socket we're binding.
The second argument is the address of sockaddr_in. It needs to be converted to a ( sockaddr* ) for whatever reason.
The third argument should always be the size of the sockaddr_in. In my case, I just wrote sizeof( server_addr )
Make the socket listen for connections
Alright, great. We've got a socket made, and we've bound it to a port and address.
Next, we can make it start listening for connections:
//Set socket to listen modeif(listen(server_s,PEND_CONNECTIONS)){std::cerr<<"Error listening for incoming connections on port "<<PORT_NUM<<"."<<std::endl;return1;}
Basically, while a socket is in listen mode, whenever someone (a client) tries to connect to the machine on that port, it puts the connection into a queue of pending connections and waits for the program to call accept() to allow the client to connect.
I'm using the same error checking trick I used in the bind code above. I'll call the listen() function and use it's return value for a condition. If listen is successful, it returns 0. Otherwise, listen will return 1 and cause the error code in the if to run.
There are only two arguments we gave to this function:
server_s: The socket we made and bound already.
PEND_CONNECTIONS: The max # of pending connections to hold. I talk about this above.
Infinite loop to accept connections & the end of main
Next up, we can actually start accepting new connections to the socket.
Note: In the code in this section, I took out the pthread stuff so we could focus on the socket stuff. I'll talk about the pthread stuff later
//Accept incoming connections to the server socket. Forever.socket_tclient_s;sockaddr_inclient_addr;socklen_tclient_addr_len;//...pthread vars...charszIP[INET_ADDRSTRLEN];while(true){//...}//Close the server's socketclose(server_s);return0;
First, notice the vars I have outside of the while loop.
Every time a client connects, those vars are temporarily used to store information about the connecting client.
The szIP is a C-style string (a character array). In the code below, I have a function that grabs the connecting client's IP and stores it in that array. INET_ADDRSTRLEN is a constant that makes sure that the IP address string is long enough.
After that, you'll notice I have an infinite loop. This program isn't designed to close on it's own; it'll just keep running and running and running, accepting connections forever. It has to be manually closed; I'll discuss how to do this later.
Notice that after the while loop ends, it's the end of main(). I call close() to shut down the server socket, then return 0 to indicate the program ended successfully.
note: The code after the infinite loop never actually gets called, since it's an infinite loop, but it's good to have it in there anyway because the compiler may gripe about it if you don't.
Accepting connections
Now, finally, lets look at the contents of the while loop.
Note: Again, in the code in this section, I took out the pthread stuff so we could focus on the socket stuff. I'll talk about the pthread stuff later
while(true){std::cout<<"Listening for incoming connections on port "<<PORT_NUM<<"..."<<std::endl;//Accept incoming connectionclient_addr_len=sizeof(client_addr);if((client_s=accept(server_s,(sockaddr*)&client_addr,&client_addr_len))<0){//Report error and wait for next connection.std::cerr<<"Error accepting connection."<<std::endl;continue;}//Report person connecting.std::cout<<"Connection accepted ("<<inet_ntop(AF_INET,(void*)&client_addr.sin_addr,szIP,INET_ADDRSTRLEN)<<")."<<std::endl;//Spawn thread//pthread code...}//Close the server's socketclose(server_s);return0;
First thing I do is output a message showing that we're waiting for a connection. I also include a port number in this message so if I forget what port it is, I can look at it on screen.
Next, I set client_addr_len. This variable will store the size of the client_addr (client address) structure.
Next step is to call accept().
When you call accept, the program will wait (not do anything) until a connection arrives.
Accept takes the following arguments:
server_s: This is our server socket we made, bound, and put in listen mode.
__( sockaddr ) &client_addr__: Address of a sockaddr_in to store the connecting client's IP address in. Has to be converted to a ( sockaddr ) for whatever reason.
&client_addr_len: Address of a socklen_t (an integer). The size of the connecting client's address, in bytes, in output here.
Whenever a connection finally arrives, a new socket ID# is returned by the function. I store that ID# in client_s. This new socket (the client socket) allows the program to communicate with the person who just connected.
Like I did with bind() and listen() above, I'm using the error checking & handling trick using the if. Notice, though, that there's something weird. I'm setting client_s inside of the if's condition:
Also note that, because I don't want the program to close if there's some error accepting a connection, inside of the error handling code, rather than returning 1 (causing the program to close and report that it has failed), I tell it to report it's error message then "continue", in other words, the loop skips the rest of the code in the current iteration and goes back to the beginning to the loop, waiting for a new connection to appear.
Lastly, notice where I say "report person connecting". The function there (inet_ntop) converts an IPv4 (which is why I gave AF_INET) address ( i.e. (void)&client_addr.sin_addr) to a string. It's stored in szIP (the IP address string we set up outside of the while loop), and we let the function know that this array can hold INET_ADDRSTRLEN* characters in it.
inet_ntop returns a pointer to szIP after it stores the converted IP in it, which is why it can be given as part of the output like I show above.
Spawning a thread to handle the connection
I wanted to talk about the pthread stuff I cut out above.
First, the pthread vars outside of the while loop:
Like the name suggests, uiThreadID will store the ID# of the thread we're going to create.
attr is a struct we'll put settings into. We'll give this to the function that creates the thread.
cArgs is a struct we'll put some data into. This struct will contain the info we want to pass to the thread.
I defined ThreadArg_t at the top of the file but didn't talk about it above. Here it is:
//Thread argument type (data passed to a newly created thread)structThreadArg_t{socket_tclient_s;};
If you need to pass more data to a thread for whatever reason, just add extra entries to this struct.
Next, lets look at the code that creates the thread, inside of the while() loop:
//Spawn threadpthread_attr_init(&attr);//Initialize the pthread attributes to defaultscArgs.client_s=client_s;//Pass client socket as an argument//pthread_create( //Create a child thread// &uiThreadID, //Output thread ID here (system assigned)// &attr, //Use default thread attributes// ResponseThread, //Use this thread routine// (void*)&cArgs //Arguments to be passed// )if(pthread_create(&uiThreadID,&attr,ResponseThread,(void*)&cArgs)){//Report error, close client socket, wait for next connectionstd::cerr<<"Error creating thread."<<std::endl;close(client_s);continue;}
The comments here are pretty self explanatory except for a couple of things.
First off, ResponseThread is the name of a function.
You can call it whatever you want, however: the function must take a single void and return a void:
void*ResponseThread(void*pArg){//...returnNULL;}
Note that when we pass arguments to the new thread, we're passing the address of cArgs. This address needs to be converted to a (void*) since the ResponseThread can only take a void* as an argument.
We use the same error checking trick here to catch errors creating threads. In addition to the normal stuff of reporting the error, and skipping to the next iteration of the loop, we also make sure to close the socket of the client who connected to us. This will disconnect him (we don't want the client to stay connected after an error occurs).
Whenever the new thread is created, it runs the ResponseThread function in parallel with the other threads in the program, including the main thread (the one with the while loop). This way, even if the main thread is stuck waiting for connections, the other threads can keep working.
The response function
This post is getting pretty long, so I'm gonna wrap it up here.
This is the response thread function I was using for my assignment, minus the HTTP stuff:
constchar*STR_ONE="1";//Name: ResponseThread//Desc: Root of a response thread.// After a response thread is created, it runs this function.// The response thread terminates when the function returns.//Params: void* pArg - Pointer to arguments passed to the function.//Ret: void* - Always NULL.void*ResponseThread(void*pArg){ThreadArg_t*pcThreadArgs=(ThreadArg_t*)pArg;socket_tclient_s=pcThreadArgs->client_s;charpszRecvBuf[BUF_SIZE];//Input buffercharpszSendBuf[BUF_SIZE];//Output buffer//Wait for client to send dataif(recv(client_s,pszRecvBuf,BUF_SIZE,0)<0){std::cerr<<"Error receiving from client."<<std::endl;close(client_s);returnNULL;}//Send a 1strcpy(pszSendBuf,STR_ONE);send(client_s,pszSendBuf,strlen(pszSendBuf),0);//Close the connectionclose(client_s);//Nothing to returnreturnNULL;}
First thing I do is make a pointer to a ThreadArg_t (remember this is the type of structure we use to store arguments passed to this thread). Then I set that variable by converting the void* given to the thread into a ThreadArg_t*.
From there, I get the client socket's ID# by pulling it out of the thread args.
Then I make two buffers (C-style strings / arrays of characters) - one for receiving data, and another for sending data. They're BUF_SIZE characters long.
Then, I call the recv() function. This function will make the thread wait until the client sends data. If there's some kind of error here, I'm using the typical trick to detect it, then make it output an error, closes the client's socket, and returns NULL to end the thread.
Once I've got the clients data and done something with it (presumably you would check the data he sent, i.e. check to see if the username / password the user sent is valid), then I can send some data back to the client. I do that with the send() function. Note: You should use the strlen() function to make sure we're only sending as much data as we need (i.e. the buffer could be 1024 characters big, but only a single character is used).
After we've sent data, the last step is to close the connection, and we do that with the close() function.
Then you can return NULL to the end the thread.
And, voila! A thorough introduction to making a Linux app with sockets & threads!
Please post any questions related to these topics here, I'm more than glad to help.
Last edit: Jeff Hutchins 2012-11-16
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Here's a link to a program I made for Dr. Fujinoki's OS class.
It sets up a socket that listens for connections, and makes a thread for each user who connects.
Should be useful for writing the owner agent programs.
http://96.35.9.243/dl/main.cpp
I should have a little more time to talk about the Sockets code I posted now.
I'll write a tutorial here that should hopefully explain most of the questions you might have about writing, compiling, and running a C++ program that uses sockets and threads in Linux.
Compile w/ g++ & Include the pthread library
Basically the file I posted was my code from an assignment that Dr. Fujinoki gave in his OS class last semester. It's a simple HTTP server using sockets + threads.
When you open the file you'll probably notice this near the top:
What does this mean?
Normally, whenever you compile a c++ application on Unix, you run this command:
Note: g++ is the GNU c++ compiler. Running this command is similar to pressing the build/compile/run buttons in Visual Studio; it turns your .cpp into an actual program.
However, since we're compiling an application that uses the pthread library, we need some way of telling g++ to use the pthread library, or else the compiler will fail when trying to build that program. So, to do this, we'll run this command instead:
Note: -l makes g++ include code from a library when it's building your program. By doing -lpthread you're asking it to "include library pthread"
Includes section
So, now, on to the actual code.
This is the includes section at the top of the file:
After each include I made a comment saying why I included it. Like, for example, my program needed the htons(), htonl(), ntohl(), and ntohs() functions, which are declared inside of <arpa inet.h="">. So, this part's pretty self explanatory. I'll explain what these functions do a little further down.</arpa>
Typedefs section
Next thing you'll see is this ( note: socket_t means socket type ):
All I'm doing here is saying "socket_t is the same thing as an int". So, why? Basically, it's because all of the UNIX socket functions like socket(), accept(), listen(), and so on take or return ints. These ints are socket ID #s. I felt like "socket_t mysocket;" made this more obvious than "int mysocket;", so I made that typedef and used it on all the vars I was storing socket ID #s in.
Unnecessary HTTP stuff
Next part of the file:
This program used to be a simple webserver. These are HTTP response headers (and the HTML for the 404, in the case of MESS_404). Since we're not making a webserver, it's safe to get rid of these.
Socket constants
Next:
This section contains a few constants I set up related to the sockets the program uses.
Like the comments say, PORT_NUM is the port our server is listening for connections on. Feel free to change this to whatever port you want.
BUF_SIZE - Basically, Unix sockets use a char array[size] to store received data. char arrays are also used to store data the server sends to other users. The way I had it set up, BUF_SIZE is how big these receive and send arrays are. So, for instance, if BUF_SIZE was 1024, that means I can receive a max of 1024 characters from a single call to read() (I'll talk a little more about read() later).
(note: in C/C++, a char is a byte, so we can make these char arrays hold any kind of binary data, not just text).
PEND_CONNECTIONS - So, imagine you're at a checkout lane at a grocery store or something waiting to be checked out. While you're waiting in line, people come in behind you, and now the line's now full. While the line is full, customers who want to check out there are turned away. Basically, in terms of UNIX sockets, the checkout lane is a socket. The people waiting in line are pending connections - in other words, people who want to connect to that socket, but haven't been accept()ed yet. Just like how the line at a checkout station can become full, so can the queue that holds these pending connections. The PEND_CONNECTIONS constant sets how long this queue is.
I set it at 100, which is more than enough since connections are accepted automatically.
Main function
Alright, cool. We know what the constants are now. On to the main():
First, I'd like to talk about the main function itself. We'll talk about the code inside of it later.
You've probably noticed whenever you're writing C++ in Visual Studio you can get away with a main function that looks like this:
Turns out this doesn't work when you try to compile that code with g++. Whenever you write a main function in g++ it should look like the one from my program that I showed above that.
Status code
For starters, notice that main() returns an int. What's that about?
Basically, this is a status code. Whenever the program ends, the # your program returns in main is given back to the OS. This is used to tell the OS whether the program exited successfully ( usually indicated by a 0 ) or failed for some reason (usually indicated by a 1, or really, anything other than 0 ).
If you were writing a shell script or something, you could use the status code to detect whether an application failed or succeeded when it's done. Even though we probably won't be using it on any of the C++ apps we're making, it's still useful to know.
Passing & handling command line arguments
Next, notice the main() function has some arguments:
Lets say you run a program by doing this:
When you run the program:
But, lets say I want to give the program some extra information prior to running it. It's possible to do this by giving the program arguments in the command line:
This time when the program runs:
Making a server socket
Alright, so hopefully this clears up the weird shit with the main(). Onto the socket code!
This is the first line in main():
So, what does this line even do. If you recall I mentioned above that socket_t means int, but I just use it to show that a variable will hold a socket id#. So basically, I'm making a variable called server_s, then calling the socket() function to make a new socket. The new socket's ID then gets stored in server_s.
So, about the args we gave to socket():
Binding a socket
So, we've made our socket, but it's not ready to accept connections yet. We have to do a few things before we can get to that point.
Next up is this code:
Here we're binding the socket to an IP address and a port. The first part of doing this is to make a sockaddr_in object and fill it with data. In the code above, I've called my sockaddr_in server_addr. Then I set a few things in it:
Note: Notice that when I set sin_port I don't just give PORT_NUM - I give htons( PORT_NUM ). Basically, htons stands for "host to network short". A full explanation of what htons() is doing could get a little complicated. But the short explanation is, it rearranges the order of the bytes inside of the short integer given, making sure they're in "network" (big endian) order. It's basically the same way for sin_addr.s_addr, but using htonl(), or "host to network long".
Next, you'll notice the if. For the if's condition, we're trying to bind the socket with the bind() function, and then check if there was an error (by checking if the error code that bind returned was less than 0). If this if check passes, an error has occurred. In that case, I put out an error message using cerr, and then return 1 (remember from above, returning 1 in the main() function is used to indicate a program has failed).
Make the socket listen for connections
Alright, great. We've got a socket made, and we've bound it to a port and address.
Next, we can make it start listening for connections:
Basically, while a socket is in listen mode, whenever someone (a client) tries to connect to the machine on that port, it puts the connection into a queue of pending connections and waits for the program to call accept() to allow the client to connect.
I'm using the same error checking trick I used in the bind code above. I'll call the listen() function and use it's return value for a condition. If listen is successful, it returns 0. Otherwise, listen will return 1 and cause the error code in the if to run.
There are only two arguments we gave to this function:
Infinite loop to accept connections & the end of main
Next up, we can actually start accepting new connections to the socket.
Note: In the code in this section, I took out the pthread stuff so we could focus on the socket stuff. I'll talk about the pthread stuff later
First, notice the vars I have outside of the while loop.
Every time a client connects, those vars are temporarily used to store information about the connecting client.
The szIP is a C-style string (a character array). In the code below, I have a function that grabs the connecting client's IP and stores it in that array. INET_ADDRSTRLEN is a constant that makes sure that the IP address string is long enough.
After that, you'll notice I have an infinite loop. This program isn't designed to close on it's own; it'll just keep running and running and running, accepting connections forever. It has to be manually closed; I'll discuss how to do this later.
Notice that after the while loop ends, it's the end of main(). I call close() to shut down the server socket, then return 0 to indicate the program ended successfully.
note: The code after the infinite loop never actually gets called, since it's an infinite loop, but it's good to have it in there anyway because the compiler may gripe about it if you don't.
Accepting connections
Now, finally, lets look at the contents of the while loop.
Note: Again, in the code in this section, I took out the pthread stuff so we could focus on the socket stuff. I'll talk about the pthread stuff later
First thing I do is output a message showing that we're waiting for a connection. I also include a port number in this message so if I forget what port it is, I can look at it on screen.
Next, I set client_addr_len. This variable will store the size of the client_addr (client address) structure.
Next step is to call accept().
When you call accept, the program will wait (not do anything) until a connection arrives.
Accept takes the following arguments:
Whenever a connection finally arrives, a new socket ID# is returned by the function. I store that ID# in client_s. This new socket (the client socket) allows the program to communicate with the person who just connected.
Like I did with bind() and listen() above, I'm using the error checking & handling trick using the if. Notice, though, that there's something weird. I'm setting client_s inside of the if's condition:
Don't be scared. I was saving space. What I wrote works exactly the same way this does:
Also note that, because I don't want the program to close if there's some error accepting a connection, inside of the error handling code, rather than returning 1 (causing the program to close and report that it has failed), I tell it to report it's error message then "continue", in other words, the loop skips the rest of the code in the current iteration and goes back to the beginning to the loop, waiting for a new connection to appear.
Lastly, notice where I say "report person connecting". The function there (inet_ntop) converts an IPv4 (which is why I gave AF_INET) address ( i.e. (void)&client_addr.sin_addr) to a string. It's stored in szIP (the IP address string we set up outside of the while loop), and we let the function know that this array can hold INET_ADDRSTRLEN* characters in it.
inet_ntop returns a pointer to szIP after it stores the converted IP in it, which is why it can be given as part of the output like I show above.
Spawning a thread to handle the connection
I wanted to talk about the pthread stuff I cut out above.
First, the pthread vars outside of the while loop:
I defined ThreadArg_t at the top of the file but didn't talk about it above. Here it is:
If you need to pass more data to a thread for whatever reason, just add extra entries to this struct.
Next, lets look at the code that creates the thread, inside of the while() loop:
The comments here are pretty self explanatory except for a couple of things.
First off, ResponseThread is the name of a function.
You can call it whatever you want, however: the function must take a single void and return a void:
Note that when we pass arguments to the new thread, we're passing the address of cArgs. This address needs to be converted to a (void*) since the ResponseThread can only take a void* as an argument.
We use the same error checking trick here to catch errors creating threads. In addition to the normal stuff of reporting the error, and skipping to the next iteration of the loop, we also make sure to close the socket of the client who connected to us. This will disconnect him (we don't want the client to stay connected after an error occurs).
Whenever the new thread is created, it runs the ResponseThread function in parallel with the other threads in the program, including the main thread (the one with the while loop). This way, even if the main thread is stuck waiting for connections, the other threads can keep working.
The response function
This post is getting pretty long, so I'm gonna wrap it up here.
This is the response thread function I was using for my assignment, minus the HTTP stuff:
First thing I do is make a pointer to a ThreadArg_t (remember this is the type of structure we use to store arguments passed to this thread). Then I set that variable by converting the void* given to the thread into a ThreadArg_t*.
From there, I get the client socket's ID# by pulling it out of the thread args.
Then I make two buffers (C-style strings / arrays of characters) - one for receiving data, and another for sending data. They're BUF_SIZE characters long.
Then, I call the recv() function. This function will make the thread wait until the client sends data. If there's some kind of error here, I'm using the typical trick to detect it, then make it output an error, closes the client's socket, and returns NULL to end the thread.
Once I've got the clients data and done something with it (presumably you would check the data he sent, i.e. check to see if the username / password the user sent is valid), then I can send some data back to the client. I do that with the send() function. Note: You should use the strlen() function to make sure we're only sending as much data as we need (i.e. the buffer could be 1024 characters big, but only a single character is used).
After we've sent data, the last step is to close the connection, and we do that with the close() function.
Then you can return NULL to the end the thread.
And, voila! A thorough introduction to making a Linux app with sockets & threads!
Please post any questions related to these topics here, I'm more than glad to help.
Last edit: Jeff Hutchins 2012-11-16