peperonity.net
Welcome, guest. You are not logged in.
Log in or join for free!
 
Stay logged in
Forgot login details?

Login
Stay logged in

For free!
Get started!

Text page


sypsniux.peperonity.net

socketai

. System Calls or Bust
4.1. socket()—Get the File Descriptor!
4.2. bind()—What port am I on?
4.3. connect()—Hey, you!
4.4. listen()—Will somebody please call me?
4.5. accept()—"Thank you for calling port 3490."
4.6. send() and recv()—Talk to me, baby!
4.7. sendto() and recvfrom()—Talk to me, DGRAM-style
4.8. close() and shutdown()—Get outta my face!
4.9. getpeername()—Who are you?
4.10. gethostname()—Who am I?
4.11. DNS—You say "whitehouse.gov", I say "63.161.169.137"

4. System Calls or Bust

This is the section where we get into the system calls that allow you to access the network functionality of a Unix box. When you call one of these functions, the kernel takes over and does all the work for you automagically.

The place most people get stuck around here is what order to call these things in. In that, the man pages are no use, as you've probably discovered. Well, to help with that dreadful situation, I've tried to lay out the system calls in the following sections in exactly (approximately) the same order that you'll need to call them in your programs.

That, coupled with a few pieces of sample code here and there, some milk and cookies (which I fear you will have to supply yourself), and some raw guts and courage, and you'll be beaming data around the Internet like the Son of Jon Postel!
4.1. socket()—Get the File Descriptor!

I guess I can put it off no longer—I have to talk about the socket() system call. Here's the breakdown:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

But what are these arguments? First, domain should be set to "PF_INET". Next, the type argument tells the kernel what kind of socket this is: SOCK_STREAM or SOCK_DGRAM. Finally, just set protocol to "0" to have socket() choose the correct protocol based on the type. (Notes: there are many more domains than I've listed. There are many more types than I've listed. See the socket() man page. Also, there's a "better" way to get the protocol, but specifying 0 works in 99.9% of all cases. See the getprotobyname() man page if you're curious.)

socket() simply returns to you a socket descriptor that you can use in later system calls, or -1 on error. The global variable errno is set to the error's value (see the perror() man page.)

(This PF_INET thing is a close relative of the AF_INET that you used when initializing the sin_family field in your struct sockaddr_in. In fact, they're so closely related that they actually have the same value, and many programmers will call socket() and pass AF_INET as the first argument instead of PF_INET. Now, get some milk and cookies, because it's times for a story. Once upon a time, a long time ago, it was thought that maybe a address family (what the "AF" in "AF_INET" stands for) might support several protocols that were referred to by their protocol family (what the "PF" in "PF_INET" stands for). That didn't happen. And they all lived happily ever after, The End. So the most correct thing to do is to use AF_INET in your struct sockaddr_in and PF_INET in your call to socket().)

Fine, fine, fine, but what good is this socket? The answer is that it's really no good by itself, and you need to read on and make more system calls for it to make any sense.
4.2. bind()—What port am I on?

Once you have a socket, you might have to associate that socket with a port on your local machine. (This is commonly done if you're going to listen() for incoming connections on a specific port—MUDs do this when they tell you to "telnet to x.y.z port 6969".) The port number is used by the kernel to match an incoming packet to a certain process's socket descriptor. If you're going to only be doing a connect(), this may be unnecessary. Read it anyway, just for kicks.

Here is the synopsis for the bind() system call:

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd is the socket file descriptor returned by socket(). my_addr is a pointer to a struct sockaddr that contains information about your address, namely, port and IP address. addrlen can be set to sizeof(struct sockaddr).

Whew. That's a bit to absorb in one chunk. Let's have an example:

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 3490

int main(void)
{
int sockfd;
struct sockaddr_in my_addr;

sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking!

my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);

// don't forget your error checking for bind():
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
.
.
.

There are a few things to notice here: my_addr.sin_port is in Network Byte Order. So is my_addr.sin_addr.s_addr. Another thing to watch out for is that the header files might differ from system to system. To be sure, you should check your local man pages.

Lastly, on the topic of bind(), I should mention that some of the process of getting your own IP address and/or port can be automated:

my_addr.sin_port = 0; // choose an unused port at random
my_addr.sin_addr.s_addr = INADDR_ANY; // use my IP address

See, by setting my_addr.sin_port to zero, you are telling bind() to choose the port for you. Likewise, by setting my_addr.sin_addr.s_addr to INADDR_ANY, you are telling it to automatically fill in the IP address of the machine the process is running on.

If you are into noticing little things, you might have seen that I didn't put INADDR_ANY into Network Byte Order! Naughty me. However, I have inside info: INADDR_ANY is really zero! Zero still has zero on bits even if you rearrange the bytes. However, purists will point out that there could be a parallel dimension where INADDR_ANY is, say, 12 and that my code won't work there. That's okay with me:

my_addr.sin_port = htons(0); // choose an unused port at random
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // use my IP address

Now we're so portable you probably wouldn't believe it. I just wanted to point that out, since most of the code you come across won't bother running INADDR_ANY through htonl().

bind() also returns -1 on error and sets errno to the error's value.

Another thing to watch out for when calling bind(): don't go underboard with your port numbers. All ports below 1024 are RESERVED (unless you're the superuser)! You can have any port number above that, right up to 65535 (provided they aren't already being used by another program.)

Sometimes, you might notice, you try to rerun a server and bind() fails, claiming "Address already in use." What does that mean? Well, a little bit of a socket that was connected is still hanging around in the kernel, and it's hogging the port. You can either wait for it to clear (a minute or so), or add code to your program allowing it to reuse the port, like this:

int yes=1;
//char yes='1'; // Solaris people use this

// lose the pesky "Address already in use" error message
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}

One small extra final note about bind(): there are times when you won't absolutely have to call it. If you are connect()ing to a remote machine and you don't care what your local port is (as is the case with telnet where you only care about the remote port), you can simply call connect(), it'll check to see if the socket is unbound, and will bind() it to an unused local port if necessary.
4.3. connect()—Hey, you!

Let's just pretend for a few minutes that you're a telnet application. Your user commands you (just like in the movie TRON) to get a socket file descriptor. You comply and call socket(). Next, the user tells you to connect to "10.12.110.57" on port "23" (the standard telnet port.) Yow! What do you do now?

Lucky for you, program, you're now perusing the section on connect()—how to connect to a remote host. So read furiously onward! No time to lose!

The connect() call is as follows:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd is our friendly neighborhood socket file descriptor, as returned by the socket() call, serv_addr is a struct sockaddr containing the destination port and IP address, and addrlen can be set to sizeof(struct sockaddr).

Isn't this starting to make more sense? Let's have an example:

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define DEST_IP "10.12.110.57"
#define DEST_PORT 23

int main(void)
{
int sockfd;
struct sockaddr_in dest_addr; // will hold the destination addr

sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking!

dest_addr.sin_family = AF_INET; // host byte order
dest_addr.sin_port = htons(DEST_PORT); // short, network byte order
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(dest_addr.sin_zero, '\0', sizeof dest_addr.sin_zero);

// don't forget to error check the connect()!
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
.
.
.

Again, be sure to check the return value from connect()—it'll return -1 on error and set the variable errno.

Also, notice that we didn't call bind(). Basically, we don't care about our local port number; we only care where we're going (the remote port). The kernel will choose a local port for us, and the site we connect to will automatically get this information from us. No worries.
4.4. listen()—Will somebody please call me?

Ok, time for a change of pace. What if you don't want to connect to a remote host. Say, just for kicks, that you want to wait for incoming connections and handle them in some way. The process is two step: first you listen(), then you accept() (see below.)

The listen call is fairly simple, but requires a bit of explanation:

int listen(int sockfd, int backlog);

sockfd is the usual socket file descriptor from the socket() system call. backlog is the number of connections allowed on the incoming queue. What does that mean? Well, incoming connections are going to wait in this queue until you accept() them (see below) and this is the limit on how many can queue up. Most systems silently limit this number to about 20; ...
Next part ►


This page:




Help/FAQ | Terms | Imprint
Home People Pictures Videos Sites Blogs Chat
Top
.