[Linux] Network programming sockets (C++)

Table of contents

1. Preliminary knowledge

【1.1】Understand the source IP address and destination IP address

【1.2】Understand the port number

【1.3】Understand "port number" and "process ID"

【1.4】Understand the source port number and destination port number

【1.5】Understand the TCP protocol

【1.6】Understand UDP protocol

2. Network byte order

【2.1】Socket programming interface

【2.1.1】socket API

【2.1.2】bind API

【2.1.3】listen API

【2.1.4】accept API

【2.1.5】connect API

【2.1.6】recvfrom API

【2.1.7】sendto API

【2.2】Port conversion function

【2.3】Address conversion function

【2.3】sockaddr structure

【2.3.1】sockaddr structure

【2.3.2】sockaddr_in structure

【2.3.3】Special initialization function for sockaddr structure

3. Check the system network with netstat

【5】UDP realizes network communication

【5.1】Makefile file code

【5.2】UdpServer file code

【5.2.1】UdpServer.hpp file code

【5.2.2】UdpServer.cc file code

【5.3】UdpClient file code

【5.3.1】UdpClient.hpp file code

【5.3.2】UdpClient.cc file code

【7】TCP realizes network communication

【7.1】Makefile file code

【7.2】Log.hcc file code

【7.3】TcpServer file code

【7.3.1】TcpServer.hpp file code

【7.3.2】TcpServer.cc file code

【7.4】TcpClient file code

【7.4.1】TcpClient.hpp file code

【7.4.2】TcpClient.cc file code

【9】TCP protocol communication process

【10】Comparison between TCP and UDP


Linux Network Programming Sockets (C++)

1. Preliminary knowledge

【1.1】Understand the source IP address and destination IP address

        In the IP packet header, there are two IP addresses, called the source IP address and the destination IP address.

[Thinking] Can we complete communication just by having an IP address? Imagine the example of sending QQ messages. With the IP address, the message can be sent to the other party's machine, but there needs to be another identifier to distinguish it. This Which program should the data be parsed for?

        In order to better express the uniqueness of the unique host service process, we use the port number port to identify the server process and the uniqueness of the client process.

【1.2】Understand the port number

The port number (port) is the content of the transport layer protocol:

  • The port number is a 2-byte 16-bit integer.

  • The port number is used to identify a process and tell the operating system which process the current data should be handed over for processing.

  • The IP address + port number can identify a process on a certain host on the network.

  • A port number can only be occupied by one process.

IP address (uniqueness of the host in the entire network) + port number on the host identifies the uniqueness of the process on the server (ip + PortA, ip + PortB). The essence of network communication: In fact, it is communication between processes. IP guarantees (unique in the entire network), port guaranteed (unique in the host).

【1.3】Understand "port number" and "process ID"

        When we were learning system programming before, we learned that pid represents the only process; here our port number also represents the only process. So what is the relationship between the two?

In addition, a process can bind multiple port numbers; but a port number cannot be bound by multiple processes.

【1.4】Understand the source port number and destination port number

        There are two port numbers in the data segment of the transport layer protocol (TCP and UDP) , which are called the source port number and the destination port number. They describe "who sent the data and to whom it is sent";

【1.5】Understand the TCP protocol

        Here we first have an intuitive understanding of TCP (Transmission Control Protocol); we will discuss some details of TCP in detail later.

  • transport layer protocol

  • There is a connection

  • Reliable transmission

  • byte stream oriented

【1.6】Understand UDP protocol

        Here we also have an intuitive understanding of UDP (User Datagram Protocol ); we will discuss it in detail later.

  • transport layer protocol

  • no connection

  • Unreliable transmission

  • datagram oriented

2. Network byte order

【2.1】Socket programming interface

#include <socket.h>
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// domain : 域:本地通信、网络通信
// type   : 我们socket提供的能力类型
#include <socket.h>
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
 socklen_t address_len);
// socket : 绑定指定的文件描述符
// sockaddr : 参数结构
#include <sys/types.h> 
#include <sys/socket.h>
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
#include <sys/types.h> 
#include <sys/socket.h>
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
#include <sys/types.h> 
#include <sys/socket.h>
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
#include <sys/types.h>
#include <sys/socket.h>

// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
#include <sys/types.h>
#include <sys/socket.h>
// 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

【2.1.1】socket API

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • socket() opens a network communication port and, if successful, returns a file descriptor just like open().

  • Applications can use read/write to send and receive data on the network just like reading and writing files.

  • If an error occurs in the socket() call, -1 is returned. For IPv4, the family parameter is specified as AF_INET.

  • For the TCP protocol, the type parameter is specified as SOCK_STREAM, which represents a stream-oriented transmission protocol.

  • The introduction of the protocol parameter is omitted, just specify it as 0.

【2.1.2】bind API

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
  • The network address and port number monitored by the server program are usually fixed. After the client program learns the address and port number of the server program, it can initiate a connection to the server; the server needs to call bind to bind a fixed network address and port. Number.

  • bind() returns 0 on success and -1 on failure.

  • The function of bind() is to bind the parameters sockfd and myaddr together so that sockfd, a file descriptor used for network communication, listens to the address and port number described by myaddr.

  • As mentioned before, struct sockaddr * is a general pointer type. The myaddr parameter can actually accept sockaddr structures of multiple protocols, and their lengths are different, so the third parameter addrlen is needed to specify the length of the structure.

【2.1.3】listen API

#include <sys/socket.h>
int listen(int socket, int backlog);
  • listen() declares that sockfd is in the listening state, and allows at most backlog clients to be in the connection waiting state. If more connection requests are received, they will be ignored. The setting here will not be too large (usually 5). I will teach you the details. Study in depth later.

  • listen() returns 0 on success and -1 on failure.

【2.1.4】accept API

#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
  • After the three-way handshake is completed, the server calls accept() to accept the connection.

  • If there is no connection request from the client when the server calls accept(), it blocks and waits until a client connects.

  • addr is an outgoing parameter, and the client's address and port number are passed out when accept() returns; if NULL is passed to the addr parameter, it means that the client's address is not concerned.

  • The addrlen parameter is an incoming and outgoing parameter (value-result argument). What is passed in is the length of the buffer addr provided by the caller to avoid buffer overflow problems. What is passed out is the actual length of the client address structure ( It is possible that the buffer provided by the caller is not filled);.

【2.1.5】connect API

#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen_t address_len);
  • The client needs to call connect() to connect to the server.

  • The parameter forms of connect and bind are the same. The difference is that the parameter of bind is your own address, while the parameter of connect is the address of the other party.

  • connect() returns 0 on success and -1 on error.

【2.1.6】recvfrom API

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd: the network port of the file descriptor (socket)

  • buf: read into buffer

  • len: buffer length

  • flags: The default reading mode is 0 (blocking reading)

  • src_addr: read socket information

  • addrlen: socket length

【2.1.7】sendto API

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd: the network port of the file descriptor (socket)

  • buf: buffer sent

  • len: buffer length

  • flags: The default sending method is 0 (blocking sending)

  • dest_addr: Send your own socket information

  • addrlen: socket length

【2.2】Port conversion function

        We already know that multi-byte data in memory is divided into big-endian and little-endian relative to the memory address. Multi-byte data in disk files is also divided into big-endian and little-endian relative to the offset address in the file. Network data Streams are also divided into big-endian and little-endian . So how to define the address of the network data stream?

  • The sending host usually sends the data in the sending buffer in order from low to high memory address.

  • The receiving host stores the bytes received from the network in the receiving buffer in order, and also in the order of memory addresses from low to high.

  • Therefore, the address of the network data flow should be specified as follows: the data sent first is the low address, and the data sent later is the high address.

  • The TCP/IP protocol stipulates that network data flow should use big-endian byte order, that is, low address and high byte.

  • Regardless of whether the host is a big-end machine or a little-end machine, data will be sent/received according to the network byte order specified by TCP/IP.

  • If the current sending host is little endian, the data needs to be converted to big endian first; otherwise, it is ignored and sent directly.

        In order to make network programs portable and make the same C code run normally after compilation on big-endian and little-endian computers, you can call the following library function to convert network byte order and host byte order.

#include <arpa/inet.h>
// 函数将无符号整数hostlong从主机字节顺序转换为网络字节顺序.
uint32_t htonl(uint32_t, hostlong);
#include <arpa/inet.h>
// 函数将无符号短整数hostshort从主机字节顺序转换为网络字节顺序.
uint16_t htons(uint16_t, hostshort);
#include <arpa/inet.h>
// 函数将无符号整数netlong从网络字节顺序转换为主机字节顺序.
uint32_t ntohl(uint32_t netlong);
#include <arpa/inet.h>
// 函数将无符号短整数netshort从网络字节顺序转换为主机字节顺序.
uint16_t ntohs(uint16_t netshort);
  • These function names are easy to remember, h represents host, n represents network, l represents a 32-bit long integer, and s represents a 16-bit short integer.

  • For example, htonl means converting a 32-bit long integer from host byte order to network byte order, such as converting an IP address and preparing to send it.

  • If the host is little-endian, these functions convert the parameters accordingly and return them.

  • If the host is big-endian, these functions do not perform conversion and return the parameters unchanged.

【2.3】Address conversion function

        This section only introduces socket network programming based on IPv4. The member struct in_addr sin_addr in sockaddr_in represents a 32-bit IP address. However, we usually use dotted decimal strings to represent IP addresses. The following functions can be used between string representation and in_addr representation. Convert

        inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof - Internet address operation routines

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// inet_addr()函数的作用是:将Internet主机地址cp从IPv4的数字点法转换为网络字节顺序的二进制数据。如果输入无效,INADDR_NONE(通常是-1)返回。使用这个函数是有问题的,因为-1是一个有效的地址(255.255.255.255)。避免使用inet_aton()、inet_pton(3)或getad‐Drinfo(3)提供了一种更清晰的方式来指示错误返回。
in_addr_t inet_addr(const char *cp);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_network(const char *cp);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// net_ntoa()函数的作用是:将Internet主机地址(以网络字节顺序给出)转换为IPv4点分十进制格式的字符串。字符串以静态方式返回已分配的缓冲区,后续调用将覆盖该缓冲区。
char *inet_ntoa(struct in_addr in);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

struct in_addr inet_makeaddr(int net, int host);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_lnaof(struct in_addr in);
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_netof(struct in_addr in);

        Among them, inet_pton and inet_ntop can not only convert the in_addr of IPv4, but also the in6_addr of IPv6, so the function interface is void*addrptr.

【2.3】sockaddr structure

        The socket API is an abstract network programming interface that is suitable for various underlying network protocols, such as IPv4, IPv6, and UNIX DomainSocket to be discussed later. However, the address formats of various network protocols are not the same.

        The address format of IPv4 and IPv6 is defined in netinet/in.h. The IPv4 address is represented by the sockaddr_in structure, including 16-bit address type, 16-bit port number and 32-bit IP address.

        IPv4 and IPv6 address types are defined as constants AF_INET and AF_INET6 respectively. In this way, as long as the first address of a certain sockaddr structure is obtained, there is no need to know what type of sockaddr structure it is, and the address in the structure can be determined based on the address type field. content.

        The socket API can be represented by the struct sockaddr * type, which needs to be forced to be converted into sockaddr_in when used; the advantage of this is the versatility of the program, which can receive IPv4, IPv6, and UNIX Domain Socket various types of sockaddr structure pointers as parameter.

【2.3.1】sockaddr structure

        sockaddr is defined in the header file #include <sys/socket.h> . The defect of sockaddr is that sa_data mixes the target address and port information together, as follows:

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

【2.3.2】sockaddr_in structure

        sockaddr_in is defined in the header file #include<netinet/in.h> or #include <arpa/inet.h> `. This structure solves the shortcomings of sockaddr and stores port and addr separately in two variables, as follows:

struct sockaddr_in {
    sa_family_t			sin_family; 	// 地址族(Address Family)
    uint16_t			sin_port;		// 16位TCP\UDP端口号
    struct in_addr		sin_addr;		// 32位ip地址
    char				sin_zero[8]		// 不使用
}

// 该结构体中提到另一个结构体in_addr定义如下:它用来存放32位ip地址
struct in_addr {
    in_addr_t			s_addr;			// 32位IPv4地址
}

【2.3.3】Special initialization function for sockaddr structure

#include <strings.h>
// 对struct sockaddr 数据类型做初始化            
// bzero()函数将从s开始的区域的前n个字节设置为零(包含'\0'的字节)。
void bzero(void *s, size_t n);

3. Check the system network with netstat

grammar

netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip]

Function

        The Linux netstat command displays network status.

        Using the netstat command allows you to know the network status of the entire Linux system.

Options

  • -a or --all displays all connected Sockets.

  • -A<network type> or --<network type> lists the relevant addresses in the connection of this network type.

  • -c or --continuous lists network status continuously.

  • -C or --cache displays router configuration cache information.

  • -e or --extend displays other network-related information.

  • -F or --fib displays the route cache.

  • -g or --groups Display the list of multicast function group members.

  • -h or --help online help.

  • -i or --interfaces Display the network interface information form.

  • -l or --listening displays the Socket of the server being monitored.

  • -M or --masquerade displays masqueraded network connections.

  • -n or --numeric Use the IP address directly without going through the domain name server.

  • -N or --netlink or --symbolic Displays the symbolic link names of network hardware peripherals.

  • -o or --timers Display timers.

  • -p or --programs displays the program identification code and program name that are using Socket.

  • -r or --route displays the Routing Table.

  • -s or --statistics displays network work information statistics table.

  • -t or --tcp displays the connection status of the TCP transmission protocol.

  • -u or --udp displays the connection status of UDP transmission protocol.

  • -v or --verbose displays the instruction execution process.

  • -V or --version displays version information.

  • -w or --raw displays the connection status of the RAW transfer protocol.

  • -x or --unix This parameter has the same effect as specifying the "-A unix" parameter.

  • --ip or --inet This parameter has the same effect as specifying the "-A inet" parameter.

[Remarks] If you want to see more details, add sudo

[shaxiang@VM-8-14-centos 99_Lesson_20230707_CodeExecute_UdpNetWork_Linux]$ netstat -nuap // 监控端口号和IP
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                   
udp        0      0 10.0.8.14:123           0.0.0.0:*                           -                   
udp        0      0 127.0.0.1:123           0.0.0.0:*                           -                   
udp6       0      0 fe80::5054:ff:fec6::123 :::*                                -                   
udp6       0      0 ::1:123                 :::*                                -             
[shaxiang@VM-8-14-centos 20230811_TcpNewWork]$ netstat -nltp	// 监控TCP服务器
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:45638         0.0.0.0:*               LISTEN      20145/node          
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      4410/./TcpServer    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::3306                 :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                       

【5】UDP realizes network communication

【5.1】Makefile file code

# 定义变量并且赋值相应的字符串信息
cc := g++
standard := -std=c++11

compile: udpServer udpClient
udpServer: UdpServer.cc 
	$(cc) -o $@ $^ $(standard)
udpClient: UdpClient.cc 
	$(cc) -o $@ $^ $(standard)

clean:
	rm -rf udpServer udpClient

# .PHONY: 可以避免与系统的命令冲突
.PHONY: compile clean 

【5.2】UdpServer file code

【5.2.1】UdpServer.hpp file code

#pragma once
/* C头文件包含 */
#include <cstdlib>
#include <cstring>
#include <cerrno>

/* C++头文件包含 */
#include <iostream>
#include <functional>
#include <string>

/* 系统头文件包含 */
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* UDP服务器Demo封装命名空间 */
namespace Server
{
    enum { USAGE_ERR = 1, SOCKET_ERR = 2, BIND_ERR = 3 };
    const int g_num = 1024;


    /* UDP服务器命名空间 */
    class UdpServer
    {
    private:
        using func_t = std::function<void(const int&, const std::string&, const uint16_t&, const std::string&)>;

    public:
        /* 构造函数 */
        UdpServer(const func_t& func, const uint16_t& port)
            : _selfSockFd(-1)
            , _selfSockProt(port)
            , _callBack(func)
        {}

        /* 析构函数 */
        ~UdpServer() 
        {}

    public:
        /* 初始化 */
        void Init()
        {
            // 创建socket(打开网卡驱动文件)
            _selfSockFd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字    
            if(_selfSockFd < 0)
            {
                std::cerr << "socket error " << errno << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "socket success..." << std::endl;

            // 绑定自己的Ip和端口号
            struct sockaddr_in localAddr; // 套接字对象
            bzero(&localAddr, sizeof(localAddr)); // 初始化
            localAddr.sin_family = AF_INET; // 地址家族
            localAddr.sin_addr.s_addr = INADDR_ANY; // 任意ip地址
            localAddr.sin_port = htons(_selfSockProt); // 端口号
            
            int bindState = bind(_selfSockFd, (struct sockaddr*)& localAddr, sizeof(localAddr)); // 绑定套接字
            if(bindState < 0)
            {
                std::cerr << "bind error " << errno << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cout << "bind success..." << std::endl;
        }

        /* 启动服务 */
        void Start()
        {
            // 启动
            char reBuffer[g_num] = "\0";
            while(true)
            {
                struct sockaddr_in clientAddr; // 套接字对象
                bzero(&clientAddr, sizeof(clientAddr)); // 初始化
                socklen_t addrLength = sizeof(clientAddr);
                // 读取
                int reCnt = recvfrom(_selfSockFd, reBuffer, sizeof(reBuffer) - 1, 0, (struct sockaddr*)& clientAddr, &addrLength); 
                if(reCnt > 0)
                {
                    reBuffer[reCnt] = '\0'; // 处理字符串
                    std::string clientIp = inet_ntoa(clientAddr.sin_addr); // 获取客户端ip
                    uint16_t clientPort = ntohs(clientAddr.sin_port); // 获取客户端port
                    // 调用回调函数
                    _callBack(_selfSockFd, clientIp, clientPort, reBuffer);
                }
            }
        }

    private:
        uint16_t     _selfSockProt;     // 服务器(自己)UDP通讯端口号
        int          _selfSockFd;       // 服务器(自己)UDP通讯文件描述符   
        func_t       _callBack;         // 服务器(自己)UDP回调函数
    };
};

【5.2.2】UdpServer.cc file code

#include <memory>
#include "UdpServer.hpp"
using namespace Server;

/* 函数接口:用户启动提示 */ 
void Usage(char* argv)
{
    std::cout << "Usage:\n\t" << argv << " local_port\n\n" << std::endl;
}

/* 函数回调:通讯功能 */
void CallbackFunction(const int& selfSockFd, const std::string& clientIp, const uint16_t& clientPort, const std::string& message) 
{
    // 打印接收信息
    std::cout << "client ip[" << clientIp << "] " << "port[" << clientPort << "]: " << message << std::endl;
}

/* 函数接口:程序入口 */
int main(int argc, char** argv)
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 服务器对象交管给智能指针
    uint16_t userPort = atoi(argv[1]);
    std::unique_ptr<UdpServer> udpSevr(new UdpServer(CallbackFunction, userPort));
    udpSevr->Init();
    udpSevr->Start();

    return 0; 
}

【5.3】UdpClient file code

【5.3.1】UdpClient.hpp file code

#pragma once
/* C头文件包含 */
#include <string>
#include <cstring>

/* C++头文件包含 */
#include <iostream>

/* 系统头文件包含 */
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* UDP客户端Demo封装命名空间 */
namespace Client
{
    enum { USAGE_ERR = 1, SOCKET_ERR = 2, BIND_ERR = 3 };
    const int g_num = 1024;

    /* UDP客户端命名空间 */
    class UdpClient
    {
    public:
        /* 构造函数 */
        UdpClient(const uint16_t& port, const std::string& ip)
            : _selfSockFd(-1)
            , _sevrSockIp(ip)
            , _sevrSockProt(port)
        {}

        /* 析构函数 */
        ~UdpClient() 
        {}

    public:
        /* 初始化 */
        void Init()
        {   
            // 创建socket(打开网卡驱动文件)
            _selfSockFd = socket(AF_INET, SOCK_DGRAM, 0);    
            if(_selfSockFd < 0)
            {
                std::cerr << "socket error " << errno << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "socket success..." << std::endl;
        }

        /* 启动服务 */
        void Start()
        {
            struct sockaddr_in serverAddr; // 套接字对象
            bzero(&serverAddr, sizeof(serverAddr)); // 初始化
            serverAddr.sin_family = AF_INET; // 地址家族
            serverAddr.sin_addr.s_addr = inet_addr(_sevrSockIp.c_str()); // 服务器ip
            serverAddr.sin_port = htons(_sevrSockProt); // 服务器port

            // 启动
            char stoBuffer[g_num];
            while(true)
            {   
                std::cout << "Please Say: ";
                std::cin.getline(stoBuffer, g_num);  
                // 发送信息
                sendto(_selfSockFd, stoBuffer, sizeof(stoBuffer), 0, (struct sockaddr*)& serverAddr, sizeof(serverAddr));

            }
        }

    private:
        std::string  _sevrSockIp;       // 服务器(对方)UDP通讯Ip地址
        uint16_t     _sevrSockProt;     // 服务器(对方)UDP通讯端口号
        int          _selfSockFd;       // 客户端(自己)UDP通讯文件描述符   
    };
};

【5.3.2】UdpClient.cc file code

#include <memory>
#include "UdpClient.hpp"
using namespace Client;

/* 函数接口:用户启动提示 */ 
void Usage(char* argv)
{
    std::cout << "Usage:\n\t" << argv << " ServerIp" << " ServerPort\n\n" << std::endl;
}

/* 函数接口:程序入口 */
int main(int argc, char** argv)
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 客户端对象交管给智能指针
    uint16_t userPort = atoi(argv[2]);
    std::string userIp = argv[1];
    std::unique_ptr<UdpClient> udpClit(new UdpClient(userPort, userIp));
    udpClit->Init();
    udpClit->Start();

    return 0; 
}

【7】TCP realizes network communication

【7.1】Makefile file code

# 定义变量并且赋值相应的字符串信息
cc := g++
standard := -std=c++11

compile:tcpServer tcpClient
tcpServer:TcpServer.cc
	$(cc) -o $@ $^ $(standard)
tcpClient:TcpClient.cc
	$(cc) -o $@ $^ $(standard)

clean:
	rm -rf tcpServer tcpClient
	
# .PHONY: 可以避免与系统的命令冲突
.PHONY:clean compile

【7.2】Log.hcc file code

#pragma once 
#include <iostream>

#define DEBUG    0 // 调试等级
#define NORMAL   1 // 正常等级
#define WARNING  2 // 警告等级
#define ERROR    3 // 错误等级
#define FATAL    4 // 致命等级

/* 函数:日志等级转为字符串 */
void LevelToString(const int& level)
{
    
}

/* 函数:日志打印 */
void LogMessage(const int& level, const std::string& message)
{
    // 格式:[日志等级] [时间戳/时间] [pid] [信息]
    // 比如:[WARNING] [2023-05-11 18:09:23] [12345] [创建socket文件描述符失败!]    
    std::cout << message << std::endl;
}

【7.3】TcpServer file code

【7.3.1】TcpServer.hpp file code

#pragma once
#include <iostream>

#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include "Log.hpp"

namespace Server
{
    /* 枚举常量 */
    enum{ 
        USAGE_ERR = 1, 
        SOCKET_ERR, 
        BIND_ERR,  
        LISTEN_ERR,
        ACCEPT_ERR
    };
    const int g_backLog = 10;
    const int g_num = 1024;

    class TcpServer
    {
    public:
        /* 构造函数 */
        TcpServer(const uint16_t& port)
            : _selfListenFd(-1)
            , _selfPort(port)
        {}

        /* 析构函数 */
        ~TcpServer() {}

    public:
        /* 初始化 */
        void Init()
        {
            // No.1 创建套socket文件套接字对象
            _selfListenFd = socket(AF_INET, SOCK_STREAM, 0);
            if(_selfListenFd < 0)
            {
                LogMessage(FATAL, "create socket fail!");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL, "create socket success...");

            // No.2 绑定自己的网络信息
            struct sockaddr_in localAddr; // 创建sockAddr对象
            bzero(&localAddr, sizeof(localAddr)); // 初始化对象
            localAddr.sin_family = AF_INET; // 绑定协议家族
            localAddr.sin_addr.s_addr = INADDR_ANY; // 绑定回环地址[0.0.0.0]
            localAddr.sin_port = htons(_selfPort); // 绑定端口号
            int bindState = bind(_selfListenFd, (struct sockaddr*)&localAddr, sizeof(localAddr));
            if(bindState < 0)
            {
                LogMessage(FATAL, "bind socket fail!");
                exit(BIND_ERR); 
            }
            LogMessage(NORMAL, "bind socket success...");

            // No.3 开始监听网络
            int listenState = listen(_selfListenFd, g_backLog);
            if(listenState < 0)
            {
                LogMessage(FATAL, "listen socket fail!");
                exit(LISTEN_ERR); 
            }
            LogMessage(NORMAL, "listen socket success...");
        }

        /* 启动 */
        void Start()
        {
            // 运行服务器
            while(true)
            {
                // No.4 获取新连接
                struct sockaddr_in clientAddr; // sockAddr对象
                socklen_t addrLen = sizeof(clientAddr); // 求长度
                bzero(&clientAddr, sizeof(clientAddr)); // 初始化
                // 接收链接
                _selfSocketFd = accept(_selfListenFd, (struct sockaddr*)&clientAddr, &addrLen);
                if(_selfListenFd < 0)
                {
                    LogMessage(ERROR, "accept socket fail!");
                    exit(ACCEPT_ERR); 
                }
                LogMessage(NORMAL, "accept socket success...");
                std::cout << "listenFd: " << _selfListenFd << " " << "sockFd: " << _selfSocketFd << std::endl;

                // 面向字节流的读取(对文件进行读取)
                ServiceIO();
                close(_selfSocketFd); // 必须关闭,防止文件描述符泄露!
                break;
            }
        }

        /* 面向字节流读取消息 */
        void ServiceIO()
        {
            while(true)
            {
                // 接收
                char inBuffer[g_num] = { '\0' };
                ssize_t n = read(_selfSocketFd, inBuffer, sizeof(inBuffer) - 1);
                if(n > 0)
                {
                    // 处理读取到的内容
                    inBuffer[n] = '\n';
                    std::cout << "recv buffer: " << inBuffer << std::endl;

                    // 响应
                    std::string outBuffer;
                    outBuffer = "Server echo# ";
                    outBuffer += inBuffer;
                    write(_selfSocketFd, outBuffer.c_str(), outBuffer.size());
                }
                else if(n == 0) // 在读取的时候,如果读取到了0,说明客户端已经退出了,这时候服务器也可以退出了!
                {
                    LogMessage(NORMAL, "client quit me too...");
                    break;
                }
            }
        }

    private:
        int         _selfListenFd;  // TCP通讯(自己)网络监听文件描述符
        int         _selfSocketFd;  // TCP通讯(自己)网络服务文件描述符
        uint16_t    _selfPort;      // TCP通讯(自己)网络端口
    };
};

【7.3.2】TcpServer.cc file code

#include <memory>
#include "TcpServer.hpp"

using namespace Server;

/* 函数:消息提示 */ 
void Usage(char* argv)
{
    std::cout << "Usage:\n\t" << argv << " local_port\n\n" << std::endl;
}

/* 函数:程序入口函数 */
int main(int argc, char** argv)
{
    // 检查用户启动命令
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 将服务器对象转交给智能指针进行管理
    // 获取用户指定的端口号
    uint16_t userPort = atoi(argv[1]);
    std::unique_ptr<TcpServer> pTcpSevr(new TcpServer(userPort));
    pTcpSevr->Init();
    pTcpSevr->Start();
    
    return 0;
}

【7.4】TcpClient file code

【7.4.1】TcpClient.hpp file code

#pragma once
#include <iostream>

#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include "Log.hpp"

namespace Client
{
    /* 枚举常量 */
    enum{ 
        USAGE_ERR = 1, 
        SOCKET_ERR, 
        BIND_ERR,  
        LISTEN_ERR,
        ACCEPT_ERR,
        CONNECT_ERR
    };
    const int g_num = 1024;


    class TcpClient
    {
    public:
        /* 构造函数 */
        TcpClient(const std::string& sevrIp, const uint16_t& sevrPort)
            : _selfSocketFd(-1)
            , _sevrSocketIp(sevrIp)
            , _sevrSocketPort(sevrPort)
        {}
        
        /* 析构函数 */
        ~TcpClient() {}

    public:
        /* 初始化 */
        void Init() 
        {
            // No.1 创建套socket文件套接字对象
            _selfSocketFd = socket(AF_INET, SOCK_STREAM, 0);
            if(_selfSocketFd < 0)
            {
                LogMessage(FATAL, "create socket fail!");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL, "create socket success...");

            // No.2 创建链接
            struct sockaddr_in local; // 创建sockAddr对象
            bzero(&local, sizeof(local)); // 初始化对象
            local.sin_family = AF_INET; // 绑定协议家族
            local.sin_addr.s_addr = inet_addr(_sevrSocketIp.c_str()); // 绑定服务器Ip
            local.sin_port = htons(_sevrSocketPort); // 绑定服务器端口号
            int connectState = connect(_selfSocketFd, (struct sockaddr*)&local, sizeof(local));
            if(connectState < 0)
            {
                LogMessage(FATAL, "connect socket fail!");
                exit(CONNECT_ERR);   
            }
            LogMessage(NORMAL, "connect socket success...");
        }

        /* 启动 */
        void Start()
        {
            // 运行
            while(true)
            {
                // 响应
                std::cout << "Plase Say# ";
                std::string outBuffer;
                std::getline(std::cin, outBuffer);
                write(_selfSocketFd, outBuffer.c_str(), outBuffer.size());

                // 等待回复
                char inBuffer[g_num] = { '\0' };
                int n = read(_selfSocketFd, inBuffer, sizeof(inBuffer) - 1);
                if(n > 0)
                {   
                    // 处理读取到的内容
                    inBuffer[n] = '\0';
                    std::cout << inBuffer << std::endl;
                }
                else if(n == 0) // 在读取的时候,如果读取到了0,说明服务器已经退出了,这时候客户端也可以退出了!
                {
                    LogMessage(NORMAL, "server quit me too...");
                    break;
                }
            }

            close(_selfSocketFd);
        }

    private:
        int             _selfSocketFd;
        std::string     _sevrSocketIp;
        uint16_t        _sevrSocketPort;
    };
};

【7.4.2】TcpClient.cc file code

#include <memory>
#include "TcpClient.hpp"

using namespace Client;

/* 函数:消息提示 */ 
void Usage(char* argv)
{
    std::cout << "Usage:\n\t" << argv << " local_port\n\n" << std::endl;
}

/* 函数:程序入口函数 */
int main(int argc, char** argv)
{
    // 检查用户启动命令
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 将客户端对象转交给智能指针进行管理
    // 获取用户指定的IP地址和端口号
    std::string userIp = argv[1];
    uint16_t userPort = atoi(argv[2]);
    std::unique_ptr<TcpClient> pTcpClit(new TcpClient(userIp, userPort));
    pTcpClit->Init();
    pTcpClit->Start();
    
    return 0;
}

【9】TCP protocol communication process

[Server initialization]

  • Call the socket to create a file descriptor.

  • Call bind to bind the current file descriptor and ip/port together; if the port is already occupied by other processes, bind will fail.

  • Call listen to declare the current file descriptor as a server file descriptor to prepare for subsequent accept.

  • Call accecpt and block, waiting for the client to connect.

[Process of establishing connection]

  • Call the socket to create a file descriptor.

  • Call connect to initiate a connection request to the server.

  • connect will send out a SYN segment and block waiting for the server to respond; (first time).

  • When the server receives the client's SYN, it will respond with a SYN-ACK segment indicating "agree to establish a connection"; (second time).

  • After receiving SYN-ACK, the client will return from connect() and respond with an ACK segment; (the third time).

This process of establishing a connection is usually called a three-way handshake .

[Process of data transmission]

  • After the connection is established, the TCP protocol provides full-duplex communication services; the so-called full-duplex means that in the same connection, at the same time, both communicating parties can write data at the same time; the opposite concept is called half-duplex, in which the same connection At the same time, only one party can write data.

  • The server calls read() immediately after returning from accept(). Reading the socket is like reading a pipe. If no data arrives, it blocks and waits.

  • At this time, the client calls write() to send a request to the server. After receiving it, the server returns from read() to process the client's request. During this period, the client calls read() to block and wait for the server's response.

  • The server calls write() to send the processing results back to the client, and calls read() again to block and wait for the next request.

  • After receiving it, the client returns from read() and sends the next request, and the cycle continues.

[Process of disconnection]

  • If the client has no more requests, close() is called to close the connection, and the client will send a FIN segment to the server (for the first time).

  • At this time, after the server receives the FIN, it will respond with an ACK, and read will return 0 (the second time).

  • After read returns, the server knows that the client has closed the connection and calls close to close the connection. At this time, the server will send a FIN; (the third time) to the client.

  • The client receives the FIN and returns an ACK to the server; (the fourth time).

This disconnection process is often called four waves .

When learning the socket API, pay attention to how the application and the TCP protocol layer interact.

  • What actions does the TCP protocol layer complete when an application calls a socket function? For example, calling connect() will send out a SYN segment.

  • How does the application know the status changes of the TCP protocol layer? For example, returning from a blocked socket function indicates that the TCP protocol has received certain segments. For example, if read() returns 0, it indicates that the FIN segment has been received.

【10】Comparison between TCP and UDP

Reliable transmission Unreliable transmission
TCP communication UDP communication
There is a link no connection
byte stream data pack

Guess you like

Origin blog.csdn.net/lx473774000/article/details/132865452