[Operating System - Network Programming] Socket Communication Server&Client (Full Duplex Socket, Full Duplex Socket)

Lab Week 09 Experiment Report

Experiment content: interprocess communication - pipe and socket communication

  • Compile and run the sample codes alg.11-1, alg.11-2.1, alg.11-2.2 of the courseware Lecture11, point out what you think is inappropriate and make improvements.

I. Socket communication

In Socket network programming, there is a process as shown in the following figure:

image-20220409174357295

Socket network programming is mainly to establish a connection between two network nodes (Socket): Client and Server, in order to communicate with each other, where Server listens on the specified IP address and port number, and Client actively establishes a connection with the Server. connect.

The following picture is also the same principle:

image-20220409182834905

0. Communication protocol

  1. TCP (Transmission Control Protocol, TCP, Transmission Control Protocol)

    Transmission Control Protocol (TCP, Transmission Control Protocol) is a connection-oriented, reliable, byte stream- based transport layer communication protocol.

  2. UDP (User Datagram Protocol, UDP, User Datagram Protocol))

    The UDP protocol is a connectionless-oriented protocol. When transmitting data, there is no need to establish a connection. No matter whether the service on the other side is started, the data, data source and destination are directly encapsulated in the data packet and sent directly. The size of each packet is limited to 64k. It is an unreliable protocol, because there is no connection, so the transmission speed is fast, but it is easy to lose data.

1. Related functions

[Function description refers to the function Reference in man7 Linux manual pages: section 3 ]

  1. socket()(header file <sys/socket.h>)

    Function prototype:int socket(int __domain, int __type, int __protocol)

    Function function: create a socket, and return a file descriptor fd, which identifies a unique socket;

    The domain parameter specifies the communication protocol family, such as AF_INET: IPv4 protocol, AF_INET6: IPv6 protocol;

    ​ The type parameter specifies the type of Socket, such as SOCK_STREAM: byte stream TCP (reliable, connection oriented),

    ​ SOCK_DGRAM:UDP(unreliable, connectionless)

    ​ The protocol parameter specifies the transmission protocol, usually set to 0 (the system automatically selects according to the type), and there are also some commonly used protocols IPPROTO_TCP: TCP transmission protocol, IPPTOTO_UDP: UDP transmission protocol;

2. bind()(header file <sys/socket.h>)

Function prototype:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

Function: Assign the specific address of the socket specified by sockfd. socket()The socket created by is initially unnamed, and only the protocol family is used to identify it.

For example: the socket of the IPv4 protocol assigns the combination of IPv4 address and port number to the socket.

The three parameters of the function are:

  • sockfd: socket()the file descriptor returned by the socket created
  • addr: A pointer of const struct sockaddr type, pointing to the protocol address (IP+port number) to be bound to sockfd, IPv4 corresponds to:
struct in_addr {
    
    
    unsigned long s_addr;
};
struct sockaddr_in {
    
    
    short int sin_family; /* 2 bytes, AF_INET for ipv4 */
    unsigned short int sin_port; /* 2 bytes, Port number */
    struct in_addr sin_addr; /* 4 bytes, IP address */
    unsigned char sin_zero[8]; /* padding with 0 to keep the same size as
    struct sockaddr (16bytes) */
};

​ Before calling bind, you need to initialize the parameters sin_family, sin_port, sin_addr, which correspond to the communication protocol, port number and IP address of the socket respectively.

Note that if the port number is set to 0, the operating system will randomly assign an available listening port to the program.

  • addrlen: the length of addr;

​Return value: return 0 on success, return -1 on failure


[The following functions involve type conversion]

  1. htonl()(Header file <sys/socket.h>) The letters correspond to h:host, n:net, l:long

​ Here comes the concept of host byte order (HBO, Host Byte Order) and network byte order NBO (Network Byte Order) :

​ Among them, the host byte order is big-endian or little-endian, and the network byte order is generally specified by the communication protocol. For example, the UDP/TCP/IP protocol stipulates: the first received Bytes are treated as high-order bytes, which requires the first byte sent by the sender to be the high-order byte; and when the sender sends data, the first byte sent is the beginning of the value in memory The byte corresponding to the address, that is to say, the byte corresponding to the start address of the value in the memory is the first high-order byte to be sent, that is, the high-order byte is stored at the low address. It can be seen that multi-byte values ​​are stored in big-endian mode in memory before being sent ;

Function prototype: uint32_t htonl(uint32_t __hostlong)

Function function: convert hostlong, that is, host byte order (related to the machine) into network byte order (big endian)

  1. ntohl()(header file <sys/socket.h>) net to host long

Function prototype:int getsockname(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len)

Function: Convert an unsigned long integer from network to host byte order.

  1. inet_ntoa()(header file <arpa/inet.h>) n: numeric (number, binary value of address)

​Function prototype:char *inet_ntoa (struct in_addr);

​Function function: Convert the network address (internet number) into a string format separated by ".", and return the pointer of the dotted decimal string in the static memory.

​In IPV4, 4 bytes are used to represent an IP address, and each byte is expressed as 0~255 in decimal.

​ Dotted decimal is to use 4 numbers from 0 to 255 to represent an IP address. Such as 192.168.1.1

  1. inet_pton()(Header file <arpa/inet.h>) p: presentation (expression, address format), n: numeric (value, binary value of address)

    (Convert IPv4 and IPv6 addresses from text to binary)

    Function prototype:int inet_pton(int family, const char *strptr, void *addrptr);

    Function function: This function and the function below inet_ntop()are inverse transformations. This function mainly converts the address in IPv4 or IPv6 format (specified by the parameter af, that is, AF_INET or AF_INET6, and the address is stored in strptr) into the corresponding binary value And save it in the space pointed to by the pointer addrptr.

  2. inet_ntop()(header file <arpa/inet.h>)

    Function prototype:const char *inet_ntop(int af, const void *addrptr, char *strptr, size_t len)

    Function function: This function mainly converts the binary value (stored in the pointer addrptr) corresponding to the address in IPv4 or IPv6 format (specified by parameter af, namely AF_INET or AF_INET6) into IPv4 or IPv6 address format, and stores it in the pointer strptrt In space, the parameter len is used to indicate the size of the target storage unit.


  1. getsocketname()(header file <sys/socket.h>)

Function prototype:int getsockname(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len)

Function function: If the port number initialized in the addr structure is 0 (that is, randomly assigned by the system) before calling bind(), then you need to call this function if you want to get this port number, and the corresponding parameter is in restrict addressbind For addr in (), the port number will be stored in the sin_port parameter in the structure after the call is completed.

Return value: return 0 on success, return -1 on failure

  1. setsockopt()(header file <sys/socket.h>)

Function prototype:int setsockopt( int socket, int level, int option_name, const void *option_value, size_t ,ption_len)

Function: Provide multiple options for the socket, such as SO_REUSEADDR, SO_REUSEPORT, which can allow IP addresses or port numbers to be reused, avoiding “address already in use”errors such as:

If you want to set options at the socket level, you must set the level to SOL_SOCKET

  1. listen()(header file <sys/socket.h>)

Function prototype:int listen(int socket, int backlog)

Function: Convert the specified socket from an active socket to a passive socket (passive mode), or set the socket to a connection-mode socket. At this time, the socket can accept requests from other sockets. Among them, the number of requests Restricted by the backlog, a queue (listen queue) is maintained in the socket for processing connections (pending) that have not yet been accepted or are in progress. The backlog here limits the size of the queue.

​Usually called afterlisten() bind()and beforeaccept()

  1. accept()(header file <sys/socket.h>)

    (It extracts the first connection request on the queue of pending connections for the listening socket)

Function prototype:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

Function function: the socket in the listening state (identified by sockfd) will wait for the request of the client socket and accept it when the request arrives. The addr pointer in the parameter is used to store the information of the client socket, including the IP address and port number, addrlen is the size of addr

Return value: return the sockfd of the successfully accepted socket

  1. connect()(header file <sys/socket.h>)

Function prototype:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen)

Function function: used to establish a connection between the client (Client) and the server (Server), the parameter sockfd corresponds to the descriptor of the client, and the serv_addr pointer is the information of the server, including sin_family, sin_port, sin_addr

  1. send()(header file <sys/socket.h>)

Function prototype:ssize_t send(int __fd, const void __*buf, size_t __n, int __flags)

Function: Send n bytes message from buf to the socket specified by fd

Return value: return the number of successful sending, and return -1 if it fails

  1. recv()(header file <sys/socket.h>)

Function prototype:ssize_t recv(int __fd, void *__buf, size_t __n, int __flags)

Function function: read n bytes from the socket specified by fd to buf

Return value: Returns the number of successful reads, and returns -1 if it fails

  1. getifaddrs()(header file <ifaddrs.h>)

Function prototype:int getifaddrs(struct ifaddrs **ifap);

Function: used to create a structure linked list describing the network interface of the local system, and store the address of the first item in the list in *ifap. The list consists of the ifaddrs structure, defined as follows:

struct ifaddrs {
    
     /* interface address, link list structured */
    struct ifaddrs *ifa_next; /* next item in link list */
    char *ifa_name; /* name of interface */
    unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
    struct sockaddr *ifa_addr; /* address of interface */
    struct sockaddr *ifa_netmask; /* netmask of interface */
    union {
    
    
    struct sockaddr *ifu_broadaddr;
    /* broadcast address of interface */
    struct sockaddr *ifu_dstaddr;
    /* point-to-point destination address */
    } ifa_ifu;
    #define ifa_broadaddr ifa_ifu.ifu_broadaddr
    #define ifa_dstaddr ifa_ifu.ifu_dstaddr
    void *ifa_data; /* address-specific data */
};

2. Related data structures

in_addr structure:

struct in_addr {
    
    
    unsigned long s_addr;
};

sockaddr structure:

struct sockaddr {
    
      
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
};

In the sockaddr structure, there is a certain defect, sa_data mixes the target address and port information, and the following sockaddr_in structure solves this defect;

sockaddr_in structure:

struct sockaddr_in {
    
    
    short int sin_family; /* 2 bytes, AF_INET for ipv4 */
    unsigned short int sin_port; /* 2 bytes, Port number */
    struct in_addr sin_addr; /* 4 bytes, IP address */
    unsigned char sin_zero[8]; /* padding with 0 to keep the same size as struct sockaddr (16bytes) */
};

The sockaddr_in structure stores port and addr separately in two variables, namely sin_port and sin_addr;

The size of sockaddr and sockaddr_in is the same, both are 16 bytes, 2 bytes of which are used for the address family, which can be found in sockaddr_in, the extra variable sin_zero is mainly to keep the same size as sockaddr, and it is usually set to zero.

In network programming, you need to use accept, getsockname, bind, connect and other functions; just remember to use the sockaddr_in structure when filling in the value, and convert it to the sockaddr structure when passing it in as a function parameter. 16 bytes.

[Get network interface address]

ifaddrs structure:

struct ifaddrs {
    
     /* interface address, link list structured */
    struct ifaddrs *ifa_next; /* next item in link list */
    char *ifa_name; /* name of interface */
    unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
    struct sockaddr *ifa_addr; /* address of interface */
    struct sockaddr *ifa_netmask; /* netmask of interface */
    union {
    
    
    struct sockaddr *ifu_broadaddr;
    /* broadcast address of interface */
    struct sockaddr *ifu_dstaddr;
    /* point-to-point destination address */
    } ifa_ifu;
    #define ifa_broadaddr ifa_ifu.ifu_broadaddr
    #define ifa_dstaddr ifa_ifu.ifu_dstaddr
    void *ifa_data; /* address-specific data */
};

Hostent structure: (host entries)

struct hostent {
    
    
      char  *h_name;            /* official name of host */
      char **h_aliases;         /* alias list */
      int    h_addrtype;        /* host address type */
      int    h_length;          /* length of address */
      char **h_addr_list;       /* list of addresses */
}

3. Routine code and description

A. Obtain the IP address and port number:

alg.11-1-socket-port.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
    /* #include <arpa/inet.h>
        uni32_t htonl(uni32_t hostlong); // host to net long int
        uni16_t htonl(uni16_t hostshort); // host to net short int
        uni32_t ntohl(uni32_t netlong); // net to host long int
        uni16_t ntohl(uni16_t netshort); // net to host short int
        unsigned long inet_addr(const char* cp); // cp e.g., "192.168.10.130"
        char* inet_ntoa(struct in_addr in);
    */

int main(void)
{
    
    
    unsigned short port = 0;
    int sockfd, ret, ret_val = 1;
    struct sockaddr_in myaddr;
    socklen_t addr_len;
    char ip_name_str[INET_ADDRSTRLEN];

    sockfd = socket(AF_INET, SOCK_STREAM, 0); /* AF_INET: ipv4 */
    if(sockfd == -1) {
    
    
        perror("socket()");
        return EXIT_FAILURE;
    }

    myaddr.sin_family = AF_INET; /* ipv4 */
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* INADDR_ANY: 0 */
    myaddr.sin_port = 0; /* if .sin_port is set to 0, a free port number allocated by bind() */
    addr_len = sizeof(myaddr);
    ret = bind(sockfd, (struct sockaddr *)&myaddr, addr_len);
        /* bind() not return the port number to myaddr: bind(int, const struct sockaddr, socklen_t)
           use getsockname() to get the allocated port number */
    if(ret == 0) {
    
    
        addr_len = sizeof(myaddr);
        ret = getsockname(sockfd, (struct sockaddr *)&myaddr, &addr_len);
        if(ret == 0) {
    
    
            port = ntohs(myaddr.sin_port);
            printf("port number = %d\n", port);
            strcpy(ip_name_str, inet_ntoa(myaddr.sin_addr));
            printf("host addr = %s\n", ip_name_str);
        } else {
    
    
            ret_val = 0;
        }
    } else {
    
    
        ret_val = 0;
    }

    ret = close(sockfd); /* close() defined in <unistd.h> */
    if(ret != 0) {
    
    
        ret_val = 0;
    }

    return ret_val;
}

Code description:

This program mainly uses IPv4 as the communication protocol, and uses socket()the function to create a socket. bind()Before calling the function to bind the socket, you need to sockaddr_inassign values ​​to the relevant parameters in the type structure (you need to convert the host byte order into network characters Sequence, use htonl()), including communication protocol family, IP address and port number (set to 0 in the routine code, allocated by bind()), and then call the bind()function to bind the contents of the structure with the socket, Since the port number in the routine code is randomly assigned, it is necessary to call getsockname()the function to obtain the randomly assigned port number;

It should be noted that the IP address and port number obtained at this time are stored in network byte order (big-endian), so it is necessary to call ntohs()to convert the port number into host byte order (usually we use little-endian machines, It doesn’t matter if it’s a big-endian machine), the IP address needs to be format converted, and the unsigned long integer in binary format is converted into a dotted decimal string, which is convenient for users to identify.

Finally, print out the IP address and port number.

operation result:

image-20220409151558165

It is found that the port number assigned by the system is 45117, and the IP address is assigned INADDR_ANY0.0.0.0 because it is specified

B. Server&Client

This part of the process has been described in detail in the figure at the beginning, and the following code is to implement the direct communication between Server and Client.

Routine code:

alg.11-2.1-socket-server.c

/* one client, one server, asynchronous send-receive */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/signal.h>

#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5

#define ERR_EXIT(m) \
    do {
      
       \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

    /* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
 {
    
    
    char* ret_val;
    int i = 0;

    ret_val = fgets(stdin_buf, n, stdin);
    if(ret_val) {
    
    
        while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
    
    
            i++;
        }
        if(stdin_buf[i] == '\n') {
    
    
            stdin_buf[i] = '\0';
        } else {
    
    
            while (getchar() != '\n') ;
        }
    }

    return ret_val;
}

    /* save the ipv4 address of this server to ip_addr */
int getipv4addr(char *ip_addr)
{
    
    
    struct ifaddrs *ifaddrsptr = NULL;
    struct ifaddrs *ifa = NULL;
    void *tmpptr = NULL;
    int ret;
    
    ret = getifaddrs(&ifaddrsptr);
    if (ret == -1) {
    
    
        ERR_EXIT("getifaddrs()");
    }

    for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) {
    
    
        if(!ifa->ifa_addr) {
    
    
            continue;
        }
        if(ifa->ifa_addr->sa_family == AF_INET) {
    
     /* AF_INET: ipv4 */
            tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addr_buf[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN); // 将二进制格式转换成IPv4协议的点分十进制格式
            printf("%s ipv4 address = %s\n", ifa->ifa_name, addr_buf);
            if (strcmp(ifa->ifa_name, "lo") != 0) {
    
     // lo: local,系统虚拟的环回接口
                strcpy(ip_addr, addr_buf); /* save the ipv4 address */
            }
        } else if(ifa->ifa_addr->sa_family == AF_INET6) {
    
     /* ipv6 */
            tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addr_buf[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN);
            printf("%s ipv6 address %s\n", ifa->ifa_name, addr_buf);
        }
    }

    if (ifaddrsptr != NULL) {
    
    
        freeifaddrs(ifaddrsptr);
    }

    return EXIT_SUCCESS;
}


int main(void)
{
    
    
    int server_fd, connect_fd;
    struct sockaddr_in server_addr, connect_addr;
    socklen_t addr_len;
    int recvbytes, sendbytes, ret;
    char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
    char ip_addr[INET_ADDRSTRLEN]; /* ipv4 address */
    char stdin_buf[BUFFER_SIZE];
    uint16_t port_num;
    pid_t childpid;

    server_fd = socket(AF_INET, SOCK_STREAM, 0); /* create an ipv4 server*/
    if(server_fd == -1) {
    
    
        ERR_EXIT("socket()");
    }
    printf("server_fd = %d\n", server_fd);

    ret = getipv4addr(ip_addr); /* get server ipv4 address */
    if (ret == EXIT_FAILURE) {
    
    
        ERR_EXIT("getifaddrs()");
    }
    
        /* set sockaddr_in */
    server_addr.sin_family = AF_INET; /* ipv4 */
    server_addr.sin_port = 0; /* auto server port number */
    server_addr.sin_addr.s_addr = inet_addr(ip_addr);  /* unsigned long s_addr */
    bzero(&(server_addr.sin_zero), 8); /* padding with 0s */

    int opt_val = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); /* many options */
    
    addr_len = sizeof(struct sockaddr);
    ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len);
    if(ret == -1) {
    
    
        close(server_fd);
        ERR_EXIT("bind()");    
    }
    printf("Bind success!\n");

    addr_len = sizeof(server_addr);
    ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &addr_len);
    if(ret == 0) {
    
    
        printf("Server port number = %d\n", ntohs(server_addr.sin_port)); // ntohs: net to host (short: 2 bytes)
    } else {
    
    
        close(server_fd);
        ERR_EXIT("Server port number unknownn");
    }
    ret = listen(server_fd, MAX_QUE_CONN_NM); 
    if(ret == -1) {
    
    
        close(server_fd);
        ERR_EXIT("listen()");
    }
    printf("Server ipv4 addr: %s\n", ip_addr);
    printf("Listening ...\n");
    
    addr_len = sizeof(struct sockaddr);
        /* addr_len should be refreshed before each accept() */
    connect_fd = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len);
        /* waiting for connection */
    if(connect_fd == -1) {
    
    
        close(server_fd);
        ERR_EXIT("accept()");
    }

    port_num = ntohs(connect_addr.sin_port);
    strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
    printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);

    childpid = fork();
    if(childpid < 0) {
    
    
        close(server_fd);
        close(connect_fd);
        ERR_EXIT("fork()");
    }
    if(childpid > 0) {
    
     /* parent pro */
        while(1) {
    
     /* sending cycle */
            memset(send_buf, 0, BUFFER_SIZE);
            s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
            sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
            if(sendbytes <= 0) {
    
    
                printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
                break;
            }
            if(strncmp(send_buf, "end", 3) == 0) {
    
    
                break;
            }
        }
        close(server_fd);
        close(connect_fd);
        kill(childpid, SIGKILL);
    }
    else {
    
     /* child pro */
        while(1) {
    
     /* receiving cycle */
            memset(recv_buf, 0, BUFFER_SIZE);
            recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
            if(recvbytes <= 0) {
    
    
                printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
                break;
            }
            printf("\t\t\t\t\tClient %s >>>> %s\n", ip_addr, recv_buf);
            if(strncmp(recv_buf, "end", 3) == 0) {
    
    
                break;
            }
        }
        close(connect_fd);
        close(server_fd);
        kill(getppid(), SIGKILL);
    }

    return EXIT_SUCCESS;
}

Code description:

This part plays the role of the server (server), and still uses the IPv4 protocol to create a socket, and this socket is used as the server.

Main process:socket() -> bind()-> listen()-> accept()After arriving here, you need to wait for the connection of the client. After the connection is successful, you can send and receive messages through system calls send()and .recv()

Next, give a detailed description of the details in the program:

  1. Obtain the IP address based on IPv4 protocol: The function is encapsulated in the program getipv4addr()to obtain the local IPv4 address. This function uses getifaddrs()to create a structural linked list describing the network interface of the local system, and obtain the IP address from it and save it ip_addr in .
  2. Put the address information into the structure of struct sockaddr_in type: defined in the code struct sockaddr_in server_addr, in this structure, you need to specify the communication protocol, IP address, port number, and at the same time, you need to perform type conversion when assigning these parameters. For example, the IP address needs to call inet_addr()the function to convert the dotted decimal IP address format into an unsigned long unsigned long integer. Here, the value of the port number is still assigned a value of 0, and the port number is assigned by the system.
  3. Print out the IP address and port number information: By calling getsockname(), you can put the port number assigned by the system into server_addrand print out the address information, which is convenient for the client to connect.
  4. listen and accept: After the above preparations are completed, the server needs to enter the listening state and wait for the client's connection request. After the connection is successful, the accept()function will put the client's address information into struct sockaddr_inthe type structure connect_addrand print it out. Note that type conversion is still required before this. accept()The return value of the function is the sockfd of the client, that is, the socket file descriptor, which uniquely identifies the client.
  5. Message passing: After the connection is successful, you can communicate between the server and the client. In the code, it is mainly used to fork()create a child process, in which the parent process is responsible for reading the information entered by the keyboard and sending it to the client. The process is responsible for receiving the information sent by the client. Note that the parent process and the child process here are executed concurrently.

Routine code:

alg.11-2.2-socket-client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <fcntl.h>
#include <sys/stat.h>

#define BUFFER_SIZE 1024
#define ERR_EXIT(m) \
    do {
      
       \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


     /* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
{
    
    
    char* ret_val;
    int i = 0;

    ret_val = fgets(stdin_buf, n, stdin);
    if(ret_val) {
    
    
        while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
    
    
            i++;
        }
        if(stdin_buf[i] == '\n') {
    
    
            stdin_buf[i] = '\0';
        } else {
    
    
            while (getchar() != '\n') ;
        }
    }

    return ret_val;
}

int main(void)
{
    
    
    int connect_fd, sendbytes, recvbytes, ret;
    uint16_t port_num;
    char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
    char ip_addr[INET_ADDRSTRLEN], stdin_buf[BUFFER_SIZE];
    char clr;
    struct hostent *host;
    struct sockaddr_in server_addr, connect_addr;
    socklen_t addr_len;
    pid_t childpid;
    
    printf("Input server's hostname/ipv4: "); /* www.baidu.com or an ipv4 address */
    scanf("%s", stdin_buf);
    while((clr = getchar()) != '\n' && clr != EOF); /* clear the stdin buffer */
    printf("Input server's port number: ");
    scanf("%hu", &port_num);
    while((clr = getchar()) != '\n' && clr != EOF);

    host = gethostbyname(stdin_buf);
    if(host == NULL) {
    
    
        printf("invalid name or ip address\n");
        exit(EXIT_FAILURE);
    }
    printf("server's official name = %s\n", host->h_name);
    char** ptr = host->h_addr_list;
    for(; *ptr != NULL; ptr++) {
    
    
        inet_ntop(host->h_addrtype, *ptr, ip_addr, sizeof(ip_addr));
        printf("\tserver address = %s\n", ip_addr);
    }

        /*creat connection socket*/
    connect_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(connect_fd == -1) {
    
    
        ERR_EXIT("socket()");
    }

        /* set sockaddr_in of server-side */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port_num);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(server_addr.sin_zero), 8);
    
    addr_len = sizeof(struct sockaddr);
    ret = connect(connect_fd, (struct sockaddr *)&server_addr, addr_len); /* connect to server */
    if(ret == -1) {
    
    
        close(connect_fd);
        ERR_EXIT("connect()"); 
    }
        /* connect_fd is allocated a port_number after connecting */
    addr_len = sizeof(struct sockaddr);
    ret = getsockname(connect_fd, (struct sockaddr *)&connect_addr, &addr_len);
    if(ret == -1) {
    
    
        close(connect_fd);
        ERR_EXIT("getsockname()");
    }

    port_num = ntohs(connect_addr.sin_port);
    strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
    printf("local socket ip addr = %s, port = %hu\n", ip_addr, port_num);

    childpid = fork();
    if(childpid < 0) {
    
    
        close(connect_fd);
        ERR_EXIT("fork()");
    }
    if(childpid > 0) {
    
     /* parent pro */
        while(1) {
    
     /* sending cycle */
            memset(send_buf, 0, BUFFER_SIZE);
            s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
            sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
            if(sendbytes <= 0) {
    
    
                printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
                break;
            }
            if(strncmp(send_buf, "end", 3) == 0)
                break;
        } 
        close(connect_fd);
        kill(childpid, SIGKILL);
    }
    else {
    
     /* child pro */
        while(1) {
    
     /* receiving cycle */
            memset(recv_buf, 0, BUFFER_SIZE);
            recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
            if(recvbytes <= 0) {
    
    
                printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
                break;
            }
            printf("\t\t\t\t\tserver %s >>>> %s\n", ip_addr, recv_buf);
            if(strncmp(recv_buf, "end", 3) == 0)
                break;
        }
        close(connect_fd);
        kill(getppid(), SIGKILL);
    }
    return EXIT_SUCCESS;
}

Code description:

This part of the code is mainly the realization of the client side, and the part of receiving and sending information is the same as that of the server side.

Main process:socket() -> connect()After the connection is successful, the system calls send()and can be used recv()to send and receive messages.

Next, give a detailed description of the details in the program:

  1. Obtain server-side information:struct hostent A structure pointer of type is defined at the beginning of the code host, and host-related (here, server-side) information is encapsulated in this structure, such as: official name, host address type, etc., and the server is obtained After the IP address or hostname, call gethostbyname()the function to get the structure pointer of the server-side struct hostenttype, and the official name of the server-side is printed out in the code.
  2. Create a socket and establish a connection with the server: the most important task on the client side is to establish a connection with the server side. After creating socket()a socket on the client side by calling the function, you need to call connect()the function to connect to the server side. struct sockaddr_inThe type is defined at the beginning of the code server_addrBefore calling the function connect(), you need to fill in the server-side information into the structure. Of course, don't forget the type conversion.

Note: bzero(&(server_addr.sin_zero), 8), the function here bzero()is used to server_addr.sin_zeroclear the first 8 bytes,

​ In fact, there is another function that can achieve this function:memset()

the difference:
  • bzero() is not an ANSI C function, it originated from the early Berkeley network programming code, but almost all vendors that support the socket API provide this function;
  • memset() is an ANSI C function that is more general and versatile.

operation result:

Initial state:

image-20220410103516674

From the output of the server, it can be seen that by traversing each node in the ifaddrs structure linked list, the information in the network interface of the local system can be obtained, among which there are ipv4 address = 127.0.0.1 with the interface name of lo, and ipv4 address of enp129s0f0 = 172.16.86.52, ipv4 address of docker0 = 172.17.0.1, and ipv6 address ::1 of interface name lo, ipv6 address fe80::ae1f:6bff:fe26:d546 of enp129s0f0;

At this time, the program will use the IPv4 address of docker0 as the IP address of the server, that is, 172.17.0.1, and at the same time, the assigned port number is 33942;

Then the server server calls listen()to enter the listening state, and calls accept()the function, waiting for the connection of the client client.

Now enter the IP address and port number of the server in the client:

image-20220410104913788

The IP address and port number of the connected client are displayed on the server side, the official name and server address of the server side are both 172.17.0.1, and the client’s own IP address and port number are displayed on the client side.

image-20220410105932905

Send a message from the client to the server: hello1, and display the received message on the server.

Send two more tests:

image-20220410110039534

Now in turn, the server sends the information:

image-20220410110122466

The server sends a message: hello1 client, and the client displays the received message.

Test again:

image-20220410110218796

The message was successfully received in the client.

Now to terminate the connection:

image-20220410110254063

After typing end in the client end, the client end exits the process, and then the server end is killed.

4. Code improvements:

On the basis of the above code, implement the FTP (File Transfer Protocol) file transfer protocol, so that the client can download files from the server:

Relevant code:

server_FTP.c

/* one client, one server, asynchronous send-receive */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/signal.h>
#include <sys/stat.h>
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5

#define ERR_EXIT(m) \
    do {
      
       \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

    /* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
 {
    
    
    char* ret_val;
    int i = 0;

    ret_val = fgets(stdin_buf, n, stdin);
    if(ret_val) {
    
    
        while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
    
    
            i++;
        }
        if(stdin_buf[i] == '\n') {
    
    
            stdin_buf[i] = '\0';
        } else {
    
    
            while (getchar() != '\n') ;
        }
    }

    return ret_val;
}

    /* save the ipv4 address of this server to ip_addr */
int getipv4addr(char *ip_addr)
{
    
    
    struct ifaddrs *ifaddrsptr = NULL;
    struct ifaddrs *ifa = NULL;
    void *tmpptr = NULL;
    int ret;
    
    ret = getifaddrs(&ifaddrsptr);
    if (ret == -1) {
    
    
        ERR_EXIT("getifaddrs()");
    }

    for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) {
    
    
        if(!ifa->ifa_addr) {
    
    
            continue;
        }
        if(ifa->ifa_addr->sa_family == AF_INET) {
    
     /* AF_INET: ipv4 */
            tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addr_buf[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN);  // 将二进制格式转换成IPv4协议的点分十进制格式
            printf("%s ipv4 address = %s\n", ifa->ifa_name, addr_buf);
            if (strcmp(ifa->ifa_name, "lo") != 0) {
    
    
                strcpy(ip_addr, addr_buf); /* save the ipv4 address */
            }
        } else if(ifa->ifa_addr->sa_family == AF_INET6) {
    
     /* ipv6 */
            tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addr_buf[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN);
            printf("%s ipv6 address %s\n", ifa->ifa_name, addr_buf);
        }
    }

    if (ifaddrsptr != NULL) {
    
    
        freeifaddrs(ifaddrsptr);
    }

    return EXIT_SUCCESS;
}


int main(void)
{
    
    
    int server_fd, connect_fd;
    struct sockaddr_in server_addr, connect_addr;
    socklen_t addr_len;
    int recvbytes, sendbytes, ret;
    char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
    char ip_addr[INET_ADDRSTRLEN]; /* ipv4 address */
    char stdin_buf[BUFFER_SIZE];
    uint16_t port_num;
    pid_t childpid;

    server_fd = socket(AF_INET, SOCK_STREAM, 0); /* create an ipv4 server*/
    if(server_fd == -1) {
    
    
        ERR_EXIT("socket()");
    }
    printf("server_fd = %d\n", server_fd);

    ret = getipv4addr(ip_addr); /* get server ipv4 address */
    if (ret == EXIT_FAILURE) {
    
    
        ERR_EXIT("getifaddrs()");
    }
    
        /* set sockaddr_in */
    server_addr.sin_family = AF_INET; /* ipv4 */
    server_addr.sin_port = 0; /* auto server port number */
    server_addr.sin_addr.s_addr = inet_addr(ip_addr);  /* unsigned long s_addr */
    bzero(&(server_addr.sin_zero), 8); /* padding with 0s */

    int opt_val = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); /* many options */
    
    addr_len = sizeof(struct sockaddr);
    ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len);
    if(ret == -1) {
    
    
        close(server_fd);
        ERR_EXIT("bind()");    
    }
    printf("Bind success!\n");

    addr_len = sizeof(server_addr);
    ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &addr_len);
    if(ret == 0) {
    
    
        printf("Server port number = %d\n", ntohs(server_addr.sin_port)); // ntohs: net to host (short: 2 bytes)
    } else {
    
    
        close(server_fd);
        ERR_EXIT("Server port number unknownn");
    }
    ret = listen(server_fd, MAX_QUE_CONN_NM); 
    if(ret == -1) {
    
    
        close(server_fd);
        ERR_EXIT("listen()");
    }
    printf("Server ipv4 addr: %s\n", ip_addr);
    printf("Listening ...\n");
    
    addr_len = sizeof(struct sockaddr);
        /* addr_len should be refreshed before each accept() */
    connect_fd = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len);
        /* waiting for connection */
    if(connect_fd == -1) {
    
    
        close(server_fd);
        ERR_EXIT("accept()");
    }

    port_num = ntohs(connect_addr.sin_port);
    strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
    printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);

    FILE*fileptr;
    struct stat fileattr;
    char file[BUFFER_SIZE];
    while(1){
    
    
         memset(recv_buf, 0, BUFFER_SIZE);
         memset(file, 0, BUFFER_SIZE);
         recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
         if(recvbytes <= 0) {
    
    
                printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
                break;
         }

         if(strncmp(recv_buf, "end", 3) == 0) {
    
    
                break;
         }

        fileptr = fopen(recv_buf, "r");
        if(stat(recv_buf, &fileattr) == 0) {
    
    
            printf("The filename is: %s\n", recv_buf);
            int filesize = fileattr.st_size;
            fread(file, sizeof(file), 1, fileptr);
            send(connect_fd, file, sizeof(file), 0);
        }
        else{
    
    
            printf("File doesn't exsit!\n");
        }
    }
    close(connect_fd);
    close(server_fd);
    return EXIT_SUCCESS;
}

client_FTP.c.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <fcntl.h>
#include <sys/stat.h>

#define BUFFER_SIZE 1024
#define ERR_EXIT(m) \
    do {
      
       \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


     /* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
{
    
    
    char* ret_val;
    int i = 0;

    ret_val = fgets(stdin_buf, n, stdin);
    if(ret_val) {
    
    
        while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
    
    
            i++;
        }
        if(stdin_buf[i] == '\n') {
    
    
            stdin_buf[i] = '\0';
        } else {
    
    
            while (getchar() != '\n') ;
        }
    }

    return ret_val;
}

int main(void)
{
    
    
    int connect_fd, sendbytes, recvbytes, ret;
    uint16_t port_num;
    char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
    char ip_addr[INET_ADDRSTRLEN], stdin_buf[BUFFER_SIZE];
    char clr;
    struct hostent *host;
    struct sockaddr_in server_addr, connect_addr;
    socklen_t addr_len;
    pid_t childpid;
    
    printf("Input server's hostname/ipv4: "); /* www.baidu.com or an ipv4 address */
    scanf("%s", stdin_buf);
    while((clr = getchar()) != '\n' && clr != EOF); /* clear the stdin buffer */
    printf("Input server's port number: ");
    scanf("%hu", &port_num);
    while((clr = getchar()) != '\n' && clr != EOF);

    host = gethostbyname(stdin_buf);
    if(host == NULL) {
    
    
        printf("invalid name or ip address\n");
        exit(EXIT_FAILURE);
    }
    printf("server's official name = %s\n", host->h_name);
    char** ptr = host->h_addr_list;
    for(; *ptr != NULL; ptr++) {
    
    
        inet_ntop(host->h_addrtype, *ptr, ip_addr, sizeof(ip_addr));
        printf("\tserver address = %s\n", ip_addr);
    }

        /*creat connection socket*/
    connect_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(connect_fd == -1) {
    
    
        ERR_EXIT("socket()");
    }

        /* set sockaddr_in of server-side */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port_num);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(server_addr.sin_zero), 8);
    
    addr_len = sizeof(struct sockaddr);
    ret = connect(connect_fd, (struct sockaddr *)&server_addr, addr_len); /* connect to server */
    if(ret == -1) {
    
    
        close(connect_fd);
        ERR_EXIT("connect()"); 
    }
        /* connect_fd is allocated a port_number after connecting */
    addr_len = sizeof(struct sockaddr);
    ret = getsockname(connect_fd, (struct sockaddr *)&connect_addr, &addr_len);
    if(ret == -1) {
    
    
        close(connect_fd);
        ERR_EXIT("getsockname()");
    }

    port_num = ntohs(connect_addr.sin_port);
    strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
    printf("local socket ip addr = %s, port = %hu\n", ip_addr, port_num);

    char filename[BUFFER_SIZE];
    while(1){
    
    
        printf("Please enter file name:\n");
        memset(send_buf, 0, BUFFER_SIZE);
        s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
        sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */

        if(sendbytes <= 0) {
    
    
                printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
                break;
        }

        if(strncmp(send_buf, "end", 3) == 0)
            break;

        
         memset(recv_buf, 0, BUFFER_SIZE);
         recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */

         if(recvbytes <= 0) {
    
    
                 printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
                 break;
         }
         printf("--------%s--------\n",send_buf);
         printf("%s\n",recv_buf);
    }


    close(connect_fd);
    return EXIT_SUCCESS;
}

First create two txt files as a test:

file1.txt

This is file1 -- base on FTP
ALL WORKS AND NO PLAY MAKES JACK A DULL BOY

file2.txt

This is file2 -- base on FTP
All work and no play makes jack a dull boy

After entering the IP address and port number of the server on the client side, enter the file name you want to obtain.

image-20220413152857077

After the client side enters file1.txt, the information in the file is obtained on the client side:

image-20220413153223223

Then enter file2.txt to continue the test. Similarly, the information in the file is obtained on the client side:

image-20220413153300107

Finally, the client end enters end as the end sign:

image-20220413153340930

II. Summary:

This experiment is mainly based on socket for network programming, using TCP communication protocol based on IPv4 protocol to realize the two-way transmission between server and client.

The main process of the server side is: socket()-> bind()-> listen()-> accept()After arriving here, you need to wait for the connection of the client side. After the connection is successful, you can send and receive messages through system calls send()and ; the main process of the client side is -> connect with the server side After success, send and receive messages through system calls and .recv()socket()connect()send()recv()

Guess you like

Origin blog.csdn.net/m0_52387305/article/details/124149859