Quick start to C + + network programming: the basic process of TCP network communication and the use of basic functions

Keywords: C++ network socket TCP/IP

Process Overview

The basic principle of network communication between client and server is as follows. Message oriented middleware may be added to a more complex architecture.
For the server, the communication process is as follows:

1,call socket Function create listener socket
2,call bind Function will socket Bind to a IP And port number
3,call listen Function enable listening
4,When there is a client connection request, call accept Function accepts the connection and generates a new socket(Communication with clients socket)
5,Based on newly generated socket call send or recv Function starts data communication with the client
6,After the communication is finished, the call is made. close Function close socket

For the client, the communication process is as follows:

1,call socket Function to create a client socket
2,call connect Function attempts to connect to the server
3,Call after successful connection send or recv Function to communicate data with the server
4,After the communication is finished, the call is made. close Function to turn off listening socket

Server side code implementation


#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

using namespace std;
int main() {

    // Create a listening socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        cout << " create listen socket error " << endl;
        return -1;
    }
    // Initialize server address
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);
    if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {
        cout << "bind listen socket error" << endl;
        return -1;
    }
    // lsnrctl start 
    if (listen(listenfd, SOMAXCONN) == -1) {
        cout << "listen error" << endl;
        return -1;
    }

    while (true) {
        // Create a temporary client socket
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);
        // Accept client connections
        int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);
        if (clientfd != -1) {
            char recvBuf[32] = {0};
            // Accept data from client
            int ret = recv(clientfd, recvBuf, 32, 0);
            if (ret > 0) {
                cout << "recv data from cilent , data:" << recvBuf << endl;
                // Send the received data to the client intact
                ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                if (ret != strlen(recvBuf)) {
                    cout << "send data error" << endl;
                } else {
                    cout << "send data to client successfully, data " << recvBuf <<endl;
                }
            } else {
                cout << "recv data error" <<endl;
            }
            close(clientfd);
        }
    }

    // Turn off listening socket
    close(listenfd);
    return 0;
}

Client code implementation

#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA "helloworld"

using namespace std;

int main() {
    // Create a socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
        cout << " create client socket error " << endl;
        return -1;
    }
    // Connect server
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
    if (connect(clientfd, (struct sockaddr *)& serveraddr, sizeof(serveraddr)) == -1) {
        cout << "connect socket error" << endl;
        return -1;
    }
    // Send data to server
    int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);
    if (ret != strlen(SEND_DATA)) {
        cout << "send data error" << endl;
        return -1;
    } else {
        cout << "send data to client successfully, data " << SEND_DATA <<endl;
    }
    // Pull data from server
    char recvBuf[32] = {0};
    ret = recv(clientfd, recvBuf, 32, 0);
    if (ret > 0) {
        cout << "recv data to client successfully, data " << recvBuf <<endl;
    } else {
        cout << "recv data to client error" << endl;
    }
    // Close socket
    close(clientfd);
    return 0;
}

Explanation of function and structure

sockaddr_in and SOCKADDR

Before explaining the socket programming function, it is necessary to explain the two indispensable structures of socket programming: sockaddr and sockaddr_in
The structure is as follows:

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
  };
  
/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };

Due to historical reasons, most of the parameter types used in socket functions (such as connect, bind, etc.) are sockaddr. Nowadays, socket programming mostly uses sockaddr_in to fill in the socket address. Therefore, when calling these functions, it is necessary to cast the socket address structure pointer, for example:

struct sockaddr_in serv;
 
bind(sockfd,(struct sockaddr *)&serv,sizeof(serv));

Socket: create a socket connection

/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
extern int socket (int __domain, int __type, int __protocol) __THROW; 

Provide the user with a socket, that is, socket interface description file word. It is an integer. Like the file descriptor, it is the index of the kernel identifying an IO structure.
The general incoming parameters are as follows:

int clientfd = socket(AF_INET, SOCK_STREAM, 0);

__ Domain: this parameter specifies a protocol cluster, which is often called a protocol domain. There are many protocol clusters in the system, including AF_INET -- specified as IPv4 protocol, AF_INET6 - designated as IPv6, AF_LOCAL -- specifies the UNIX protocol domain. This refers to the network layer protocol
__ Type: this parameter specifies the type of a socket interface. The possible types of socket interfaces are: SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW, etc., which respectively indicate byte stream, datagram, ordered packet, original socket.
__ Protocol: specify the corresponding transport protocol, such as TCP or UDP. The system provides a default protocol for each protocol cluster and type. We use this default value by setting protocol to 0. This refers to the transport layer protocol
Return value: the socket function returns a socket, that is, socket interface description word. If an error occurs, it returns - 1 and sets errno to the corresponding value. The user should detect it to determine what error occurs

Return value: 0 for success and non-0 for failure

bind: binding address and port number

On the server side, we have this Code:

// Initialize server address
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {
    cout << "bind listen socket error" << endl;
    return -1;
}

Function parameter interpretation:

/* Give the socket FD the local address ADDR (which is LEN bytes long).  */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)

For the address of bind, we used the macro INADDR_ANY, this macro is defined as follows:

/* Address to accept any incoming messages.  */
#define	INADDR_ANY		((in_addr_t) 0x00000000)

If the application does not care about the bind ing IP, you can use this macro. The underlying protocol stack service will automatically select an appropriate IP address, which will make it easier to select IP addresses on multiple network card machines.
If you only want to access on the local machine, the bind function address can use the local loopback address
If you only want to be accessed by the internal machine of the LAN, the bind function address can use the LAN address
If you want to be accessed by the public network, the bind function address can use INADDR_ANY or 0.0.0.0

The basic logic of network communication is that the client connects to the server, that is, from the address: port of the client to the address: port of the server.
Generally speaking, the port number of the server is fixed, while the port number of the client is randomly assigned by the operating system when the connection is initiated, and the occupied port will not be assigned. The port number is a value of type short, ranging from 0 to 65535
If the port number in the bind function is set to 0, the operating system will randomly assign an available listening port to the program. Generally speaking, the service program will not do so, because the service program is to provide external services, and the client must know the exact IP address and port number.
In special applications, we can also connect the server with the specified port number in the client program. Compared with the ordinary process, there is one more bind operation between creating the socket and initiating the connect:

Figure 1 unbound Figure 2 binding

Other related functions can be viewed in previous articles:
socket programming common function usage

Posted by wolfraider on Tue, 26 Oct 2021 05:42:52 -0700