分类: 系统运维
2005-10-11 23:42:12
If you don't have them on your system already, you probably don't need them.
Check the manual for your particular platform. If you're building for Windows,
you only need to #include
You have to use setsockopt() with the SO_REUSEADDR option on the listening socket. Check out the section on bind() and the section on select() for an example.
Use the netstat. Check the man page for full details, but you should get some good output just typing:
$ netstat |
The only trick is determining which socket is associated with which program. :-)
Run the route command (in /sbin on most Linuxes) or the command netstat -r.
Fortunately for you, virtually all machines implement a loopback network "device" that sits in the kernel and pretends to be a network card. (This is the interface listed as "lo" in the routing table.)
Pretend you're logged into a machine named "goat". Run the client in one window and the server in another. Or start the server in the background ("server &") and run the client in the same window. The upshot of the loopback device is that you can either client goat or client localhost (since "localhost" is likely defined in your /etc/hosts file) and you'll have the client talking to the server without a network!
In short, no changes are necessary to any of the code to make it run on a single non-networked machine! Huzzah!
You can tell because recv() will return 0.
All your raw sockets questions will be answered in W. Richard Stevens' UNIX Network Programming books. See the books section of this guide.
First, delete Windows and install Linux or BSD. };-). No, actually, just see the section on building for Windows in the introduction.
The linker errors happen because Sun boxes don't automatically compile in the socket libraries. See the section on building for Solaris/SunOS in the introduction for an example of how to do this.
Signals tend to cause blocked system calls to return -1 with errno set to EINTR. When you set up a signal handler with sigaction(), you can set the flag SA_RESTART, which is supposed to restart the system call after it was interrupted.
Naturally, this doesn't always work.
My favorite solution to this involves a goto statement. You know this irritates your professors to no end, so go for it!
select_restart: |
Sure, you don't need to use goto in this case; you can use other structures to control it. But I think the goto statement is actually cleaner.
Use select()! It allows you to specify a timeout parameter for socket descriptors that you're looking to read from. Or, you could wrap the entire functionality in a single function, like this:
#include |
Notice that recvtimeout() returns -2 in case of a timeout. Why not return 0? Well, if you recall, a return value of 0 on a call to recv() means that the remote side closed the connection. So that return value is already spoken for, and -1 means "error", so I chose -2 as my timeout indicator.
One easy way to do encryption is to use SSL (secure sockets layer), but that's beyond the scope of this guide.
But assuming you want to plug in or implement your own compressor or encryption system, it's just a matter of thinking of your data as running through a sequence of steps between both ends. Each step changes the data in some way.
Now the other way around:
You can also do compression at the same point that you do the encryption/decryption, above. Or you could do both! Just remember to compress before you encrypt. :)
Just as long as the client properly undoes what the server does, the data will be fine in the end no matter how many intermediate steps you add.
So all you need to do to use my code is to find the place between where the data is read and the data is sent (using send()) over the network, and stick some code in there that does the encryption.
Yes, yes it is. See the section on socket() for details.
For simplicity, lets say the client connect()s, send()s, and close()s the connection (that is, there are no subsequent system calls without the client connecting again.)
The process the client follows is this:
Meanwhile, the server is handling the data and executing it:
Beware! Having the server execute what the client says is like giving remote shell access and people can do things to your account when they connect to the server. For instance, in the above example, what if the client sends "rm -rf ~"? It deletes everything in your account, that's what!
So you get wise, and you prevent the client from using any except for a couple utilities that you know are safe, like the foobar utility:
if (!strcmp(str, "foobar")) { |
But you're still unsafe, unfortunately: what if the client enters "foobar; rm -rf ~"? The safest thing to do is to write a little routine that puts an escape ("") character in front of all non-alphanumeric characters (including spaces, if appropriate) in the arguments for the command.
As you can see, security is a pretty big issue when the server starts executing things the client sends.
You're hitting the MTU--the maximum size the physical medium can handle. On the local machine, you're using the loopback device which can handle 8K or more no problem. But on ethernet, which can only handle 1500 bytes with a header, you hit that limit. Over a modem, with 576 MTU (again, with header), you hit the even lower limit.
You have to make sure all the data is being sent, first of all. (See the sendall() function implementation for details.) Once you're sure of that, then you need to call recv() in a loop until all your data is read.
Read the section Son of Data Encapsulation for details on receiving complete packets of data using multiple calls to recv().
If they're anywhere, they'll be in POSIX libraries that may have shipped with your compiler. Since I don't have a Windows box, I really can't tell you the answer, but I seem to remember that Microsoft has a POSIX compatibility layer and that's where fork() would be. (And maybe even sigaction.)
Search the help that came with VC++ for "fork" or "POSIX" and see if it gives you any clues.
If that doesn't work at all, ditch the fork()/sigaction stuff and replace it with the Win32 equivalent: CreateProcess(). I don't know how to use CreateProcess()--it takes a bazillion arguments, but it should be covered in the docs that came with VC++.
Check out the .
Unfortunately, the purpose of a firewall is to prevent people outside the firewall from connecting to machines inside the firewall, so allowing them to do so is basically considered a breach of security.
This isn't to say that all is lost. For one thing, you can still often connect() through the firewall if it's doing some kind of masquerading or NAT or something like that. Just design your programs so that you're always the one initiating the connection, and you'll be fine.
If that's not satisfactory, you can ask your sysadmins to poke a hole in the firewall so that people can connect to you. The firewall can forward to you either through it's NAT software, or through a proxy or something like that.
Be aware that a hole in the firewall is nothing to be taken lightly. You have to make sure you don't give bad people access to the internal network; if you're a beginner, it's a lot harder to make software secure than you might imagine.
Don't make your sysadmin mad at me. ;-)
In the Unix world, there are a lot of manuals. They have little sections that describe individual functions that you have at your disposal.
Of course, manual would be too much of a thing to type. I mean, no one in the Unix world, including myself, likes to type that much. Indeed I could go on and on at great length about how much I prefer to be terse but instead I shall be brief and not bore you with long-winded diatribes about how utterly amazingly brief I prefer to be in virtually all circumstances in their entirety.
[Applause]
Thank you. What I am getting at is that these pages are called "man pages" in the Unix world, and I have included my own personal truncated variant here for your reading enjoyment. The thing is, many of these functions are way more general purpose than I'm letting on, but I'm only going to present the parts that are relevant for Internet Sockets Programming.
And, now for your reading pleasure, I have included some basic home-grown man pages right here in this guide. And here is what is wrong with them:
If you want the real information, check your local Unix man pages by typing man whatever, where "whatever" is something that you're incredibly interested in, such as "accept".
So why even include these at all in the Guide? Well, there are a few reasons, but the best are that (a) these versions are geared specifically toward network programming and are easier to digest than the real ones, and (b) these versions contain examples!
Oh! And speaking of the examples, I don't tend to put in all the error checking because it really increases the length of the code. But you should absolutely do error checking pretty much any time you make any of the system calls unless you're totally 100% sure it's not going to fail, and you should probably do it even then!
Accept an incoming connection on a listening socket
#include
#include
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
Once you've gone through the trouble of getting a SOCK_STREAM socket and setting it up for incoming connections with listen(), then you call accept() to actually get yourself a new socket descriptor to use for subsequent communication with the newly connected client.
The old socket that you are using for listening is still there, and will be used for further accept() calls as they come in.
s |
The listen()ing socket descriptor. |
addr |
This is filled in with the address of the site that's connecting to you. |
addrlen |
This is filled in with the sizeof() the structure returned in the addr parameter. You can safely ignore it if you assume you're getting a struct sockaddr_in back, which you know you are, because that's the type you passed in for addr. |
accept() will normally block, and you can use select() to peek on the listening socket descriptor ahead of time to see if it's "ready to read". If so, then there's a new connection waiting to be accept()ed! Yay! Alternatively, you could set the O_NONBLOCK flag on the listening socket using fcntl(), and then it will never block, choosing instead to return -1 with errno set to EWOULDBLOCK.
The socket descriptor returned by accept() is a bona fide socket descriptor, open and connected to the remote host. You have to close() it when you're done with it.
accept() returns the newly connected socket descriptor, or -1 on error, with errno set appropriately.
int s, s2; |
Associate a socket with an IP address and port number
#include
#include
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
When a remote machine wants to connect to your server program, it needs two pieces of information: the IP address and the port number. The bind() call allows you to do just that.
First, you call socket() to get a socket descriptor, and then you load up a struct sockaddr_in with the IP address and port number information, and then you pass both of those into bind(), and the IP address and port are magically (using actual magic) bound to the socket!
If you don't know your IP address, or you know you only have one IP address on the machine, or you don't care which of the machine's IP addresses is used, you can simply set the s_addr field in your struct sockaddr_in to INADDR_ANY and it will fill in the IP address for you.
Lastly, the addrlen parameter should be set to sizeof(my_addr).
Returns zero on success, or -1 on error (and errno will be set accordingly.)
struct sockaddr_in myaddr; |
Connect a socket to a server
#include
#include
int connect(int sockfd, const struct sockaddr
*serv_addr,
socklen_t addrlen);
Once you've built a socket descriptor with the socket() call, you can connect() that socket to a remote server using the well-named connect() system call. All you need to do is pass it the socket descriptor and the address of the server you're interested in getting to know better. (Oh, and the length of the address, which is commonly passed to functions like this.)
If you haven't yet called bind() on the socket descriptor, it is automatically bound to your IP address and a random local port. This is usually just fine with you, since you really don't care what your local port is; you only care what the remote port is so you can put it in the serv_addr parameter. You can call bind() if you really want your client socket to be on a specific IP address and port, but this is pretty rare.
Once the socket is connect()ed, you're free to send() and recv() data on it to your heart's content.
Special note: if you connect() a SOCK_DGRAM UDP socket to a remote host, you can use send() and recv() as well as sendto() and recvfrom(). If you want.
Returns zero on success, or -1 on error (and errno will be set accordingly.)
int s; |
Close a socket descriptor
#include
int close(int s);
After you've finished using the socket for whatever demented scheme you have concocted and you don't want to send() or recv() or, indeed, do anything else at all with the socket, you can close() it, and it'll be freed up, never to be used again.
The remote side can tell if this happens one of two ways. One: if the remote side calls recv(), it will return 0. Two: if the remote side calls send(), it'll recieve a signal SIGPIPE and send() will return -1 and errno will be set to EPIPE.
Windows users: the function you need to use is called closesocket(), not close(). If you try to use close() on a socket descriptor, it's possible Windows will get angry... And you wouldn't like it when it's angry.
Returns zero on success, or -1 on error (and errno will be set accordingly.)
s = socket(PF_INET, SOCK_DGRAM, 0); |
Returns the name of the system
#include
int gethostname(char *name, size_t len);
Your system has a name. They all do. This is a slightly more Unixy thing than the rest of the networky stuff we've been talking about, but it still has its uses.
For instance, you can get your host name, and then call gethostbyname() to find out your IP address.
The parameter name should point to a buffer that will hold the host name, and len is the size of that buffer in bytes. gethostname() won't overwrite the end of the buffer (it might return an error, or it might just stop writing), and it will NUL-terminate the string if there's room for it in the buffer.
Returns zero on success, or -1 on error (and errno will be set accordingly.)
char hostname[128]; |
Get an IP address for a hostname, or vice-versa
#include
#include
struct hostent *gethostbyname(const char *name);
struct
hostent *gethostbyaddr(const char *addr, int len, int type);
These functions map back and forth between host names and IP addresses. After all, you want an IP address to pass to connect(), right? But no one wants to remember an IP address. So you let your users type in things like " instead of "66.94.230.35".
gethostbyname() takes a string like ", and returns a struct hostent which contains tons of information, including the IP address. (Other information is the official host name, a list of aliases, the address type, the length of the addresses, and the list of addresses--it's a general-purpose structure that's pretty easy to use for our specific purposes once you see how.)
gethostbyaddr() takes a struct in_addr and brings you up a corresponding host name (if there is one), so it's sort of the reverse of gethostbyname(). As for parameters, even though addr is a char*, you actually want to pass in a pointer to a struct in_addr. len should be sizeof(struct in_addr), and type should be AF_INET.
So what is this struct hostent that gets returned? It has a number of fields that contain information about the host in question.
char *h_name |
The real canonical host name. |
char **h_aliases |
A list of aliases that can be accessed with arrays--the last element is NULL |
int h_addrtype |
The result's address type, which really should be AF_INET for our purposes.. |
int length |
The length of the addresses in bytes, which is 4 for IP (version 4) addresses. |
char **h_addr_list |
A list of IP addresses for this host. Although this is a char**, it's really an array of struct in_addr*s in disguise. The last array element is NULL. |
h_addr |
A commonly defined alias for h_addr_list[0]. If you just want any old IP address for this host (yeah, they can have more than one) just use this field. |
Returns a pointer to a resultant struct hostent or success, or NULL on error.
Instead of the normal perror() and all that stuff you'd normally use for error reporting, these functions have parallel results in the variable h_errno, which can be printed using the functions herror() or hstrerror(). These work just like the classic errno, perror(), and strerror() functions you're used to.
int i; |
Return address info about the remote side of the connection
#include
int getpeername(int s, struct sockaddr *addr, socklen_t *len);
Once you have either accept()ed a remote connection, or connect()ed to a server, you now have what is known as a peer. Your peer is simply the computer you're connected to, identified by an IP address and a port. So...
getpeername() simply returns a struct sockaddr_in filled with information about the machine you're connected to.
Why is it called a "name"? Well, there are a lot of different kinds of sockets, not just Internet Sockets like we're using in this guide, and so "name" was a nice generic term that covered all cases. In our case, though, the peer's "name" is it's IP address and port.
Although the function returns the size of the resultant address in len, you must preload len with the size of addr.
Returns zero on success, or -1 on error (and errno will be set accordingly.)
int s; |
Holds the error code for the last system call
#include
int errno;
This is the variable that holds error information for a lot of system calls. If you'll recall, things like socket() and listen() return -1 on error, and they set the exact value of errno to let you know specifically which error occurred.
The header file errno.h lists a bunch of constant symbolic names for errors, such as EADDRINUSE, EPIPE, ECONNREFUSED, etc. Your local man pages will tell you what codes can be returned as an error, and you can use these at run time to handle different errors in different ways.
Or, more commonly, you can call perror() or strerror() to get a human-readable version of the error.
The value of the variable is the latest error to have transpired, which might be the code for "success" if the last action succeeded.
s = socket(PF_INET, SOCK_STREAM, 0); |
Control socket descriptors
#include
#include
int fcntl(int s, int cmd, long arg);
This function is typically used to do file locking and other file-oriented stuff, but it also has a couple socket-related functions that you might see or use from time to time.
Parameter s is the socket descriptor you wish to operate on, cmd should be set to F_SETFL, and arg can be one of the following commands. (Like I said, there's more to fcntl() than I'm letting on here, but I'm trying to stay socket-oriented.)
O_NONBLOCK |
Set the socket to be non-blocking. See the section on blocking for more details. |
O_ASYNC |
Set the socket to do asynchronous I/O. When data is ready to be recv()'d on the socket, the signal SIGIO will be raised. This is rare to see, and beyond the scope of the guide. And I think it's only available on certain systems. |
Returns zero on success, or -1 on error (and errno will be set accordingly.)
Different uses of the fcntl() actually have different return values, but I haven't covered them here because they're not socket-related. See your local fcntl() man page for more information.
int s = socket(PF_INET, SOCK_STREAM, 0); |
Convert multi-byte integer types from host byte order to network byte order
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t
hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t
ntohs(uint16_t netshort);
Just to make you really unhappy, different computers use different byte orderings internally for their multibyte integers (i.e. any interger that's larger than a char.) The upshot of this is that if you send() a two-byte short int from an Intel box to a Mac (before they became Intel boxes, too, I mean), what one computer thinks is the number 1, the other will think is the number 256, and vice-versa.
The way to get around this problem is for everyone to put aside their differences and agree that Motorola and IBM had it right, and Intel did it the weird way, and so we all convert our byte orderings to "big-endian" before sending them out. Since Intel is a "little-endian" machine, it's far more politically correct to call our preferred byte ordering "Network Byte Order". So these functions convert from your native byte order to network byte order and back again.
(This means on Intel these functions swap all the bytes around, and on PowerPC they do nothing because the bytes are already in Network Byte Order. But you should always use them in your code anyway, since someone might want to build it on an Intel machine and still have things work properly.)
Note that the types involved are 32-bit (4 byte, probably int) and 16-bit (2 byte, very likely short) numbers. 64-bit machines might have a htonll() for 64-bit ints, but I've not seen it. You'll just have to write your own.
Anyway, the way these functions work is that you first decide if you're converting from host (your machine's) byte order or from network byte order. If "host", the the first letter of the function you're going to call is "h". Otherwise it's "n" for "network". The middle of the function name is always "to" because you're converting from one "to" another, and the penultimate letter shows what you're converting to. The last letter is the size of the data, "s" for short, or "l" for long. Thus:
htons() |
host to network short |
htonl() |
host to network long |
ntohs() |
network to host short |
ntohl() |
network to host long |
Each function returns the converted value.
uint32_t some_long = 10; |
Convert IP addresses from a dots-and-number string to a struct in_addr and back
#include
#include
#include
char *inet_ntoa(struct in_addr in);
int inet_aton(const char
*cp, struct in_addr *inp);
in_addr_t inet_addr(const char
*cp);
All of these functions convert from a struct in_addr (part of your struct sockaddr_in, most likely) to a string in dots-and-numbers format (e.g. "192.168.5.10") and vice-versa. If you have an IP address passed on the command line or something, this is the easiest way to get a struct in_addr to connect() to, or whatever. If you need more power, try some of the DNS functions like gethostbyname() or attempt a coup-de-tat in your local country.
The function inet_ntoa() converts a network address in a struct in_addr to a dots-and-numbers format string. The "n" in "ntoa" stands for network, and the "a" stands for ASCII for historical reasons (so it's "Network To ASCII"--the "toa" suffix has an analogous friend in the C library called atoi() which converts an ASCII string to an integer.)
The function inet_aton() is the opposite, converting from a dots-and-numbers string into a in_addr_t (which is the type of the field s_addr in your struct in_addr.)
Finally, the function inet_addr() is an older function that does basically the same thing as inet_aton(). It's theoretically deprecated, but you'll see it alot and the police won't come get you if you use it.
inet_aton() returns non-zero if the address is a valid one, and it returns zero if the address is invalid.
inet_ntoa() returns the dots-and-numbers string in a static buffer that is overwritten with each call to the function.
inet_addr() returns the address as an in_addr_t, or -1 if there's an error. (That is the same result as if you tried to convert the string "255.255.255.255", which is a valid IP address. This is why inet_aton() is better.)
struct sockaddr_in antelope; |
Tell a socket to listen for incoming connections
#include
int listen(int s, int backlog);
You can take your socket descriptor (made with the socket() system call) and tell it to listen for incoming connections. This is what differentiates the servers from the clients, guys.
The backlog parameter can mean a couple different things depending on the system you on, but loosely it is how many pending connections you can have before the kernel starts rejecting new ones. So as the new connections come in, you should be quick to accept() them so that the backlog doesn't fill. Try setting it to 10 or so, and if your clients start getting "Connection refused" under heavy load, set it higher.
Before calling listen(), your server should call bind() to attach itself to a specific port number. That port number (on the server's IP address) will be the one that clients connect to.
Returns zero on success, or -1 on error (and errno will be set accordingly.)
int s; |
Print an error as a human-readable string
#include
void perror(const char *s);
#include
char *strerror(int errnum);
Since so many functions return -1 on error and set the value of the variable errno to be some number, it would sure be nice if you could easily print that in a form that made sense to you.
Mercifully, perror() does that. If you want more description to be printed before the error, you can point the parameter s to it (or you can leave s as NULL and nothing additional will be printed.)
In a nutshell, this function takes errno values, like ECONNRESET, and prints them nicely, like "Connection reset by peer."
The function strerror() is very similar to perror(), except it returns a pointer to the error message string for a given value (you usually pass in the variable errno.)
strerror() returns a pointer to the error message string.
int s; |