5 system call or crash
This is where we go into system calls (and other library calls) that allow you to access the network functions of Unix boxes or any box that supports socket application programming interfaces (BSD, windows, Linux, apple, what do you have) When you call one of these functions, the kernel will automatically take over and do all the work for you.
Where most people are trapped here is the order in which these things are called. In this case, the man page is useless, as you may have found. To help solve this terrible situation, I try to call them in the following sections with you in the program.``*(about*``)System calls are listed in the same order.
With some sample code everywhere, some milk and biscuits (I'm afraid you have to provide it yourself), and some original courage and courage, you will spread data on the Internet like Jon Post's son!
(note that for brevity, many of the code snippets below do not include the necessary error checking. They usually assume that the result of calling getaddrinfo() is * successful * and returns valid entries in the link list. However, both cases have been properly resolved in a stand-alone program, so please use them as a model.)
5.1 getaddrinfo () - ready to launch!
This is the real force of a function with many options, but the usage is actually very simple. It helps you set up the structure you need later.
A little history: in the past, you used a function called gethostbyname () to perform DNS lookup. Then you will manually load this information into a structure sockaddr_in and use it in your call.
Thankfully, this is no longer necessary. (if you want to write code suitable for IPv4 and IPv6, this is not desirable!) in today's era, you now have the getaddrinfo () function, which can do all kinds of good things for you, including DNS and service name lookup, and fill in the structure you need. In addition!
Let's have a look!
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> int getaddrinfo(const char *node, // e.g. "www.example.com" or IP const char *service, // e.g. "http" or port number const struct addrinfo *hints, struct addrinfo **res); You give this function three input parameters, and it gives you a linked list of results res Pointer to.
The node parameter is the host name or IP address to connect to.
Next is the parameter service, which can be the port number, such as 80, or the name of a specific service (in IANA portList 20 Or found in the / etc/services file on Unix machines, such as hypertext transfer protocol, ftp, telnet or smtp, or any other name.
Finally, the prompt parameter points to the structure addrinfo that you have filled in with relevant information.
If you are a server that wants to listen to the host IP address port 3490, here is an example call. Please note that there is actually no monitoring or network setting; It just sets the structure we will use later:
int status; struct addrinfo hints; struct addrinfo *servinfo; // will point to the results memset(&hints, 0, sizeof hints); // make sure the struct is empty hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // TCP stream sockets hints.ai_flags = AI_PASSIVE; // fill in my IP for me if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status)); exit(1); } // servinfo now points to a linked list of 1 or more struct addrinfos // ... do everything until you don't need servinfo anymore .... freeaddrinfo(servinfo); // free the linked-list
Please note that I will ai_family set to AF_UNSPEC, which shows that I don't care whether we use IPv4 or IPv6. If you especially want one of them, you can set it to AF_INET or AF_INET6.
In addition, you will see AI_PASSIVE logo; This tells getaddrinfo () to assign the address of the local host to the socket structure. This is good because you don't have to hard code it. (or you can use a specific address as the first parameter of getaddrinfo (), which I currently have NULL.)
Then we call. If there is an error (getaddrinfo() returns non-zero), we can use the function gai_strerror () prints out, as you can see. However, if everything is normal, servinfo will point to a linked list of structure addresses, and each linked list contains a structure address that we can use in the future! pretty
Finally, when we finally complete the list of links that getaddrinfo () has so generously allocated to us, we can (and should) release it by calling freeaddrinfo ().
If you are a client that wants to connect to a specific server, here is an example call, such as "www.example.net" port 3490. Again, this is not actually connected, but it sets the structure we will use later:
int status; struct addrinfo hints; struct addrinfo *servinfo; // will point to the results memset(&hints, 0, sizeof hints); // make sure the struct is empty hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // TCP stream sockets // get ready to connect status = getaddrinfo("www.example.net", "3490", &hints, &servinfo); // servinfo now points to a linked list of 1 or more struct addrinfos // etc.
I've been saying that servinfo is a linked list containing all kinds of address information. Let's write a quick demo to show this information. This short program21The file you specified on the command line will be printed IP address of any host:
/* ** showip.c -- show IP addresses for a host given on the command line */ #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> int main(int argc, char *argv[]) { struct addrinfo hints, *res, *p; int status; char ipstr[INET6_ADDRSTRLEN]; if (argc != 2) { fprintf(stderr,"usage: showip hostname\n"); return 1; } memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version hints.ai_socktype = SOCK_STREAM; if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); return 2; } printf("IP addresses for %s:\n\n", argv[1]); for(p = res;p != NULL; p = p->ai_next) { void *addr; char *ipver; // get the pointer to the address itself, // different fields in IPv4 and IPv6: if (p->ai_family == AF_INET) { // IPv4 struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; addr = &(ipv4->sin_addr); ipver = "IPv4"; } else { // IPv6 struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; addr = &(ipv6->sin6_addr); ipver = "IPv6"; } // convert the IP to a string and print it: inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); printf(" %s: %s\n", ipver, ipstr); } freeaddrinfo(res); // free the linked list return 0; }
As you can see, the code calls getaddrinfo (). No matter what you pass on the command line, it will fill in the linked list pointed to by res, and then we can traverse the list and print it out, or do anything.
(it's a little ugly here. We must deeply study different types of sokaddrs according to the IP version. I'm sorry about this! I'm not sure there's a better way.)
Sample run! Everyone likes screenshots:
$ showip www.example.net IP addresses for www.example.net: IPv4: 192.0.2.88 $ showip ipv6.example.com IP addresses for ipv6.example.com: IPv4: 192.0.2.101 IPv6: 2001:db8:8c00:22::171
Now that we have control, we will pass the results obtained from getaddrinfo () to other socket functions. Finally, establish our network connection! Keep reading!
5.2 socket () - get file descriptor!
I don't think I can put it off any longer - I have to talk about socket () system calls. The following is a breakdown:
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);
But what are these parameters? They allow you to say what kind of socket you want (IPv4 or IPv6, stream or datagram, and TCP or UDP).
People used to hard code these values, and you can definitely do so now. (the domain is PF_INET or PF_INET6, the type is SOCK_STREAM or SOCK_DGRAM, and the protocol can be set to 0 to select the appropriate protocol for the given type. Alternatively, you can call get prototype name () to find the protocol you want, "tcp" or "udp".)
(this PF_INET thing is a close relative of AF_INET. You can use it when initializing the sin_family field in the structure sockaddr_in. In fact, they are so closely related that they actually have the same value that many programmers will call socket () And take AF_INET as the first parameter instead of PF_INET. Now, get some milk and biscuits, because it's time to tell a story. A long time ago, people thought that an address family (what does "AF" in AF_INET stand for) might support their protocol family (what does "PF" in PF_INET stand for) Several protocols referenced. But this did not happen. Since then, they all lived a happy life and ended. So the most correct way is to use AF_INET in your structure sockaddr_in and PF_INET in your call to socket ()
Anyway, that's enough. What you really want to do is to use the values in the result of the call to getaddrinfo () and input them directly into the socket (), as shown below:
int s; struct addrinfo hints, *res; // do the lookup // [pretend we already filled out the "hints" struct] getaddrinfo("www.example.com", "http", &hints, &res); // again, you should do error-checking on getaddrinfo(), and walk // the "res" linked list looking for valid entries instead of just // assuming the first one is good (like many of these examples do). // See the section on client/server for real examples. s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
Socket () returns only one socket descriptor, which you can use in future system calls, or - 1 in case of an error. The global variable errno is set to the wrong value (for more details, see the errno man page and quick notes for using errno in multithreaded programs).
Okay, okay, okay, but what are the benefits of this socket? The answer is that it's really not good in itself. You need to continue reading and make more system calls so that it makes sense.
5.3 bind () - which port am I on?
Once you have a socket, you may have to associate the socket with a port on the local machine. (if you want to listen for () incoming connections on a specific port, this is usually done - multiplayer online games will do this when they tell you to "connect to 192.168.5.10 port 3490".) the port number is used by the kernel to match the incoming packet with the socket descriptor of a process. If you only make one connection () (because you are a client, not a server), this may not be necessary. Read it anyway, just for fun.
The following is a summary of 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 the structure Sockaddr that contains your address (that is, port and IP address) information. addrlen is the byte length of the address.
Whew. It's a little hard to absorb in one block. Let's take an example to bind the socket to the host running the program, port 3490:
struct addrinfo hints, *res; int sockfd; // first, load up address structs with getaddrinfo(): memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // fill in my IP for me getaddrinfo(NULL, "3490", &hints, &res); // make a socket: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); // bind it to the port we passed in to getaddrinfo(): bind(sockfd, res->ai_addr, res->ai_addrlen);
By using AI_ The passive flag, I tell the program to bind to the IP of the host on which it runs. If you want to bind to a specific local IP address, delete AI_ Pass, and put an IP address in the first parameter of getaddrinfo().
bind() also returns - 1 in case of an error and sets errno to the wrong value.
Many old code packages SOCKADDR manually before calling bind()_ In structure. Obviously, this is unique to IPv4, but nothing can prevent you from doing the same thing for IPv6, except that it is usually easier to use getaddrinfo (). Anyway, the old code looks like this:
// !!! THIS IS THE OLD WAY !!! int sockfd; struct sockaddr_in my_addr; sockfd = socket(PF_INET, SOCK_STREAM, 0); my_addr.sin_family = AF_INET; 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); bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr);
In the above code, if you want to bind to a local IP address (such as the AI_PASSIVE flag above), you can also set INADDR_ANY assigned to s_addr field. Inaddr for IPv6 version_ Any is a global variable in6addr_ Any, assigned to your structure SOCKADDR_ Sin6 of in6_ In the addr field. (there is also a macro IN6ADDR_ANY_INIT that you can use in the variable initializer.)
Another thing to note when calling bind () is: do not use port numbers. All ports below 1024 are reserved (unless you are a super user)! You can have any port number higher than 1024 up to 65535 (provided they are not used by other programs).
Sometimes, you may notice that you try to run the server again, but bind() fails, claiming that "the address is already in use". What's the meaning of this? Well, the socket with one point connection is still hanging in the kernel, and it occupies the port. You can wait for it to clear (about a minute), or add code to your program to allow it to reuse ports, as follows:
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 yes) == -1) { perror("setsockopt"); exit(1); }
One last caveat about bind (): sometimes you don't have to call it. If you are connecting () to a remote machine and you don't care what your local port is (as in the case of telnet, you only care about the remote port), you can simply call connect (), which will check whether the socket is unbound and bind it to an unused local port if necessary.
5.4 connection () - Hey, you!
Let's pretend for a few minutes that you are a telnet application. Your user commands you (as in the movie TRON *) * to get a socket file descriptor. You follow and call socket (). Next, the user tells you to connect to "10.12.110.57" on port "23" (standard telnet port). Yo! What do you do now?
You're lucky, program, you're now perusing the section on connecting () - how to connect to a remote host. So please read on! There's no time to waste!
The connection () 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, which is returned by socket () call, serv_addr is the structure Sockaddr containing the target port and IP address, and addrlen is the length of the server address structure (in bytes).
All this information can be gathered from the results of the getaddrinfo () call, which is very useful.
Is this beginning to make more sense? I can't hear you here, so I can only hope so. Let's take an example. Here we establish a socket to connect to "www.example.com" on port 3490:
struct addrinfo hints, *res; int sockfd; // first, load up address structs with getaddrinfo(): memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; getaddrinfo("www.example.com", "3490", &hints, &res); // make a socket: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); // connect! connect(sockfd, res->ai_addr, res->ai_addrlen);
Similarly, the old program filled in its own structure sockaddr_ins passed to connection (). You can do this if you like. See above bind() Similar notes in section.
Be sure to check the return value of the connection () - it will return an error of - 1 and set the variable errno.
Also, notice that we did not call bind(). Basically, we don't care about our local port number; We only care where we're going (remote port). The kernel will select a local port for us, and the site we connect to will automatically get this information from us. never mind.
5.5 listen () - can someone call me?
OK, it's time to change the rhythm. What if you don't want to connect to the remote host. For example, just for fun, you want to wait for incoming connections and deal with them in some way. There are two steps in this process: first you listen (), and then you accept () (see below).
Listening to the () call is quite simple, but it needs a little explanation:
int listen(int sockfd, int backlog);
ockfd is a socket file descriptor commonly used in socket () system calls. Backlog is the number of connections allowed in the incoming queue. What's the meaning of this? Incoming connections will wait in this queue until you accept () them (see below), which is the limit on the number of connections that can be queued. Most systems silently limit this number to about 20; You may be able to set it to 5 or 10 without being affected.
Similarly, as usual, listen to () return - 1 and set the errno error.
As you might imagine, we need to call bind () first and then List (), so that the server can run on a specific port. (you must be able to tell your friends which port to connect to!) so if you want to listen for incoming connections, the order of system calls you will make is:
getaddrinfo(); socket(); bind(); listen(); /* accept() goes here */
I'll put it in the sample code because it's quite self-evident. (the code that accepts () is more complete below.) the really tricky part of the whole Sha ang is calling accept ().
5.6 accept () - "thank you for calling port 3490."
Ready -- it's a little strange to accept () calls! This can happen: someone in the distance will try to connect () to your machine on the port you are listening to (). Their connections are queued for acceptance (). You call accept () and tell it to get the pending connection. It will return a new socket file descriptor to you for this single connection! Yes, suddenly you have two socket file descriptors, the price of one! The original one is still listening for more new connections. The newly created one is finally ready to send () and recv (). Here we are.
The telephone number is as follows:
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Sockfd is the listening socket descriptor. It's simple. Addr usually points to the local structure SOCKADDR_ Pointer to storage. This is where information about incoming connections goes (through which you can determine which host calls you from which port). Addrlen is a local integer variable that should be set to sizeof (structure sockaddr_storage) before its address is passed to the receiver (). Accept () will not put more than that many bytes in addr. If it puts less, it changes the value of addrlen to reflect this.
Guess what? Accept () returns - 1. If an error occurs, set errno. Betcha didn't think of that.
As before, this is a collection that can be absorbed in a block, so here is a sample code fragment for you to read:
#include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #define MYPORT "3490" // the port users will be connecting to #define BACKLOG 10 // how many pending connections queue will hold int main(void) { struct sockaddr_storage their_addr; socklen_t addr_size; struct addrinfo hints, *res; int sockfd, new_fd; // !! don't forget your error checking for these calls !! // first, load up address structs with getaddrinfo(): memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // fill in my IP for me getaddrinfo(NULL, MYPORT, &hints, &res); // make a socket, bind it, and listen on it: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); bind(sockfd, res->ai_addr, res->ai_addrlen); listen(sockfd, BACKLOG); // now accept an incoming connection: addr_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size); // ready to communicate on socket descriptor new_fd! . . .
Also, notice that we will use the socket descriptor new_fd all send () and recv () calls. If you only get one connection, you can turn off () listening ockfd if you like to prevent more incoming connections on the same port.
5.7 send () and reply () - talk to me, baby!
These two functions are used to communicate through a stream socket or a connected datagram socket. If you want to use a regular unconnected datagram socket, you need to see the following about sendto() And recvfrom () Part of.
Send () call:
int send(int sockfd, const void *msg, int len, int flags);
Sockfd is the socket descriptor to which you want to send data (whether it is returned through socket () or received through accept (). msg is the pointer to the data you want to send, and len is the length of the data, in bytes. Just set the flag to 0. (for more information about flags, see the send () man page.)
Some example code might be:
char *msg = "Beej was here!"; int len, bytes_sent; . . . len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); . . .
Send () returns the number of bytes actually sent - this may be less than the number of bytes you told it to send! Look, sometimes you tell it to send a lot of data *, it just * can't handle it. It will send as much data as possible and trust you to send the rest later. Remember, if the value returned by * send () * doesn't match the value in len, it's up to you to send the rest of the string. The good news is that if the packet is small (less than 1K), it may send all the content at once. Similarly, - 1 is returned when an error occurs, and errno is set to the error number.
The recv () call is similar in many ways:
int recv(int sockfd, void *buf, int len, int flags);
Sockfd is the socket descriptor to be read, buf is the buffer to be read, len is the maximum length of the buffer, and the flag can be set to 0 again. (for flag information, see the recv () man page.)
recv() returns the number of bytes actually read into the buffer, or - 1 (set errno accordingly).
Wait! Recv() can return 0. This only means one thing: the remote side has closed your connection! The return value of 0 is how recv () lets you know that this is happening.
It's easy, isn't it? You can now pass data back and forth on the stream socket! WOW! You are a Unix network programmer!
5.8 sendto() and recvfrom() - talk to me, DGRAM style
"It's all good," I heard you say, "but does this leave me with an unconnected datagram socket?" no problem, friend. We happen to have something.
Since the datagram socket is not connected to the remote host, guess what information we need to give before sending the packet? you 're right. Destination address! Here's the scoop:
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, socklen_t tolen); As you can see, this call is basically the same call. Send () adds two other messages. to Is a pointer to a structure sokaddr(This may be another structure sockaddr_in Or structure sockaddr_in6 Or structure sockaddr_storage,You throw it at the last minute), including the goal IP Address and port. tolen,One int Deep layer, which can be simply set to sizeof*to or sizeof(structure sockaddr_storage).
To get the target address structure, you may either get it from getaddrinfo () or recvfrom () below, or fill it in manually.
Just as in send (), sendto () returns the number of bytes actually sent (again, it may be less than the number of bytes you told it to send!), or - 1 in case of an error.
recv () is similar to recvfrom (). The summary of recvfrom() is:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen); Again, it's like recv()Several fields have been added. from Is a pointer to the local structure sockaddr_storage The structure will populate the original machine IP Address and port. Fromlen Is a local int Pointer to the int Should initialize to sizeof*from or sizeof(structure sockaddr_storage). When the function returns, Fromlen Store the actual contents in from The length of the address in.
recvfrom() returns the number of bytes received, or - 1 (set errno accordingly).
So here's a question: why do we use the structure sockaddr_storage as socket type? Why not structure sockaddr_in? Because, you see, we don't want to tie ourselves to IPv4 or IPv6. So we use the general structure sockaddr_storage we know it's big enough.
(so... Here's another question: why is the structure Sockaddr itself not big enough for any address? We even convert the general structure sockaddr_storage to the general structure Sockaddr! It seems redundant. The answer is that it's not big enough. I think there will be a problem changing it at this point. So they made a new one.)
Remember that if you connect a datagram socket (), you can simply use send () and recv () for all transactions. The socket itself is still a datagram socket, and the packet still uses UDP, but the socket interface will automatically add destination and source information for you.
5.9 close () and close () - get out of my face!
Whew! You've been sending () ing and recv () ing data all day. You've had enough. You are ready to close the connection on the socket descriptor. It's easy. You can turn off () functions using only regular Unix file descriptors:
close(sockfd);
This will block any read and write to the socket. Anyone who tries to read or write to a socket on the remote side will receive an error.
If you want more control over how sockets are closed, you can use the close () function. It allows you to cut off communication in one direction or in both directions (just like closing (). Introduction:
int shutdown(int sockfd, int how);
Sockfd is the socket file descriptor you want to close and how to close one of the following:
howEffect0Further receives are disallowed1Further sends are disallowed2Further sends and receives are disallowed (like close())
Shutdown() returns 0 on success and - 1 on error (set errno accordingly).
If you condescend to use close () on an unconnected datagram socket, it will only make the socket unavailable for further send () and recv () calls (remember, if you connect () your datagram socket, you can use these).
Note that Shutdown () does not actually close the file descriptor - it just changes the availability of the file descriptor. To release the socket descriptor, you need to use close ().
There's nothing to say.
(except remember, if you are using Windows and Winsock, you should call closure () instead of closing ().)
5.10 getpeername () - who are you?
This function is too simple.
It's too simple. I hardly give it my own part. But anyway, it's here.
The function getpeername () will tell you who is on the other end of the connected stream socket. Summary:
#include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
Sockfd is the descriptor of the connected stream socket. Addr is a pointer to the structure Sockaddr (or structure sockaddr_in), which will store information about the other side of the connection. addrlen is a pointer to int, which should be initialized to sizeof*addr or sizeof (structure Sockaddr).
The function returns - 1 on error and sets errno accordingly.
Once you have their address, you can use inet_ntop(), getnameinfo(), or gethostbyaddr(). No, you can't get their login. (OK, OK. This is possible if another computer is running an ident daemon. However, this is beyond the scope of this document. See RFC 141322 Learn more.)
5.11 place names () - who am I?
Simpler than getpeername(), the function gethostname(). It returns the name of the computer on which the program is running. The following gethostbyname () can use this name to determine the IP address of the local computer.
What could be more interesting than that? I can think of some things, but they have nothing to do with socket programming. Anyway, here are the categories:
#include <unistd.h> int gethostname(char *hostname, size_t size);
The parameter is simple: host name is a pointer to the character array containing the host name when the function returns, and size is the byte length of the host name array.
The function returns 0 on successful completion and - 1 on error, and sets errno as usual.