socket communication programming

Keywords: Linux network Network Protocol

socket introduction

The "ip address" of the network layer can uniquely identify the host in the network, while the "port" of the transport layer can uniquely identify the application (process) in the host. In this way, triples (ip address, protocol, port) are used The process of the network can be identified, and the process communication in the network can use this flag to interact with other processes. The application program using TCP/IP protocol usually adopts the application programming interface: socket of UNIX BSD To realize the communication between network processes. At present, almost all applications use socket, and now it is the network era. Process communication is everywhere in the network. That's why I say "everything is socket" TCP/IP protocol family includes transport layer, network layer and link layer, and socket is located as shown in the figure. Socket is the intermediate software abstraction layer for communication between application layer and TCP/IP protocol family

Socket originated from UNIX, and one of the basic philosophies of Unix/Linux is "everything is a file". In many operating systems, socket API was originally developed as a part of UNIX operating system, so socket API is integrated with other I/O devices of the system. Applications should create a socket for Internet communication The operating system returns a small integer as a descriptor to identify the socket. Then the application takes the descriptor as a transfer parameter and completes some operations (such as reading or writing data from the socket) by calling corresponding functions (such as read, write, close, etc.).

In life, A calls B, A dials, and B picks up the phone after hearing the phone ring. Then A and B establish A connection, and A and B can talk. When the communication is over, hang up and end the conversation. Calling is A simple explanation of the working principle: "open write/read close" mode. The following is the basic process of network socket communication

socket operation API function

socket function

int socket(int domain,int type,int protocol);

parameter:
    domain: Specify the domain to send communications
       Available values: AF_UNIX: Local host communication with IPC similar
					AF_INET: Internet address IPV4 agreement
					AF_INET6: Internet address IPV6 agreement
    type: appoint socket type
      Available values: SOCK_STREAM(Stream Socket ),SOCK_DGRAM(datagram socket ),SOCK_RAW(Raw Socket )
      protocol: Specifies a special protocol on the socket descriptor, such as TCP,UDP Etc., generally set to 0,Automatically selected type Protocol corresponding to the type of
    Return value:
  		Success: returns the created socket descriptor
      	Failed:-1
    Supplement: SOCK_STREAM(Stream Socket )application TCP Protocol to provide sequential, reliable, byte stream based two-way links
        SOCK_DGRAM(datagram socket )application UDP Protocol, no link, unreliable, not fixed
       SOCK_RAW(Raw Socket )Provide access to Internet protocols and Internal Network Interfaces Only super users can use.

connect function
After calling socket to create socket_fd, the TCP client program calls the connect function to connect to the server. If the client calls the connect function to make a connection request, the server will receive the request and return the accept function. The new file descriptor returned by the accept function is the TCP connection corresponding to the client through these two file descriptors (FD from connect on the client side and FD from accept on the server side) the client side and the server side can communicate with each other.

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

parameter
	sockfd: Client socket()Description word created
	addr: Of the server to connect to socket Address information, which contains the address of the server IP Address and port information
	addrlen: socket Length of address

Before calling the connect function, you also need to set the ip address and port information of the server to addr

 //Address zeroing, ipv4, convert the port to network byte order, and point decimal forwarding to 32 is the shaping ip address
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    inet_aton(SERVER_IP,&serv_addr.sin_addr);
    
    rv = connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(rv < 0)
    {
        printf("Failed to connect server:%s\n",SERVER_IP,SERVER_PORT,strerror(errno));
        return -2;
    }
	printf("Connecting to the server[%s:%d] successfully\n",SERVER_IP,SERVER_PORT);

bind function
When calling socket to create a socket, the returned socket description exists in the protocol family space, but there is no specific address. If you want to assign an address to it, you must call the bind function. Usually, the server will bind a well-known address (IP address + port number) at startup , used to provide services, customers can connect to the server through it; the client does not need to specify, and the system automatically assigns a port number and its own IP address combination. This is why usually the server calls the bind function before the listen function, but the client does not call it, but the system randomly generates one when the connect function is used. Of course, the client can also Bind an address and port before calling the connect function, so that you can use a specific IP and port to connect to the server.

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

parameter
	sockfd: Namely socket Description word, it is through socket()Function created, uniquely identifying one socket. bind()The function is to bind a name to the description word.
	
	addr: One const struct sockaddr *Pointer to the object to be bound to sockfd This address structure is created from the address socket The address protocol family is different, but it will be assigned to after forced conversion sockaddr This type of pointer is passed to the kernel
	
	addrlen: Corresponding to addr The length of the.

Before calling the bind function, you also need to set the ip address and port information of the client to addr

 //Clear the address, ipv4, port network, convert the host byte order, and listen for all ip to convert to the host byte order
    memset(&serv_addr,0 ,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(LISTEN_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if(bind(socket_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))<0)
    {
        printf("create socket failure:%s\n",strerror(errno));
        return -2;
    }
    printf("socket[%d] bind on port [%d] for all ip address ok\n",socket_fd,LISTEN_PORT);

Universal socket sockaddr type definition

typedef unsigned short int sa_family_t;
struct sockaddr 
{ 
 sa_family_t 	sa_family; 	  /*2 Byte address family, AF_xxx */
 char 			sa_data[14]; /*14 Byte Protocol address */
}

Socket sockaddr_in type definition for IPv4

typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr 
{
 uint32_t s_addr; 
};
struct sockaddr_in 
{
 sa_family_t 		sin_family; /* 2 Byte address family, such as AF_xxx*/
 in_port_t 			sin_port; /* 2 Byte port*/
 struct in_addr 	sin_addr; /* 4 Byte IPv4 address*/
 unsigned char 		sin_zero[8]; /*8 Byte unused padding data, always zero */
};

Socket sockaddr_in6 type definition for IPv6

typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in6_addr
{
 union
 {
 uint8_t __u6_addr8[16];
 uint16_t __u6_addr16[8];
 uint32_t __u6_addr32[4];
 } __in6_u;
}
struct sockaddr_in6 {
 sa_family_t sin6_family; /*2B*/
 in_port_t sin6_port; /*2B*/
 uint32_t sin6_flowinfo; /*4B*/
 struct in6_addr sin6_addr; /*16B*/
 uint32_t sin6_scope_id; /*4B*/
};

sockaddr_un type definition corresponding to Unix domain

#define UNIX_PATH_MAX 108
struct sockaddr_un
 {
 sa_family_t 	sun_family; 
 char 			sun_path[UNIX_PATH_MAX]; 
};

listen function

int listen(int sockfd, int backlog);

parameter
	sockefd: socket()Created by system call to listen socket Description word
	backlog: corresponding socket The maximum number of connections that can be queued in the kernel

accept function
After calling the socket function, bind function and listen function in turn, the TCP server will listen to the specified socket address. After that, the server will call the accpet function to accept the connection request from the client. By default, this function is a blocking function, which also means that if no client connects to the server, the program will be blocked until there is one Once the client calls the connect function, the accept function of the server will be triggered to return, and the whole TCP link will be established.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

parameter
	sockfd: The server starts calling socket()Function, called listening socket Description word;
	addr:  Used to return the protocol address of the client. This address contains the protocol address of the client IP And port information;
	addrlen: Returns the length of the client protocol address

Example code

       //Listen to the socket, wait for the client to connect, create a new fd, and create a new client address and client address length
        client_fd = accept(socket_fd,(struct sockaddr*)&cli_addr,&cliaddr_len);
        if(client_fd <0)
        {
             printf("accept new socket failure :%s\n",strerror(errno));
             return -2;
        }
        //Convert the network address to the dot decimal address, and forward the ipv4 and client port of the client address to the dot decimal address
        printf("accept new client[%s:%d] wlth fd [%d]\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),client_fd);

The return value of the accept function is a new description word (fd) automatically generated by the kernel, which represents the TCP connection with the return client. If you want to send data to the client, you can call write() and other functions to write content to the fd; If you want to read content from the client, you can call functions such as read() to read data from the fd. A server usually only creates a listening socket descriptor, which exists throughout the life cycle of the server. The kernel creates a new socket descriptor for each client connection accepted by the server process. When the server completes the service to a client, it should close the corresponding socket descriptor of the client.
htons/htonl function
This means that the host byte order (small end byte order) is changed to the network byte order (large end byte order)
There are two most common
1. Little endian: store low order bytes at the starting address
2. Big endian: store high-order bytes at the starting address

htons:host network short(2 bytes / 16 bits), port number 16 bits

htonl:host network long(4 bytes / 32 bits), IP address 32 bits

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

parameter
	htonl Convert port number host byte order to network byte order
	htons hold ip Address host to network byte order
	
    ntohl Converts unsigned integers from network byte order to host byte order
    ntohs Converts unsigned short characters from network byte order to host byte order
	INADDR_ANY The specified address is 0.0.0.0 address,Means listening to all IP address
	
    hostlong:32 bits expressed in host byte order
    hostshort:16 bits expressed in host byte order
    
    netlong:A 32-bit number expressed in network byte order
    netshort:A 16 digit number expressed in network byte order

serv_addr.sin_port = htons(LISTEN_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

Take IP address 127.0.0.1 as an example

Step 1 127   .   0    .   0    .   1                 hold IP Each part of the address is converted to an 8-bit binary number.

Step 2 01111111.00000000.00000000.00000001 = 2130706433   (Host byte order)

Then rearrange the above four binary numbers from right to left, and it becomes:

Step 3 00000001.00000000.00000000.01111111 = 16777343        (Network byte order)

Take port 12345 as an example

 First step     00110000           00111001         =           12345 (Host byte order)
In fact, the port number is already the host byte order. First, write the port number as a 16 bit binary number
 Step 2      00111001          00110000          =           14640 (Network byte order)
Then, the first eight bits and the last eight bits of the host byte order are exchanged to form a new 16 bit binary number, which is the binary representation of the network byte order

Therefore, if it is known that the network byte order of port 12345 is 14640, serv_addr.sin_port=htons(12345) can be written directly as serv_ addr.sin_ The result of port = htons (14604) is the same. The function of htons is to convert the host byte order of port number to the network byte order.

inet_aton and inet_ntoa function

inet_ The Aton function converts a dot decimal string to the network address INET_ The ntoa function converts the network address to dot decimal string format

int inet_aton(const char *string, struct in_addr *addr);
Convert the network address represented by string to the integer representation of the address value, and the returned numbers are always in the order of network bytes

Parameter Description:
   	input parameter  string contain ASCII Expressed IP Address.
  	Output parameters addr It's going to be new IP Structure of address update.
Return value:
  If the function succeeds, the return value of the function is non-zero. Zero is returned if the address entered is incorrect.
  Using this function, there is no error code stored in the errno So his value will be ignored

char *inet_ntoa(struct in_addr in)
Convert the binary value transmitted by the network into dot decimal ip address

Returns a pointer to a dotted decimal string.
This function converts a network byte order IP The address is converted to its corresponding dotted decimal string.
Note: Yes inet_aton Instead of passing a pointer to a structure inet_ntoa The call to passes the structure itself.

inet_pton/inet_ntop function
inet_pton:
The IP address conversion function supports IPv4/IPv6. It can convert the IP address from dotted decimal to network byte order to represent the Internet address.
inet_ntop:
The IP address conversion function supports IPv4/IPv6, and can convert the Internet address represented in network byte order into "dotted decimal"

nt inet_pton(int af, const char *src, void *dst);
for example
	inet_pton(AF_INET, "172.20.223.151", &servaddr.sin_addr);
parameter
	af:Can be AF_INET(Corresponding to ipv4)or AF_INET6(ipv6),If,As an unsupported address family family Parameters,
	Both functions return an error and errno Set as EAFNOSUPPORT.
	src:Is a pointer to a dotted decimal string,
	dst:Is a pointer to the binary value of the converted network byte order.
Return value
	1 if successful, 0 if the input is not a valid expression, and 0 if an error occurs-1,And will errno Set as EAFNOSUPPORT.


const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
for example
	inet_ntop(AF_INET, &servaddr.sin_addr, IPdotdec, 16);
parameter
	af:Can be AF_INET(Corresponding to ipv4)or AF_INET6(ipv6),If,As an unsupported address family family Parameter, both functions return an error and errno Set as EAFNOSUPPORT.
	src:Is a pointer to a dotted decimal string,
	dst:Parameter cannot be a null pointer. The caller must allocate memory for the target storage unit and specify its size. When the call is successful, this pointer is the return value of the function.
	size:It is the cache to which it points dst If the cache is too small to store the value of the address, a null pointer will be returned and errno Set as ENOSPC

read function

ssize_t read(int fd , void *buf,size_t nbytes)
Parameters:
	sockfd: Socket descriptor for a remote communication connection
	buf: Buffer address to receive data
	len: Buffer length	

The read function is responsible for reading content from fd. When reading is successful, read returns the number of bytes actually read; If the returned value is 0, it means that the end of the file has been read. If it is a network socke fd, it means that the TCP link is broken; Less than 0 indicates an error and sets the error flag to the errno global variable. If the error is EINTR, it indicates that the reading is caused by interruption. If ECONNREST, it indicates that there is a problem with the network connection
write function

ssize_t write(int fildes, const void *buf, size_t nbyte);

args:
    int fildes     : File descriptor to write to file
    const void *buf: Write the address where data is stored in memory space
    size_t nbyte   : Maximum number of bytes expected to write data

The write function writes the nbytes byte content in buf to the file descriptor fd. When successful, it returns the number of bytes written. Returns - 1 on failure and sets the errno variable. In network programs, there are two possibilities when we write to the socket file descriptor. If the return value of write is greater than 0, it indicates that part or all of the data has been written. The returned value is less than 0, and an error occurs. We have to deal with it according to the error type. If the error is EINTR, it indicates that an interrupt error occurred during writing. If it is EPIPE, there is a problem with the network connection (the other party has closed the connection).

There are the following groups of network I/O operation functions: see the man documentation for details

 ssize_t read(int fd, void *buf, size_t count);
 ssize_t write(int fd, const void *buf, size_t count);
 
 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 
 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,socklen_t addrlen);
 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t*addrlen);
 
 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
 ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

close/shutdown function
After the connection between the server and the client is established, some read-write operations will be carried out. After the read-write operation is completed, the corresponding socket description word will be closed, just as the open file will be closed by calling the close function. The default behavior of closing a TCP socket is to mark the socket as closed, and then immediately return to the calling process

int close(int fd);
int shutdown(int sockfd, int how);

parameter
	fd:File to close
	how:Value is SHUT_RD The socket can no longer read data
		SHUT_WR The socket can no longer send data
		SHUT_RDWR Then the socket can neither read nor write data

code implementation

Client code

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

#define SERVER_IP       "127.0.0.1"
#define SERVER_PORT      12345
#define MSG_STR         "Hello beautiful world"


int main (int argc, char **argv)
{
    int                     sockfd  = -1;
    int                     rv      = -1;
    struct sockaddr_in      serv_addr;
    char                    buf[1024];
    
    
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
		printf("Create sockte failure:%s\n",strerror(errno));
        return -1;
    }
    printf("Create sockte [%d] successful!\n",sockfd);

    
    
    //Clear the server address, ipv4, forward the port to the network byte order, and point decimal forward to 32 as the shaping ip address
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    inet_aton(SERVER_IP,&serv_addr.sin_addr);


    rv = connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(rv < 0)
    {
        printf("Failed to connect server:%s\n",SERVER_IP,SERVER_PORT,strerror(errno));
        return -2;
    }
	printf("Connecting to the server[%s:%d] successfully\n",SERVER_IP,SERVER_PORT);


    while(1)
    {
        rv = write(sockfd,MSG_STR,strlen(MSG_STR));
        if(rv < 0)
        {
            printf("Write to server[%d] successfully:%s\n",sockfd,strerror(errno));
            break;
        }

        memset(buf,0,sizeof(buf));
        rv = read(sockfd,buf,sizeof(buf));
        if(rv <0 )
        {
            printf("Failed to read data server:%s\n",strerror(errno));
            break;
        }
        else if(0==rv)
        {
            printf("Connecting to the server:'%s'\n",rv,buf);
            break;
        }
        printf("read %d bytes bata from server:'%s'\n",rv,buf);
        close(sockfd);
    }
    return 0;
} 

Server side code

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_PORT     12345
#define BACKLOG         8


int main (int argc, char **argv)
{
    int                 rv = -1;
    int                 socket_fd = -1;
    int                 client_fd = -1;
    struct sockaddr_in  serv_addr;
    struct sockaddr_in  cli_addr;
    socklen_t           cliaddr_len;
    char                buf[1024];
    int                 on = 1;


    //Create socket ipv4 tcp protocol
    socket_fd = socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd < 0)
    {
        printf("create socket failure:%s\n",strerror(errno));
        return -1;
    }
    printf("socket create fd[%d]\n",socket_fd);
    

    //Reuse port
    setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));


    //Clear the server address, ipv4, port network, convert the host byte order, and listen for all ip to convert to the host byte order
    memset(&serv_addr,0 ,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(LISTEN_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);


    //Binding port
    if(bind(socket_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))<0)
    {
        printf("create socket failure:%s\n",strerror(errno));
        return -2;
    }
    printf("socket[%d] bind on port [%d] for all ip address ok\n",socket_fd,LISTEN_PORT);

    //Up to BACKLOG connections
    listen(socket_fd,BACKLOG);


    while(1)
    {
        printf("\n start wainting and accept new client connect.... \n",socket_fd);

       //Listen to the socket, create a new fd, wait for the client to connect, create a new client address and client address length, and wait
        client_fd = accept(socket_fd,(struct sockaddr*)&cli_addr,&cliaddr_len);
        if(client_fd <0)
        {
             printf("accept new socket failure :%s\n",strerror(errno));
             return -2;
        }
        //Convert the network address to the dot decimal address, and forward the ipv4 and client port of the client address to the dot decimal address
        printf("accept new client[%s:%d] wlth fd [%d]\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),client_fd);


        memset(buf, 0 ,sizeof(buf));
        rv = read(client_fd,buf,sizeof(buf));
        if(rv < 0)
        {
            printf("read data from client socket[%d] failure:%s\n",client_fd ,strerror(errno));
            close(client_fd);
            continue;
        }
        else if(rv == 0)
        {
            printf("client socket[%d] disconnected\n",client_fd);
            close(client_fd);
            continue;
        }
        printf("read %d bytes bata from client[%d] and echo it back :'%s'\n",rv ,client_fd,buf);



        rv = write(client_fd,buf,rv);
        if(rv < 0)
        {
            printf("write %d bytes data back to client[%d] failure:%s\n",rv ,client_fd,strerror(errno));
            close(client_fd);
        }
        printf("close client socket[%d]\n",client_fd);
        close(client_fd);

    }
    close(socket_fd);

    return 0;
} 

Posted by dimkasmir on Thu, 02 Dec 2021 11:57:34 -0800