当前位置: 首页 > news >正文

socket编程

文章目录

    • 预备知识
      • 初步认识TCP(Transmission Control Protocol)
      • 初步认识UDP(User Datagram Protocol)
      • socket常见API
    • socket
      • 简单的udp程序
        • 代码udp_server.cc
        • 代码udp_client.cc
        • 代码Makefile
      • 升级的udp代码
        • udp_server.cc
        • udp_client.cc
      • tcp代码
        • Makefile
        • Task.hpp
        • thread_pool.hpp
        • tcp_server.cc
        • tcp_client.cc
    • 了解过程

预备知识

  • 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<iostream>
#include<cerrno>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
//以上四个头文件经常会用

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;
}

代码udp_client.cc

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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;
}

代码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

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

升级的udp代码

  • feof判断是否读到文件结尾 在这里插入图片描述
  • fgets获取文件中的内容,一行一行读数据
    在这里插入图片描述
    在这里插入图片描述

udp_server.cc

#include <iostream>
#include <cerrno>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//以上四个头文件经常会用

// 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;
}

udp_client.cc

#include <iostream>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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;
}

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

Task.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>

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(){}
    };
}

thread_pool.hpp

#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

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

tcp_server.cc

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#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;
}

tcp_client.cc

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

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.创建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链接的三次握手

相关文章:

  • 多传感器融合定位技术
  • 2022第十一届PMO大会(线上会议)成功召开
  • 公众号如何运营?教你几招超实用的公众号运营方法
  • C# 第六章『交互式图形界面』◆第5节:FolderBrowserDialog类、DialogResult枚举
  • 氨基化/环氧化/胺化/羧基化/巯基改性/笼空状磺化聚苯乙烯微球相关制备
  • docker-compose安装mysql主流版本及差异
  • 牛客多校3 - Journey(建图,最短路)
  • python如何实现数据可视化,如何用python做可视化
  • 开源治理的基本实践与指导原则
  • 【APP测试】怎么对App进行功能测试
  • Mybatis-Plus复习
  • 8、JAVA入门——switch选择结构
  • Inno Setup 创建Revit安装包
  • Windows和Linux使用FRP实现内网穿透
  • c++代码如何实现在win/linux下创建编译及部署后台服务,并管理其他服务
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 03Go 类型总结
  • 0基础学习移动端适配
  • canvas 五子棋游戏
  • Laravel Mix运行时关于es2015报错解决方案
  • mockjs让前端开发独立于后端
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • react-native 安卓真机环境搭建
  • SpringBoot几种定时任务的实现方式
  • 包装类对象
  • 搞机器学习要哪些技能
  • 聊聊flink的BlobWriter
  • 如何使用 JavaScript 解析 URL
  • 使用API自动生成工具优化前端工作流
  • 网络应用优化——时延与带宽
  • 回归生活:清理微信公众号
  • ​iOS实时查看App运行日志
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (14)Hive调优——合并小文件
  • (libusb) usb口自动刷新
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (四)c52学习之旅-流水LED灯
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)创业家杂志:UCWEB天使第一步
  • (转)为C# Windows服务添加安装程序
  • (转载)Linux网络编程入门
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .bat批处理出现中文乱码的情况
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • .net打印*三角形
  • .NET命名规范和开发约定
  • .NET中GET与SET的用法
  • .net中我喜欢的两种验证码
  • .sys文件乱码_python vscode输出乱码