• socket编程


    预备知识

    • IP地址(公网IP)唯一地表示互联网中的一台主机
    • 源IP,目的IP:对一个报文来讲,从哪来,到哪去

    在这里插入图片描述
    在这里插入图片描述

    • 互联网世界,是一个进程间通信的世界
    • 我要和你聊天,实际上是我手机上的微信和你手机上的微信进行聊天
    • 物联网世界,不同的设备(冰箱和电视)可以进行沟通
      在这里插入图片描述
      一个进程可以关联多个端口号吗?-可以
      一个端口号可以关联多个进程吗?- 不能

    初步认识TCP(Transmission Control Protocol)

    是一个靠谱的协议
    在这里插入图片描述

    初步认识UDP(User Datagram Protocol)

    不可靠的在这里插入图片描述

    • 可靠和不可靠是中性词,没有谁好谁坏

    socket常见API

    在这里插入图片描述

    socket

    简单的udp程序

    在这里插入图片描述

    在这里插入图片描述

    • 作为一个服务器,要不要让用户知道对应服务器的地址(ip+port)?当然要
    • htos主机转网络
      在这里插入图片描述
    • inet_addr推荐使用,调这一个函数可以完成以下转换
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • INADDR_ANY 不关心是通过哪,都个ip上来的,只要访问的是这个端口,都要把这个数据给你
    • bind接口
      在这里插入图片描述
    • udp读取的方式recvfrom
      在这里插入图片描述
      在这里插入图片描述
    • sendto发送
      在这里插入图片描述
    代码udp_server.cc
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    //以上四个头文件经常会用
    
    const uint16_t port = 8080;
    
    //udp_server
    int main()
    {
        //1. 创建套接字,打开网络文件
        //第一个参数作用域对应IPV4,第二个类型对应udp的数据报,第三个协议类型设为0
        int sock = socket(AF_INET,SOCK_DGRAM,0);
        if(sock<0)
        {
            std::cerr<<"socket creat error: "<<errno<<std::endl;
            return 1;
        }
    
        //给该服务器绑定端口和ip(特殊处理)
        struct sockaddr_in local;
        local.sin_family = AF_INET;//对应IPV4
        local.sin_port = htons(port);//此处的端口号,是我们计算机上的变量,是主机序列
        // a. 需要将人识别的点分十进制,字符串风格的IP地址,转化成四字节整数IP
        // b. 也要考虑大小端
        //坑:
        //云服务器,不允许用户直接bind公网ip,另外,正常编写的时候,我们也不会指明IP
        //INADDR_ANY: 如果你的bind是确定的IP(主机),以为者发到该IP主机上面的数据才会交给
        //你的网络进程,但是,一般服务器配置有多张网卡,配置多个IP,我们需要的不是某个IP上面的
        //数据,我们需要的是,所有发送到该主机,发送到该端口的数据
        local.sin_addr.s_addr = INADDR_ANY;
    
        //2. bind
        //绑定成功=0,绑定失败<0
        //local本地已经配置好了,传进去
        if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
        {
            std::cerr<<"bind error : "<<errno<<std::endl;
            return 2;
        }
    
        //3. 提供服务
        //所有网络服务器都是一个死循环
        //如果是udp,不可以通过文件的形式读取,有专门的接口
        bool quit = false;
        #define NUM 1024
        char buffer[NUM];
        while(!quit)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //接收客户端消息
            recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
    
            std::cout<<"client# "<<buffer<<std::endl;
    
            std::string echo_hello = "hello";
            sendto(sock,echo_hello.c_str(),echo_hello.size(),0,(struct sockaddr*)&peer,len);
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    代码udp_client.cc
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    void Usage(std::string proc)
    {
        std::cout << "Usage: \n\t" << proc << "server_ip server_port" << std::endl;
    }
    
    //./udp_client server_ip server_port
    int main(int argc, char *argv[])
    {
        if (argc != 3)
        {
            Usage(argv[0]);
            return 0;
        }
    
        // 1.创建套接字,打开网络文件
        // IPV4协议,udp数据包,默认方式
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0)
        {
            //如果创建失败,就显示错误信息
            std::cerr << "socket error :" << errno << std::endl;
            return 1;
        }
    
        //客户端需要显示的bind吗?
        // a. 首先,客户端也要有ip和port
        // b. 但是,客户端不需要显示的bind。一旦显示的bind,就要明确,client和哪一个port关联
        // client指明的端口号,在client端一定会有吗?有可能被占用,就导致client无法使用
        // server要的是port必须明确,而且不变,但client只要有即可,一般是由OS自动给你bind()
        //就是client正常发送数据的时候,OS自动给你bind,采用的是随机端口的方式
    
        // b. 要发给谁?
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        server.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
        // 2.使用服务
        while (1)
        {
            // a. 数据从哪来
            std::string message;
            std::cout << "输入# ";
            std::cin >> message;
    
            //客户端发送
            sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server)) ;
    
            //此处的tmp就是一个占位符而已
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp);
            char buffer[1024];
            recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&len);
    
            std::cout<<"server echo#"<<buffer<<std::endl;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    代码Makefile
    .PHONY:all
    all:udp_server udp_client
    
    udp_server:udp_server.cc
    	g++ -o $@ $^ -std=c++11
    
    udp_client:udp_client.cc
    	g++ -o $@ $^ -std=c++11
    
    .PHONY:clean
    clean:
    	rm -f udp_server udp_client
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    在这里插入图片描述

    升级的udp代码

    • feof判断是否读到文件结尾 在这里插入图片描述
    • fgets获取文件中的内容,一行一行读数据
      在这里插入图片描述
      在这里插入图片描述
    udp_server.cc
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    //以上四个头文件经常会用
    
    // const uint16_t port = 8080;
    
    std::string Usage(std::string proc)
    {
        std::cout << proc << " port" << std::endl;
    }
    // udp_server
    //./udp_server port
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            return -1;
        }
    
        uint16_t port = atoi(argv[1]);
        // 1. 创建套接字,打开网络文件
        //第一个参数作用域对应IPV4,第二个类型对应udp的数据报,第三个协议类型设为0
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0)
        {
            std::cerr << "socket creat error: " << errno << std::endl;
            return 1;
        }
    
        //给该服务器绑定端口和ip(特殊处理)
        struct sockaddr_in local;
        local.sin_family = AF_INET;   //对应IPV4
        local.sin_port = htons(port); //此处的端口号,是我们计算机上的变量,是主机序列
        // a. 需要将人识别的点分十进制,字符串风格的IP地址,转化成四字节整数IP
        // b. 也要考虑大小端
        //坑:
        //云服务器,不允许用户直接bind公网ip,另外,正常编写的时候,我们也不会指明IP
        // INADDR_ANY: 如果你的bind是确定的IP(主机),以为者发到该IP主机上面的数据才会交给
        //你的网络进程,但是,一般服务器配置有多张网卡,配置多个IP,我们需要的不是某个IP上面的
        //数据,我们需要的是,所有发送到该主机,发送到该端口的数据
        local.sin_addr.s_addr = INADDR_ANY;
    
        // 2. bind
        //绑定成功=0,绑定失败<0
        // local本地已经配置好了,传进去
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            std::cerr << "bind error : " << errno << std::endl;
            return 2;
        }
    
        // 3. 提供服务
        //所有网络服务器都是一个死循环
        //如果是udp,不可以通过文件的形式读取,有专门的接口
        bool quit = false;
    #define NUM 1024
        char buffer[NUM];
        while (!quit)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //接收客户端消息
            ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (cnt > 0)
            {
                buffer[cnt] = 0; // 0 == '\0'
                FILE *fp = popen(buffer, "r");
                std::string echo_hello;
                char line[1024] = {0};
                while (fgets(line, sizeof(line), fp) != NULL)
                {
                    echo_hello += line;
                }
                pclose(fp);
                std::cout << "client# " << buffer << std::endl;
    
                echo_hello += "...";
                sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    udp_client.cc
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    void Usage(std::string proc)
    {
        std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
    }
    
    //./udp_client server_ip server_port
    int main(int argc, char *argv[])
    {
        if (argc != 3)
        {
            Usage(argv[0]);
            return 0;
        }
    
        // 1.创建套接字,打开网络文件
        // IPV4协议,udp数据包,默认方式
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0)
        {
            //如果创建失败,就显示错误信息
            std::cerr << "socket error :" << errno << std::endl;
            return 1;
        }
    
        //客户端需要显示的bind吗?
        // a. 首先,客户端也要有ip和port
        // b. 但是,客户端不需要显示的bind。一旦显示的bind,就要明确,client和哪一个port关联
        // client指明的端口号,在client端一定会有吗?有可能被占用,就导致client无法使用
        // server要的是port必须明确,而且不变,但client只要有即可,一般是由OS自动给你bind()
        //就是client正常发送数据的时候,OS自动给你bind,采用的是随机端口的方式
    
        // b. 要发给谁?
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        server.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
        // 2.使用服务
        while (1)
        {
            // // a. 数据从哪来
            // std::string message;
            // std::cout << "输入# ";
            // std::cin >> message;
            std::cout<< "My Shell $ ";
            char line[1024];
            fgets(line,sizeof(line),stdin);
            //客户端发送
            sendto(sock,line,strlen(line),0,(struct sockaddr*)&server,sizeof(server)) ;
    
            //此处的tmp就是一个占位符而已
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp);
            char buffer[1024];
            ssize_t cnt = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&len);
            if(cnt > 0)
            {
                //在网络通信中,只有报文大小,或者是字节流中字节的个数,没有C/C++字符串这样的概念(虽然后续可能有类似情况)
                buffer[cnt] = 0;
                std::cout<<"server echo#"<<buffer<<std::endl;
            }
    
    
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    tcp代码

    • accept获取上来新的链接
      在这里插入图片描述

    • tcp也有的listen监听状态,backlog是相应套接字排队的最大连接个数

    • read第一个参数文件描述符,第二个读的数据放到缓冲区里,第三个期望读多少字节
      在这里插入图片描述
      在这里插入图片描述

    • 第一个参数识别服务器套接字,第二个参数保存套接字对应的“地方”(客户端IP和端口信息),第三个参数是“地方”的占地大小,返回值对应套接字表示,返回值小于0就是出现了error
      在这里插入图片描述
      在这里插入图片描述

    • bezero 把一段缓冲区清0,不太推荐,等同于memset的功能
      在这里插入图片描述

    • 获取IP地址 在这里插入图片描述

    Makefile
    .PHONY:All
    All:tcp_client tcp_server
    
    tcp_client:tcp_client.cc
    	g++ -o $@ $^ -std=c++11
    
    tcp_server:tcp_server.cc
    	g++ -o $@ $^ -std=c++11
    
    .PHONY:clean
    clean:
    	rm -f tcp_client tcp_server
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Task.hpp
    #pragma once
    
    #include 
    #include 
    #include 
    
    namespace ns_task
    {
        class Task
        {
        private:
            int sock;
    
        public:
            Task() {}
            Task(int _sock) : sock(_sock) {}
            int Run()
            {
                char buffer[1024];
                memset(buffer, 0, sizeof(buffer));
                ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
                if (s > 0) //读到数据了
                {
                    buffer[s] = 0; //将获取的内容变成字符串
                    std::cout << "client# " << buffer << std::endl;
                    //拉取逻辑
                    std::string echo_string = ">>>server<<<,";
                    echo_string += buffer;
                    write(sock, echo_string.c_str(), echo_string.size());
                }
                else if (s == 0) //读完了
                {
                    std::cout << "client quit ..." << std::endl;
                    // break;
                }
                else // s<0 出错了
                {
                    std::cerr << "read error" << std::endl;
                    //break;
                }
    
                close(sock);
            }
    
            ~Task(){}
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    thread_pool.hpp
    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    namespace ns_threadpool
    {
        const int g_num = 5;
    
        template <class T>
        class ThreadPool
        {
        private:
            int num_;
            std::queue<T> task_queue_; //该成员是一个临界资源
    
            pthread_mutex_t mtx_;
            pthread_cond_t cond_;
    
            static ThreadPool<T> *ins;
    
        private:
            // 构造函数必须得实现,但是必须的私有化
            ThreadPool(int num = g_num) : num_(num)
            {
                pthread_mutex_init(&mtx_, nullptr);
                pthread_cond_init(&cond_, nullptr);
            }
            ThreadPool(const ThreadPool<T> &tp) = delete;
            //赋值语句
            ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;
    
        public:
            static ThreadPool<T> *GetInstance()
            {
                static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
                // 当前单例对象还没有被创建
                if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!
                {
                    pthread_mutex_lock(&lock);
                    if (ins == nullptr)
                    {
                        ins = new ThreadPool<T>();
                        ins->InitThreadPool();
                        std::cout << "首次加载对象" << std::endl;
                    }
                    pthread_mutex_unlock(&lock);
                }
    
                return ins;
            }
    
            void Lock()
            {
                pthread_mutex_lock(&mtx_);
            }
            void Unlock()
            {
                pthread_mutex_unlock(&mtx_);
            }
            void Wait()
            {
                pthread_cond_wait(&cond_, &mtx_);
            }
            void Wakeup()
            {
                pthread_cond_signal(&cond_);
            }
            bool IsEmpey()
            {
                return task_queue_.empty();
            }
    
        public:
            // 在类中要让线程执行类内成员方法,是不可行的!
            // 必须让线程执行静态方法
            static void *Rountine(void *args)
            {
                pthread_detach(pthread_self());
                ThreadPool<T> *tp = (ThreadPool<T> *)args;
    
                while (true)
                {
                    tp->Lock();
                    while (tp->IsEmpey())
                    {
                        //任务队列为空,线程该做什么呢??
                        tp->Wait();
                    }
                    //该任务队列中一定有任务了
                    T t;
                    tp->PopTask(&t);
                    tp->Unlock();
    
                    t.Run();
                }
            }
            void InitThreadPool()
            {
                pthread_t tid;
                for (int i = 0; i < num_; i++)
                {
                    pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
                }
            }
            void PushTask(const T &in)
            {
                Lock();
                task_queue_.push(in);
                Unlock();
                Wakeup();
            }
            void PopTask(T *out)
            {
                *out = task_queue_.front();
                task_queue_.pop();
            }
            ~ThreadPool()
            {
                pthread_mutex_destroy(&mtx_);
                pthread_cond_destroy(&cond_);
            }
        };
    
        template <class T>
        ThreadPool<T> *ThreadPool<T>::ins = nullptr;
    } // namespace ns_threadpool
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    tcp_server.cc
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "thread_pool.hpp"
    #include "Task.hpp"
    
    using namespace ns_task;
    using namespace ns_threadpool;
    
    void Usage(std::string proc)
    {
        std::cout << proc << " port" << std::endl;
    }
    
    // void *HandlerRequest(void *args)
    // {
    //     pthread_detach(pthread_detach(pthread_self()));
    //     int sock = *(int *)args;
    //     delete (int *)args;
    
    //     ServiceIO(sock);
    //     close(sock);
    // }
    
    // void ServiceIO(int new_sock)
    // {
    //     while (true)
    //     {
    //         char buffer[1024];
    //         memset(buffer, 0, sizeof(buffer));
    //         ssize_t s = read(new_sock, buffer, sizeof(buffer) - 1); //不用读\0
    
    //         if (s > 0) //读到消息了
    //         {
    //             buffer[s] = 0; //将读取的内容当成字符串
    //             std::cout << "client# " << buffer << std::endl;
    
    //             std::string echo_string = ">>>server<<<, ";
    //             echo_string += buffer;
    //             // write是返回消息
    //             write(new_sock, echo_string.c_str(), echo_string.size());
    //         }
    //         else if (s == 0) //读完了
    //         {
    //             std::cout << "client quit ..." << std::endl;
    //             break;
    //         }
    //     }
    // }
    
    //./tcp_server 8081
    int main(int argc, char *argv[])
    {
        //启动服务器只要两个参数即可
        if (argc != 2)
        {
            Usage(argv[0]);
            return 1;
        }
    
        // 1. 创建套接字
        int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (listen_sock < 0)
        {
            std::cerr << "socket error: " << errno << std::endl;
            return 2;
        }
    
        // 2. bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET; // IPV4
        local.sin_port = htons(atoi(argv[1]));
        local.sin_addr.s_addr = INADDR_ANY; //一台机器有多个网卡,无论哪个网卡传进来都能被收到
    
        if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            std::cerr << "bind error : " << errno << std::endl;
            return 3;
        }
    
        // 3. 因为tcp是面向连接的, a. 在通信前,需要建立连接 b. 才能进行通信
        //  一定有人主动建立(客户端,需要服务),一定有人被动接收服务(服务器,提供服务)
        //  我们当前写的是一个server,周而复始的不断的等待客户的到来
        //  我们要不断提供给用户一个建立连接的功能
        //  设置套接字是listen状态,本质是允许用户连接
        const int back_log = 5; // 套接字排队的最大连接数
        if (listen(listen_sock, back_log) < 0)
        {
            std::cerr << "listen error" << std::endl;
            return 4;
        }
    
       // signal(SIGCHLD, SIG_IGN); //在linux中父进程忽略子进程的SIGCHLD信号,子进程会自动退出释放资源
    
        for (;;)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); 
            int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
            if (new_sock < 0)
            {
                continue;
            }
            uint16_t cli_port = ntohs(peer.sin_port);
            std::string cli_ip = inet_ntoa(peer.sin_addr);
    
            //提供服务
            // version 1 :无人使用的单进程版本
            // ServiceIO(new_sock);
    
            std::cout << "get a new link ..." << new_sock << std::endl;
    
            // 1.构建一个新的任务
            Task t(new_sock);
            // 2.将任务push到后端的线程池
            ThreadPool<Task>::GetInstance()->PushTask(t);
    
    
            // vision4:
            //  vision 2,3: a. 创建线程,进程无上限 b. 客户的链接来了,我们才给用户创建进程/线程
    
            // //vision 3: 曾经被主线程打开的fd,新线程是否能看到,是否共享?能
            // pthread_t tid;
            // int* pram = new int(new_sock);
            // pthread_create(&tid,nullptr,HandlerRequest,pram);
    
            // version 2 版本
            //  pid_t id = fork(); //创建一个子进程
            //  if (id < 0)        //创建失败的情况
            //  {
            //      continue;
            //  }
            //  else if (id == 0)
            //  {
            //      close(listen_sock);//子进程关闭,不会影响父进程
    
            //     if(fork() > 0) exit(0);
    
            //     ServiceIO(new_sock);
            //     close(new_sock);//服务用完了,文件描述符就不用了,我们就把它关掉
            //     exit(0);
            // }
            // else
            // {
            //     // father
            //     // do noithing
            //     waitpid(id,nullptr,0);
            //     close(new_sock);
            // }
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    tcp_client.cc
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    void Usage(std::string proc)
    {
        std::cout << proc << " server_ip server_port" << std::endl;
    }
    
    // tcp_client server_ip server_port
    int main(int argc, char *argv[])
    {
        if (argc != 3)
        {
            Usage(argv[0]);
            return 1;
        }
    
        std::string svr_ip = argv[1];
        uint16_t svr_port = atoi(argv[2]);
    
        // 1. 创建socket
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) //创建失败的情况
        {
            std::cerr << "socket error: " << std::endl;
            return 2;
        }
    
        // client无需显示的bind,client->server
        // client -> connect
        struct sockaddr_in server;
        bzero(&server, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(svr_port);
        server.sin_addr.s_addr = inet_addr(svr_ip.c_str()); //该函数做两件事情 a.将点分十进制的字符串风格的IP,转化成4字节IP b. 将4字节IP转化为网络序列
    
        if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
        {
            std::cout << "connect server failed !" << std::endl;
            return 3;
        }
    
        std::cout << "connect success!" << std::endl;
    
        // 进行正常的业务请求了
        while (true)
        {
            std::cout << "Please Enter# ";
            char buffer[1024];
            fgets(buffer, sizeof(buffer)-1, stdin);
    
            write(sock, buffer, strlen(buffer));
    
            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server echo# " << buffer << std::endl;
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    了解过程

    • 1.创建socket的过程,socket(),本质是打开文件 – 仅仅有系统相关的内容
    • 2.bind(), struct sockaddr_in -> ip ,port 本质是ip + port 和文件信息进行关联
    • 3.listen(), 本质是设置该socket文件的状态,允许别人来连接我
    • 4.accept(),获取新链接倒应用层,是以fd为代表的
      当有很多个链接连上我们服务器的时候,OS肯定会有很多连接,如何管理?先描述,后组织
      所谓的“链接”,在OS层面本质就是一个描述链接的结构体
      -> 文件
    • 5.read/write,本质就是进行网络通信,但是,对于用户来讲,相当于我们进行正常的文件读写
    • 6.close(fd),关闭文件,a.系统层面,释放曾经申请的文件资源,连接资源等。 b. 网络层面,通知对方,我的连接已经关闭了 c.client && server, 本质在网络层面,其实就是在进行四次挥手
    • 7.connect(),本质是发起链接,在系统层面,就是构建一个请求报文发送过去 ,在网络层面,发起tcp链接的三次握手
  • 相关阅读:
    Spring项目-前端问题:Can‘t find variable:$
    前端面试中Vue的有经典面试题三
    Android学习笔记 1.6 Android应用结构分析
    xss-labs/level8
    能链科技完成重点项目签约
    第十二届蓝桥杯之货物摆放
    代码随想录算法训练营Day60|单调栈01
    外包干了4年,技术退步明显.......
    javaScript中的函数防抖和节流
    自然语言处理 中文停用词词典
  • 原文地址:https://blog.csdn.net/Morn_Star_Oliver/article/details/126419013