4-Elementary TCP Sockets

来源:互联网 发布:无创dna数据辨别男女 编辑:程序博客网 时间:2024/03/29 13:04

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

4.1 Introduction

4.2 ‘socket’ Function

  • To perform network I/O, the first thing a process must do is call the socket function to specify the type of communication protocol desired.
#include <sys/socket.h>int socket(int family, int type, int protocol);Returns: non-negative descriptor if OK, -1 on error
  • family specifies the protocol family and is one of the constants shown in Figure 4.2. The socket type is one of the constants shown in Figure 4.3. The protocol argument to the socket function should be set to the specific protocol type found in Figure 4.4, or 0 to select the system’s default for the given combination of family and type.

  • Not all combinations of socket family and type are valid. Figure 4.5 shows the valid combinations, along with the actual protocols that are valid for each pair. The boxes marked “Yes” are valid but do not have handy acronyms. The blank boxes are not supported.

  • On success, the socket function returns a small non-negative integer value which is called a socket descriptor, or a sockfd.

AF_xxx Versus PF_xxx

4.3 ‘connect’ Function

  • The connect function is used by a TCP client to establish a connection with a TCP server.
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);Returns: 0 if OK, -1 on error
  • sockfd is a socket descriptor returned by the socket function. The second and third arguments are a pointer to a socket address structure and its size. The socket address structure must contain the IP address and port number of the server.
  • The client does not have to call bind before calling connect: the kernel will choose both an ephemeral port and the source IP address if necessary.
  • For TCP socket, the connect function initiates TCP’s three-way handshake. The function returns only when the connection is established or an error occurs. There are several different error returns possible.
    1. If the client TCP receives no response to its SYN segment, ETIMEDOUT is returned. E.g.: 4.4BSD sends one SYN when connect is called, another 6 seconds later, and another 24 seconds later. If no response is received after a total of 75 seconds, the error is returned.
    2. If the server’s response to the client’s SYN is a reset(RST), this indicates that no process is waiting for connections on the server host at the port specified(i.e., the server process is probably not running). This is a hard error and the error ECONNREFUSED is returned to the client as soon as the RST is received.
  • An RST is a type of TCP segment that is sent by TCP when something is wrong. Three conditions that generate an RST are: when a SYN arrives for a port that has no listening server(above condition), when TCP wants to abort an existing connection, and when TCP receives a segment for a connection that does not exist.
    1. If the client’s SYN elicits an ICMP “destination unreachable” from some intermediate router, this is considered a soft error(maybe a transient condition). The client kernel saves the message but keeps sending SYNs with the same time between each SYN as in the first scenario. If no response is received after some fixed amount of time, the saved ICMP error is returned to the process as either EHOSTUNREACH or ENETUNREACH(applications should treat these two as the same error). It is also possible that the remote system is not reachable by any route in the local system’s forwarding table, or that the connect call returns without waiting at all.
  • We can see these different error conditions with our simple client from Figure 1.5. We first specify the local host(127.0.0.1), which is running the daytime server, and see the output.

  • In TCP state transition diagram(Figure 2.4): connect moves from the CLOSED state to the SYN_SENT state, and then, on success, to the ESTABLISHED state. If connect fails, the socket is no longer usable and must be closed. We cannot call connect again on the socket. In Figure 11.10: when we call connect in a loop, trying each IP address for a given host until one works, each time connect fails, we must close the socket descriptor and call socket again.

4.4 ‘bind’ Function

  • The bind function assigns a local protocol address to a socket which is the combination of either a 32-bit IPv4 address or a 128-bit IPv6 address, along with a 16-bit TCP or UDP port number.
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);Returns: 0 if OK,-1 on error
  • The second argument is a pointer to a protocol-specific address, and the third argument is the size of this address structure. With TCP, calling bind lets us specify a port number, an IP address, both, or neither.
  • If a TCP client/server does not bind port, the kernel chooses an ephemeral port for the socket when either connect or listen is called. It is normal for a TCP client to let the kernel choose an ephemeral port, unless the application requires a reserved port(Figure 2.10), but it is rare for a TCP server to let the kernel choose an ephemeral port.
  • Exceptions are Remote Procedure Call(RPC) servers. They normally let the kernel choose an ephemeral port for their listening socket since this port is then registered with the RPC port mapper. Clients have to contact the port mapper to obtain the ephemeral port before they can connect to the server. This also applies to RPC servers using UDP.
  • A process can bind a specific IP address to its socket. The IP address must belong to an interface on the host. For a TCP client, this assigns the source IP address that will be used for IP datagrams sent on the socket. For a TCP server, this restricts the socket to receive incoming client connections destined only to that IP address.
  • Normally, a TCP client does not bind an IP address to its socket. The kernel chooses the source IP address when the socket is connected, based on the outgoing interface that is used, which in turn is based on the route required to reach the server.
  • If a TCP server does not bind an IP address to its socket, the kernel uses the destination IP address of the client’s SYN as the server’s source IP address.
  • Figure 4.6 summarizes the values to which we set sin_addr and sin_port, or sin6_addr and sin6_port, depending on the desired result.

  • If we specify a port number of 0, the kernel chooses an ephemeral port when bind is called. If we specify a wildcard IP address, the kernel does not choose the local IP address until either the socket is connected(TCP) or a datagram is sent on the socket(UDP).
  • With IPv4, the wildcard address is specified by the constant INADDR_ANY, whose value is normally 0. This tells the kernel to choose the IP address.
struct sockaddr_in  servaddr;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* wildcard */
  • We cannot use this technique with IPv6, since the 128-bit IPv6 address is stored in a structure. We write
struct sockaddr_in6 serv;serv.sin6_addr = in6addr_any; /* wildcard */
  • The system allocates and initializes the in6addr_any variable to the constant IN6ADDR_ANY_INIT. The

4.5 ‘listen’ Function

  • The listen function is called only by a TCP server and it performs two actions:
    1. When a socket is created by the socket function, it is assumed to be an active socket, that is, a client socket that will issue a connect. The listen function converts an unconnected socket into a passive socket, indicating that the kernel should accept incoming connection requests directed to this socket. The call to listen moves the socket from the CLOSED state to the LISTEN state.
    2. The second argument specifies the maximum number of connections the kernel should queue for this socket.
#include <sys/socket.h>#int listen(int sockfd, int backlog);Returns: 0 if OK, -1 on error
  • This function is normally called after both the socket and bind functions and must be called before calling the accept function.
  • For a given listening socket, the kernel maintains two queues:
    1. An incomplete connection queue, which contains an entry for each SYN that has arrived from a client for which the server is awaiting completion of the TCP three-way handshake. These sockets are in the SYN_RCVD state.
    2. A completed connection queue, which contains an entry for each client with whom the TCP three-way handshake has completed. These sockets are in the ESTABLISHED state.

  • The backlog argument to the listen function has specified the maximum value for the sum of both queues.
  • When an entry is created on the incomplete queue, the parameters from the listen socket are copied over to the newly created connection. The connection creation mechanism is automatic; the server process is not involved.

  • When a SYN arrives from a client, TCP creates a new entry on the incomplete queue and then responds with the second segment of the three-way handshake: the server’s SYN with an ACK of the client’s SYN. This entry will remain on the incomplete queue until the third segment of the three-way handshake arrives(the client’s ACK of the server’s SYN), or until the entry times out. If the three-way handshake completes, the entry moves from the incomplete queue to the end of the completed queue. When the process calls accept, the first entry on the completed queue is returned to the process, or if the queue is empty, the process is put to sleep until an entry is placed onto the completed queue.
  • Berkeley-derived implementations add a factor to the backlog: It is multiplied by 1.5. For example, the specified backlog of 5 really allows up to 8 queued entries on these systems.

  • Do not specify a backlog of 0 since different implementations interpret this differently. If you do not want any clients connecting to your listening socket, close the listening socket.
  • What value should the application specify for the backlog?
    One method is to assume some default but allow a command-line option or an environment variable to override the default. It is acceptable to specify a value that is larger than supported by the kernel, as the kernel should silently truncate the value to the maximum value that it supports, without returning an error.
  • Figure 4.9: We allow the environment variable LISTENQ to override the value specified by the caller.

  • If the queues are full when a client SYN arrives, TCP ignores the arriving SYN, not send an RST. This is because the condition is considered temporary, and the client TCP will retransmit its SYN. If the server TCP immediately responded with an RST, the client could not differentiate between an RST in response to a SYN meaning “there is no server at this port” versus “there is a server at this port but its queues are full.”
  • Data that arrives after the three-way handshake completes, but before the server calls accept, should be queued by the server TCP, up to the size of the connected socket’s receive buffer.
  • SYN flooding: The hacker writes a program to send SYNs at a high rate to the victim, filling the incomplete connection queue for one or more TCP ports. Additionally, the source IP address of each SYN is set to a random number(this is called IP spoofing) so that the server’s SYN/ACK goes nowhere. This also prevents the server from knowing the real IP address of the hacker. By filling the incomplete queue with bogus SYNs, legitimate SYNs are not queued, providing a denial of service to legitimate clients.

4.6 ‘accept’ Function

  • accept is called by a TCP server to return the next completed connection from the front of the completed connection queue. If the completed connection queue is empty, the process is put to sleep.
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);Returns: non-negative descriptor if OK, -1 on error
  • The cliaddr and addrlen arguments are used to return the protocol address of the connected peer process(the client). addrlen is a value-result argument: Before the call, we set the integer value referenced by *addrlen to the size of the socket address structure pointed to by cliaddr; on return, this integer value contains the actual number of bytes stored by the kernel in the socket address structure.
  • If accept is successful, its return value is a new descriptor created by the kernel which refers to the TCP connection with the client.
  • We call the first argument to accept the listening socket(the descriptor created by socket and then used as the first argument to both bind and listen), and we call the return value from accept the connected socket. A given server normally creates only one listening socket, which then exists for the lifetime of the server. The kernel creates one connected socket for each client connection that is accepted(i.e., for which the TCP three-way handshake completes). When the server is finished serving a given client, the connected socket is closed.
  • This function returns three values: an integer return code that is either a new socket descriptor or an error indication, the protocol address of the client process(through the cliaddr pointer), and the size of this address(through the addrlen pointer). If we are not interested in having the protocol address of the client returned, we set both cliaddr and addrlen to null pointers.
  • Figure 1.9 shows these points. The connected socket is closed each time through the loop, but the listening socket remains open for the life of the server. We also see that the second and third arguments to accept are null pointers, since we were not interested in the identity of the client.

Example: Value-Result Arguments

#include <sys/socket.h>  // socket()#include <strings.h>  // bzero()#include <arpa/inet.h>  // htonl(), htons()#include <stdlib.h>  // getenv()#include <time.h>  // time()#include <stdio.h>#include <errno.h>  // perror()#include <unistd.h>  // close()#include <string.h>  // strlen()#define MAXLINE 4096#define LISTENQ 1024int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol)) < 0)    {        perror("socket error\n");        exit(1);    }    return n;}void Bind(int fd, const struct sockaddr *sa, socklen_t salen){    if(bind(fd, sa, salen) < 0)    {        perror("bind error");        exit(1);    }}void Listen(int fd, int backlog){    char *ptr;    if((ptr = getenv("LISTENQ")) != NULL)    {        backlog = atoi(ptr);    }    if(listen(fd, backlog) < 0)    {        perror("listen error\n");        exit(1);    }}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr){    int n;again:    if((n = accept(fd, sa, salenptr)) < 0)    {#ifdef EPROTO        if(errno == EPROTO || errno == ECONNABORTED)#else        if(errno == ECONNABORTED)#endif        {            goto again;        }        else        {            perror("accept error\n");            exit(1);        }    }    return n;}const char *Inet_ntop(int family, const void *addrptr, char *strptr, size_t len){    const char  *ptr;    if(strptr == NULL)  // check for old code    {        perror("NULL 3rd argument to inet_ntop");    }    if((ptr = inet_ntop(family, addrptr, strptr, len)) == NULL)    {        perror("inet_ntop error");  // sets errno    }    return(ptr);}void Write(int fd, void *ptr, size_t nbytes){    if(write(fd, ptr, nbytes) != nbytes)    {        perror("write error\n");        exit(1);    }}void Close(int fd){    if(close(fd) == -1)    {        perror("close error\n");        exit(1);    }}int main(int argc, char **argv){    int listenfd, connfd;    socklen_t len;    struct sockaddr_in servaddr, cliaddr;    char buff[MAXLINE];    time_t ticks;    listenfd = Socket(AF_INET, SOCK_STREAM, 0);  // <sys/socket.h>    bzero(&servaddr, sizeof(servaddr));  // <strings.h>    servaddr.sin_family = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // <arpa/inet.h>    servaddr.sin_port = htons(13);  // <arpa/inet.h>, daytime server    Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));    Listen(listenfd, LISTENQ);    for( ; ; )    {        len = sizeof(cliaddr);        connfd = Accept(listenfd,(struct sockaddr *)&cliaddr, &len);        printf("connection from %s, port %d\n",               Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),               ntohs(cliaddr.sin_port));        ticks = time(NULL);  // <time.h>        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));        Write(connfd, buff, strlen(buff));        Close(connfd);    }}

New declarations 7–8

  • len: a value-result variable, cliaddr: contain the client’s protocol address.

Accept connection and print client’s address 19–23

  • We initialize len to the size of the socket address structure and pass a pointer to the cliaddr structure and a pointer to len as the second and third arguments to accept. We call inet_ntop(Section 3.7) to convert the 32-bit IP address in the socket address structure to a dotted-decimal ASCII string and call ntohs(Section 3.4) to convert the 16-bit port number from network byte order to host byte order.
  • Since daytime client(Figure 1.5) does not call bind, the kernel chooses the source IP address based on the outgoing interface that is used.
    1. The kernel sets the source IP address to the loopback address.
    2. It sets the address to the IP address of the Ethernet interface.
$ ./1-5-daytimetcpcli 127.0.0.100Thu Aug  4 21:52:08 2016$ ./1-5-daytimetcpcli 192.168.0.104Thu Aug  4 21:52:37 2016$ sudo ./4-11-daytimetcpsrv1 connection from 127.0.0.1, port 43356connection from 192.168.0.104, port 56486

4.7 ‘fork’ and ‘exec’ Functions

#include <unistd.h>pid_t fork(void);Returns: 0 in child, process ID of child in parent, -1 on error
  • fork is called once but it returns twice: once in the calling process(parent) with a return value that is the process ID of the newly created process(child), once in the child with a value of 0.
  • All descriptors open in the parent before the call to fork are shared with the child after fork returns. This feature is used by network servers: The parent calls accept and then calls fork. The connected socket is then shared between the parent and child. Normally, the child then reads and writes the connected socket and the parent closes the connected socket.
  • Two typical uses of fork:
    1. A process makes a copy of itself so that one copy can handle one operation while the other copy does another task. This is typical for network servers.
    2. A process wants to execute another program. The process first calls fork to make a copy of itself, and then one of the copies(typically the child) calls exec to replace itself with the new program. This is typical for programs such as shells.
  • The only way in which an executable program file can be executed by Unix is for an existing process to call one of the six exec functions. exec replaces the current process image with the new program file, and this new program normally starts at the main function. The process ID does not change. We refer to the process that calls exec as the calling process and the newly executed program as the new program.
  • The differences in the six exec functions are:
    (a) whether the program file to execute is specified by a filename or a pathname;
    (b) whether the arguments to the new program are listed one by one or referenced through an array of pointers;
    (c) whether the environment of the calling process is passed to the new program or whether a new environment is specified.
#include <unistd.h>int execl(const char *pathname, const char *arg0, ... /*(char *) 0 */ );int execv(const char *pathname, char *const argv[]);int execle(const char *pathname, const char *arg0, ... /*(char *) 0, char *const envp[] */ );int execve(const char *pathname, char *const argv[], char *const envp[]);int execlp(const char *filename, const char *arg0, ... /*(char *) 0 */ );int execvp(const char *filename, char *const argv[]);All six return: -1 on error, no return on success
  • These functions return to the caller only if an error occurs. Otherwise, control passes to the start of the new program, normally the main function.
  • Only execve is a system call within the kernel and the other five are library functions that call execve.

  • The three functions in the top row specify each argument string as a separate argument to the exec function, with a null pointer terminating the variable number of arguments. The three functions in the second row have an argv array, containing pointers to the argument strings. This argv array must contain a null pointer to specify its end, since a count is not specified.
  • The two functions in the left column specify a filename argument. This is converted into a pathname using the current PATH environment variable. If the filename argument to execlp or execvp contains a slash(/) anywhere in the string, the PATH variable is not used. The four functions in the right two columns specify a fully qualified pathname argument.
  • The four functions in the left two columns do not specify an explicit environment pointer. The current value of the external variable environ is used for building an environment list that is passed to the new program. The two functions in the right column specify an explicit environment list. The envp array of pointers must be terminated by a null pointer.
  • Descriptors open in the process before calling exec normally remain open across the exec. But they can be disabled using fcntl to set the FD_CLOEXEC descriptor flag.

4.8 Concurrent Servers

  • The server in Figure 4.11 is an iterative server. One way to write a concurrent server under Unix is to fork a child process to handle each client. Figure 4.13 shows the outline for a typical concurrent server.

  • When a connection is established, accept returns, the server calls fork, and the child process services the client(on connfd, the connected socket) and the parent process waits for another connection(on listenfd, the listening socket). The parent closes the connected socket since the child handles the new client.
  • When doit function returns, we explicitly close the connected socket in the child. This is not required since kernel will close all open descriptors when calling exit.
  • Calling close on a TCP socket causes a FIN to be sent, followed by the normal TCP connection termination sequence. Why doesn’t the close of connfd by the parent terminate its connection with the client?
    1. Every file or socket has a reference count which is the number of descriptors that are currently open that refer to this file or socket and it is maintained in the file table entry(APUE P57–60).
    2. In Figure 4.13, after socket returns, the file table entry associated with listenfd has a reference count of 1. After accept returns, the file table entry associated with connfd has a reference count of 1. After fork returns, both descriptors are shared(i.e., duplicated) between the parent and child, so the file table entries associated with both sockets now have a reference count of 2.
    3. When the parent closes connfd, it decrements the reference count from 2 to 1. The actual cleanup and deallocation of the socket does not happen until the reference count reaches 0. This will occur when the child closes connfd.
  • First, Figure 4.14 shows the status of the client and server while the server is blocked in the call to accept and the connection request arrives from the client.

  • Immediately after accept returns, we have the scenario shown in Figure 4.15. The connection is accepted by the kernel and a new socket, connfd, is created.

  • The next step in the concurrent server is to call fork. Figure 4.16 shows the status after fork returns.

  • The next step is for the parent to close the connected socket and the child to close the listening socket. This is shown in Figure 4.17.

  • This is the final state of the sockets. The child is handling the connection with the client and the parent can call accept again on the listening socket, to handle the next client connection.

4.9 ‘close’ Function

  • close function is used to close a socket and terminate a TCP connection.
#include <unistd.h>int close(int sockfd);Returns: 0 if OK, -1 on error
  • The default action of close with a TCP socket is to mark the socket as closed and return to the process immediately. The socket descriptor is no longer usable by the process: It cannot be used as an argument to read or write. But TCP will try to send any data that is already queued to be sent to the other end, and after this occurs, the normal TCP connection termination sequence takes place.

Descriptor Reference Counts

  • What happens in concurrent server if the parent does not call close for each connected socket returned by accept.
    1. First, the parent will eventually run out of descriptors, as there is usually a limit to the number of descriptors that any process can have open at any time.
    2. Second, none of the client connections will be terminated. When the child closes the connected socket, its reference count will go from 2 to 1 and it will remain at 1 which will prevent TCP’s connection termination sequence from occurring, and the connection will remain open.

4.10 ‘getsockname’ and ‘getpeername’ Functions

  • getsockname: return the local protocol address associated with a socket;
    getpeername: return the foreign protocol address associated with a socket.
#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);Both return: 0 if OK, -1 on error
  • Both functions fill in the socket address structure pointed to by localaddr or peeraddr. These two functions return the protocol address associated with one of the two ends of a network connection, which for IPV4 and IPV6 is the combination of an IP address and port number.
  • These two functions are required for the following reasons:
    1. After connect successfully returns in a TCP client that does not call bind, getsockname returns the local IP address and local port number assigned to the connection by the kernel.
    2. After calling bind with a port number of 0(telling the kernel to choose the local port number), getsockname returns the local port number that was assigned.
    3. getsockname can be called to obtain the address family of a socket, as shown in Figure 4.19.
    4. In a TCP server that binds the wildcard IP address(Figure 1.9), once a connection is established with a client(accept returns successfully), the server can call getsockname to obtain the local IP address assigned to the connection. The socket descriptor argument in this call must be that of the connected socket, and not the listening socket.
    5. When a server is execed by the process that calls accept, the only way the server can obtain the identity of the client is to call getpeername. This is what happens whenever inetd(Section 13.5) forks and execs a TCP server.

  • inetd calls accept(top left box) and two values are returned: the connected socket descriptor(connfd) is the return value of the function, and the peer’s address(an Internet socket address structure) contains the IP address and port number of the client. fork is called and a child of inetd starts with a copy of the parent’s memory image. When the child execs the real server(say the Telnet server), the memory image of the child is replaced with the new program file for the Telnet server(i.e., the socket address structure containing the peer’s address is lost), and the connected socket descriptor remains open across the exec. The first function calls performed by the Telnet server is getpeername to obtain the IP address and port number of the client.
  • The Telnet server in this example must know the value of connfd when it starts. Two common ways to do this.
    1. First, the process calling exec can format the descriptor number as a character string and pass it as a command-line argument to the newly execed program.
    2. Second, a convention can be established that a certain descriptor is always set to the connected socket before calling exec.
  • The latter is what inetd does, always setting descriptors 0, 1, and 2 to be the connected socket.

Allocate room for largest socket address structure 5

  • Since we do not know what type of socket address structure to allocate, we use a sockaddr_storage value, since it can hold any socket address structure supported by the system.

Call getsockname 7–10

  • We call getsockname and return the address family.

4.11 Summary

Exercises 4.1

In Section 4.4, we stated that the INADDR_ constants defined by the

#include <netinet/in.h> // struct sockaddr_in{}#include <stdio.h>  // printf(), fputs()#include <sys/socket.h>  // socket(), connect(), struct sockaddr {}#include <strings.h>  // bzero()#include <arpa/inet.h>  // htons()#include <unistd.h>  // read()#include <stdlib.h>  // exit()#include <errno.h>#include <string.h>  // strcat()#define MAXLINE 4096int Socket(int family, int type, int protocol){    int n;    if((n = socket(family, type, protocol))  == -1)    {        perror("socket error\n");        exit(1);    }    return n;}void Inet_pton(int family, const char *strptr, void *addrptr){    int n;    if ((n = inet_pton(family, strptr, addrptr)) <= 0)    {        printf("inet_pton error for %s\n", strptr);        exit(1);    }}void Connect(int fd, const struct sockaddr *sa, socklen_t salen){    if(connect(fd, sa, salen) < 0)    {        printf("connect error\n");        exit(1);    }}void Getsockname(int fd, struct sockaddr *sa, socklen_t *salenptr){    if (getsockname(fd, sa, salenptr) < 0)    {        printf("getsockname error");        exit(1);    }}char *sock_ntop(const struct sockaddr *sa, socklen_t salen){    char        portstr[8];    static char str[128];       /* Unix domain is largest */    struct sockaddr_in  *sin = (struct sockaddr_in *) sa;    if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)    {        return(NULL);    }    if (ntohs(sin->sin_port) != 0)    {        snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));        strcat(str, portstr);    }    return(str);}char *Sock_ntop(const struct sockaddr *sa, socklen_t salen){    char    *ptr;    if((ptr = sock_ntop(sa, salen)) == NULL)    {        printf("sock_ntop error");  /* inet_ntop() sets errno */        exit(1);    }    return(ptr);}void Fputs(const char *ptr, FILE *stream){    if (fputs(ptr, stream) == EOF)    {        printf("fputs error\n");        exit(1);    }}int main(int argc, char **argv){    int sockfd, n;    char recvline[MAXLINE + 1];    struct sockaddr_in servaddr;  // <netinet/in.h>    if (argc != 2)    {        printf("usage: a.out <IPaddress>\n");        exit(1);  // #include <stdlib.h>    }    sockfd = Socket(AF_INET, SOCK_STREAM, 0);  // <sys/socket.h>    bzero(&servaddr, sizeof(servaddr));  // #include <strings.h>    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(13);  // #include <arpa/inet.h>; 13: daytime server    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);  // #include <arpa/inet.h>    Connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));    // Exercise 4.2: Add getsockname()    struct sockaddr_in cliaddr;    socklen_t len = sizeof(cliaddr);    Getsockname(sockfd, (struct sockaddr *)&cliaddr, &len);    printf("local address: %s\n", Sock_ntop((struct sockaddr *)&cliaddr, len));    // End    while((n = read(sockfd, recvline, MAXLINE)) > 0)  // #include <unistd.h>    {        recvline[n] = 0;    /* null terminate */        Fputs(recvline, stdout);    }    if (n < 0)    {        printf("read error\n");        exit(1);    }    exit(0);}$ ./ex4-2 127.0.0.6local address: 127.0.0.1:33999Fri Aug  5 12:03:32 2016$ ./ex4-2 127.0.0.6local address: 127.0.0.1:34000Fri Aug  5 12:03:41 2016$ ./ex4-2 127.0.0.6local address: 127.0.0.1:34001Fri Aug  5 12:03:42 2016

Exercises 4.3

In a concurrent server, assume the child runs first after the call to fork. The child then completes the service of the client before the call to fork returns to the parent. What happens in the two calls to close in Figure 4.13?

  • When the child calls close, the reference count is decremented from 2 to 1, so a FIN is not sent to the client. Later, when the parent calls close, the reference count is decremented to 0 and the FIN is sent.

Exercises 4.4

In Figure 4.11, first change the server’s port from 13 to 9999 (so that we do not need super-user privileges to start the program). Remove the call to listen. What happens?

  • accept returns EINVAL, since the first argument is not a listening socket.
    EINVAL: Socket is not listening for connections, or addrlen is invalid(e.g., is negative).

Exercises 4.5

Continue the previous exercise. Remove the call to bind, but allow the call to listen. What happens?

  • Without a call to bind, the call to listen assigns an ephemeral port to the listening socket. If we still connect (IP-Address, 13), result is “connect error”.

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

0 0
原创粉丝点击