• Linux:Socket套接字编程 | TCP



    全文约 10031 字,预计阅读时长: 29分钟


    函数指针类型

    ---//tcp_server.hpp
    #include 
    namespace ns_tcp_server
    {
        typedef void (*handler_t)(int);
        void hand_add(int x)
        {
            ++x;
            std::cout<<x<<std::endl;
        }
    
        void hand_sub(int x)
        {
            --x;
            std::cout<<x<<std::endl;
        }
        void loop(handler_t hand)
        {
            int a = 25;
            hand(a);
        }
    }
    ---//main.cc
    #include "tcp_server.hpp"
    using namespace ns_tcp_server;
    
    int main()
    {
        loop(hand_sub);
        //loop(hand_sub);
        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
    • Windows与Linux云服务器之间传文件:首先,服务器要安装了rz,sz;yum install lrzsz;
    • 运行rz,会将windows的文件传到linux服务器;运行sz filename,会将文件下载到windows本地。

    套接字TCP协议编程

      TCP 相比 UDP,流套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。

      TCP服务端:首先需要创建一个监听套接字文件;接下来绑定IP地址和端口号到监听套接字文件;然后进行监听;最后获取连接,进行数据的收发和处理。

      TCP客户端:首先创建套接字;不需要绑定;接着请求连接;然后完成数据的收发。
    在这里插入图片描述


    创建绑定

    先创建的套接字一般叫做:监听套接字。相当于迎宾人员,真正进行通信的是获取连接后返回的套接字文件。

    • int socket(int domain, int type, int protocol);,socket() 打开一个网络通讯端口,如果成功的话,就像 open ()一样返回一个文件描述符;
      • domain :对于IPv4, family参数指定为 AF_INET;
      • type:对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议
    #include          
    #include 
    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock < 0)
    {
        std::cout << "socket error" << std::endl;
        exit(2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    绑定:服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后,就可以向服务器发起连接;;服务器需要调用bind绑定一个固定的网络地址和端口号。

    • int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);,bind() 成功返回0,失败返回-1。
      • sockfd:上一步创建的返回值。
      • const struct sockaddr *addr:IPV4 就使用 struct sockaddr_in;传参时强转成 struct sockaddr*
        • struct sockaddr_in.sin_addr.s_addr,是用来绑定IP地址的,参数推荐设置成:INADDR_ANY
      • socklen_t addrlen:上一个结构体的大小。
    • 网络地址为INADDR_ANY ,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址。
    struct sockaddr_in local;
    bzero(&local, sizeof(local));//结构体内容清零
    
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;
    
    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
       std::cerr << "bind error" << std::endl;
       exit(3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    监听、接受 | 发起连接

    listen()声明sockfd处于监听状态,,并且最多允许有backlog个客户端处于连接等待状态,,如果接收到更多的连接请求就忽略,,这里设置不会太大(一般是5)。

    • int listen(int sockfd, int backlog);,listen()成功返回0,失败返回-1。
    const int backlog =  5;
    if(listen(listen_sock,backlog)<0)
    {
       cerr << "listen error" <<  endl;
       exit(3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三次握手完成后,服务器调用 accept() 接受连接;如果服务器调用 accept() 时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。

    • int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);成功返回一个新的用于通信的套接字文件描述符;失败返回-1。
      • sockfd:创建的监听套接字
      • 后面两个:输入输出型参数,返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址;
    while(true)
    {
      struct sockaddr_in peer;
      socklen_t len = sizeof(peer);
      int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
       if(sock < 0)
       {
         std::cout << "warning: accept error" << std::endl;
         continue;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    客户端需要调用 connect() 连接服务器;connect和 bind 的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;

    • int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);connect()成功返回0,出错返回-1;
    struct sockaddr_in svr;
    bzero(&svr, sizeof(svr));
    svr.sin_family = AF_INET;
    svr.sin_port = htons(desc_port);
    svr.sin_addr.s_addr = inet_addr(desc_ip.c_str());
    
    if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) == 0)
    {
      std::cout << "connect success ..." << std::endl;
    }
    else
    {
     std::cout << "connect failed ..." << std::endl;
     return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    数据的收发

    • 应用程序可以像读写文件一样用 read/write 在网络上收发数据。
    • 还可以使用专门的数据发送和接收接口 send()recv()
      • 用哪个网络文件写入或读取,数据从哪个缓冲区读取或写入缓冲区,读写多少个字节,0采用阻塞的方式读取
      • 返回值:入成功返回实际写入或读取的的字节数,写入失败返回-1,同时错误码会被设置。
    int send(SOCKET s,const char FAR *buf ,int len ,int flags); 
    int recv(SOCKET s ,char FAR * buf ,int len ,int flags); 
    
    
    • 1
    • 2
    • 3

    TCP服务端

    • 使用类进行了封装,sv.hpp头文件
    #pragma once 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using  std::cout;
    using  std::endl;
    using  std::cin;
    using  std::cerr;
    using  std::string;
    
    namespace ns_tcp_server
    {
        typedef void (*handler_t)(int);
        
        const int backlog =  5;
    
        class TcpSe
        {
            private:
                uint16_t _port;
                int listen_sock;
            public:
                TcpSe(uint16_t port)
                    :_port(port)
                     ,listen_sock(-1)
            {}
            void InitSe()
            {
                listen_sock = socket(AF_INET,SOCK_STREAM,0);
                if(listen_sock<0)
                {
                     cerr << "socket error" << endl;
                    exit(1);
                }
                struct sockaddr_in local;
                bzero(&local,sizeof(local));
    
                local.sin_family =AF_INET;
                local.sin_port = htons(_port);
                local.sin_addr.s_addr = INADDR_ANY;
    
                if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
                {
                     cerr << "bind error" <<  endl;
                    exit(2);
                }
                //监听 正式传递数据之前,要先建立连接
                if(listen(listen_sock,backlog)<0)
                {
                     cerr << "listen error" <<  endl;
                    exit(3);
                }
            }
            //循环获取连接,通信,处理数据
            void Loop(handler_t hand)
            {
                while(true)
                {
                    struct sockaddr_in peer;
                    socklen_t len = sizeof(peer);
                    int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
                    if(sock < 0)
                    {
                         cout<< "warning : accept error" <<  endl;
                        continue;
                    }
                    
                    uint16_t peer_port = ntohs(peer.sin_port);
                     string peer_ip = inet_ntoa(peer.sin_addr);//IP风格转换
                     cout << " debug: ip : "<< peer_ip <<" port: "<< peer_port<< endl;
                    
                    hand(sock);
                    
                    close(sock);//线程版本应该,让线程关闭
                }
            }
            ~TcpSe()
            {
                if(listen_sock >= 0)
                {
                    close(listen_sock);
                }
            }
        };
    }
    
    
    • 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
    • 服务端sv.cc
    #include "hd.hpp"             
    #include "sv.hpp"     
    
    void Usage(std::string proc)
    {
        std::cerr <<"Usage " <<"\n\t" << proc <<"_ port "<<std::endl;
    }
    
    //.server port
    int main(int argc,char* argv[])
    {
    //    if(argc != 2)
    //    {
    //        Usage(argv[0]);
    //        exit(1);
    //    }
        uint16_t port = atoi("8081");
        ns_tcp_server::TcpSe* svr = new ns_tcp_server::TcpSe(port);
        svr->InitSe();
        std::cout << "初始化完成"<<std::endl;
        //svr->Loop(ns_handler::hand_commn);
        //svr->Loop(ns_handler::hand_multiprocess);
        //svr->Loop(ns_handler::hand_multithread);
        svr->Loop(ns_handler::hand_tpool);
        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

    回调函数:处理通信数据 | 简易的网络翻译

    • hd.hpp包含:单进程版本、多进程版本、多线程版本、线程池版本
    #include "sv.hpp"
    #include "tpol.hpp"
    #include 
    #include 
    #include 
    #include 
    
    #define SIZE 1024
    
    namespace ns_handler
    {
        void hand_help(int sock)
        {
             cout << "回调函数被调用 debug: "<< sock << endl;
            while(true)
            {
                char buff[SIZE];
                ssize_t s = read(sock,buff,sizeof(buff)-1);
                if(s>0)
                {
                    buff[s]=0;
                    cout<< "client: "<<buff << endl;
                    string echo_message = buff;
                    if(echo_message == "quit")
                    {
                        break;
                    }
                    echo_message += "server says";
                  // cout<
                   send(sock,echo_message.c_str(),echo_message.size(),0);
                    //write(sock,echo_message.c_str(),echo_message.size());
                }
                else if (s==0)
                {
                     cout <<sock << ": client quit----"<< endl;
                    break;
                }
                else{
                     cerr <<"read error"<< endl;
                    break;
                }
            }
        }
        void hand_commn(int sock)
        {
            hand_help(sock);
        }
        
        //让孙子进程去执行,主进程继续去获取连接;孙子进程继承父进程的fd,用完关闭即可。
        void hand_multiprocess(int sock)
        {
            if(fork() == 0)
            {
                //child
                if(fork()>0)
                {
                    exit(0);
                }
                //grandson 孤儿进程 被os领养
                hand_help(sock);
                exit(0);
            }
            waitpid(-1,nullptr,0);
        }
        void* routine(void* args)
        {
            int sock = *(int*)args;
            delete (int*)args;
    
            cout<< "线程_sock:"<<sock<<endl;
            pthread_detach(pthread_self());
    
            hand_help(sock);
            close(sock);
            return nullptr;
        }
    
        void hand_multithread(int sock)
        {
            cout<<"sock: "<<sock<<endl;
            pthread_t tid;
            int* p = new int(sock);
            pthread_create(&tid,nullptr,routine,p);
        }
        //线程池版本
        class task
        {
            private:
                int sock;
            public:
                task(){}
                task(int sk):sock(sk){}
                void operator()()
                {
                    cout<<"当前处理客户数据的线程是"<<pthread_self()<<endl;
                    hand_help(sock);
                    close(sock);
                }
                ~task(){}
        };
        
        void hand_tpool(int sock)
        {
            ThreadPool<task>::Get_inst(5)->pusht(task(sock));
        }
    }
    
    
    
    
    
    
    • 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
    • 线程池:tpol.hpp
    #pragma once 
    #include 
    #include 
    #include 
    using std::queue;
    
    template<class T>
    class ThreadPool
    {
        private:
            queue<T> q;
            pthread_mutex_t lock;
            pthread_cond_t cond;
        public:
            ThreadPool()
            {
                pthread_mutex_init(&lock,nullptr);
                pthread_cond_init(&cond,nullptr);
            }
            ThreadPool(const ThreadPool<T>&) = delete;
            ThreadPool<T>& operator = (const ThreadPool<T>&) = delete;
            static ThreadPool<T>* inst;
        public:
            static ThreadPool<T>* Get_inst(int num)
            {
                static pthread_mutex_t mtx  = PTHREAD_MUTEX_INITIALIZER;
                if(nullptr == inst)
                {
                    pthread_mutex_lock(&mtx);
                    if(nullptr == inst)
                    {
                        inst = new ThreadPool<T>();
                        inst->InTp(num);//线程池创建 num个 线程 申请及创建
                    }
                    pthread_mutex_unlock(&mtx);
                }
                return inst;
            }
            static void* Run(void* args)//类的内部所以要设置成静态的
            {//线程执行函数只能由一个参数
                pthread_detach(pthread_self());
                ThreadPool* tp = (ThreadPool*)args;
                while(true)
                {
                    //上锁,线程间互斥,保证数据的安全一致性。
                    //条件变量,避免线程饥饿,保持线程间同步
                    //通过接口使用this属性的方法,美观环保。
                    tp->Lkq();
                    while(tp->Ispty())//while排除伪唤醒
                    {//任务队列没任务就阻塞等待。
                        tp->cnwait();
                    }
                    //从队列里取任务。
                    T t;
                    tp->popt(&t);
                    tp->ulkq();
                    t();//处理任务
                }
            }
            void Lkq()
            {
                pthread_mutex_lock(&lock);
            }
            void ulkq()
            {
                pthread_mutex_unlock(&lock);
            }
            void cnwait()
            {
                pthread_cond_wait(&cond,&lock);
            }
            void cnwake()
            {
                pthread_cond_signal(&cond);
            }
            bool Ispty()
            {
                return q.size() == 0;
            }
            void popt(T* out)
            {
               *out= q.front();
               q.pop();
            }
            void InTp(int num)//创建n个线程
            {
                for(auto i = 0; i < num;i++)
                {
                    pthread_t tid;
                    pthread_create(&tid,nullptr,Run,this);
                    //传this 使用类中的属性以及方法
                }
            }
            void pusht(const T& in)
            {
                //放任务,要么没放,要么放了,加锁保证数据的原子性
                //然后唤醒消费者取任务
                Lkq();
                q.push(in);
                cnwake();
                ulkq();
            }
            ~ThreadPool()
            {
                pthread_mutex_destroy(&lock);
                pthread_cond_destroy(&cond);
            }
    };
    
    
    • 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

    TCP客户端

    • cl.hpp
    #pragma once 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using std::cout;
    using std::cin;
    using std::string;
    using std::cerr;
    
    namespace ns_client
    {
        class Tcp_client
        {
            private://需要服务器的IP和端口
                 string desc_ip;
                uint16_t desc_port;
                int sock;
            public: 
                Tcp_client( string _ip,uint16_t _port)
                    :desc_ip(_ip)
                     ,desc_port(_port)
                {}
                void Init_Client()
                {
                    //sock
                    sock = socket(AF_INET,SOCK_STREAM,0);
                    if(sock<0)
                    {
                         cerr << "socket error"<<std::endl;
                        exit(1);
                    }
                    //不需要绑定 不需要监听 不需要accept
                }
                //通信之前 需要先建立连接
                void Start()
                {
                    //填充对方的socket信息
                    struct sockaddr_in svr;
                    bzero(&svr,sizeof(svr));
                    svr.sin_family = AF_INET;
                    svr.sin_addr.s_addr = inet_addr(desc_ip.c_str());
                    svr.sin_port = htons(desc_port);
    
                    //2.发起连接请求
                    if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))==0)
                    {
                         cout<< "connect succes"<<std::endl;
                    }
                    else{
                         cout<< "connect failed"<<std::endl;
                        return;
                    }
                    
                    //3.完成业务逻辑
                    while(true)
                    {
                        char buff[1024]={0};
                         cout<<sock<<"please enter ";
                        fflush(stdout);
                            
                        ssize_t s = read(0,buff,sizeof(buff)-1);
                        if(s>0)
                        {
                            buff[s-1]=0;
                            write(sock,buff,strlen(buff));
                           int rs =  recv(sock,buff,sizeof(buff)-1,0);
                           if(rs>0){
                           buff[rs]=0;
                           cout<<buff<<std::endl;
                           }
                           else{
                               break;
                           }
    //                        ssize_t rs = read(sock,buff,sizeof(buff)-1);
    //                        if(rs>0)
    //                        {
    //                            buff[s]=0;
    //                             cout<
    //                        }
    //                        else{
    //                             cout<<"server close---"<
    //                            break;
    //                        }
                        }
                    }
                }
                ~Tcp_client()
                {
                    if(sock>=0)
                    {
                        close(sock);
                    }
                }
        };
    }
    
    
    • 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
    • cl.cc
    #include "cl.hpp"
    #include 
    
    void Usage( string proc)
    {
         cerr<<"Usage :"<<"\n\t"<<proc<<"_ip  _port"<<std:: endl;
    }
    
    int main(int argc,char* argv[])
    {
       // if(argc!=3)
       // {
       //     Usage(argv[0]);
       //     return 1;
       // }
         string ip = "127.0.0.1";
        uint16_t port = atoi("8081");
    
        ns_client::Tcp_client cli(ip,port);
        cli.Init_Client();
        cli.Start();
        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

    查看UDP | TCP 进程服务

    • netstat -ntlp
    • 测试:
    • 写好的服务端代码,还没写客户端时,可以用命令:telnet ip 端口号,进行测试;再按Ctrl ],即可进行通信;再按一次,输入 quit 退出。
    • Linux下安装telnet

    三次握手、四次挥手

      客户端的connect触发三次握手,底层由OS自动完成,建立连接;服务端的accept()是三次握手完成后, 服务器调用 accept() 接受连接。由于TCP协议是全双工通信;所以两端都要关闭文件描述符,这就称之为四次挥手。服务端、客户端各自的close 执行挥手中的两次,也是有OS自动完成的。全双工通信:任何时刻,我可以给你发数据,你也可以给我发;半双工通信:任何时刻只能单向的发送数据。

    建立连接的本质是,双方的操作系统,构建相应的内核数据结构(结构体),后续维护连接;因此建立连接也是有时间、空间成本的。计算机的名词对应着OS中的数据结构。


  • 相关阅读:
    14.(地图数据篇)arcgis地图瓦片数据获取--java代码
    我自己制作的导航页网站,源码分享~
    RESTFul风格接口如何设计
    包管理工具--》npm的配置及使用(一)
    rust迭代器
    amd模块化——typeScript下载于安装以及运行 ——yarn下载——TS的运行——Ts数据类型描述
    智能答题功能,CRMEB知识付费系统必须有!
    公益校园网页制作 大学生网页设计作业 HTML CSS公益网页模板 大学生校园介绍网站毕业设计
    Autoware-建图
    推荐系统相关论文阅读整理
  • 原文地址:https://blog.csdn.net/WTFamer/article/details/126399646