• 【Linux 网络编程 】


    背景知识:

    • ipv4地址:32位 网络号+ 主机号
    • ipv6地址:128位
    • 端口号: short类型 应用程序的一个代号

    当我们进行网络编程的时候,只需要关注于对方进程的ip地址和端口号(0-1024为知名端口,用户不能随便使用,1024-4096为保留端口,用户也不能随便使用,端口号必须4096以上才可以用)就可以了。

    主机字节序列和网络字节序列

    主机字节序列分为大端节序和小端节序,不同的主机采用的节序不同。

    大端节序:高位字节存放在地址的低地址处。
    小端节序:高位字节存放在地址的高地址处。
    当两个字节节序不同的主机进行通信时,这样会产生冲突。
    所以就有了网络字节序列。
    网络字节序列规定使用整形数据时使用大端节序来传输。

    IP地址的转换

    人们习惯用点分十进制字符串表示 IPV4地址,但编程中我们需要先把它们转化。

    API

    网络编程接口

    int socket(int domain, int type, int protocol);创建套接字
    返回值:成功返回套接字的文件描述符,失败返回-1
    domain:设置套接字的协议簇,AF_UNIX AF_INET AF_INET6
    type:设置套接字的服务类型 SOCK_STREAM SOCK_DGRAM
    protocol:一般设置为0,表示使用默认协议
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);将 sockfd 与一个socket地址绑定。
    返回值:成功返回0,失败返回-1
    sockfd:是网络套接字描述符
    addr:地址结构
    addrlen: socket 地址的长度
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int listen(int sockfd, int backlog);创建一个监听队列以存储待处理的客户连接。
    返回值;成功返回0,失败返回-1
    sockfd:被监听的socket套接字
    backlog:表示处于完全连接状态的 socket的上限
    
    • 1
    • 2
    • 3
    • 4
    int accept(int sockfd, struct sockaddr*addr, socklen_t *addrlen);accept()从 listen 监听队列中接收一个连接,成功返回一个新的连接socket,该socket 唯一地标识了被接收的这个连接,失败返回-1
    sockfd:是执行过 listen系统调用的监听socket
    addr:用来获取被接受连接的远端socket地址
    addrlen:该socket地址的长度
    
    • 1
    • 2
    • 3
    • 4
    int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);客户端需要通过此系统调用来主动与服务器建立连接,
    返回值:成功返回0,失败返回-1
    sockfd:由socket返回的一个socket。
    serv_addr:服务器监听的socket地址
    addrlen:这个地址的长度
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int close(int sockfd);关闭一个连接,实际上就是关闭该连接对应的socket。
    
    • 1

    网络节序与主机节序转换函数

    #include
    uint32_t htonl(uint32_t hostlong);长整型的主机字节序转网络字节序
    uint32_t ntohl(uint32_t netlong);长整型的网络字节序转主机字节序
    uint16_t htons(uint16_t hostshort);短整形的主机字节序转网络字节序
    uint16_t ntohs(uint16_t netshort);短整型的网络字节序转主机字节序
    
    • 1
    • 2
    • 3
    • 4
    • 5

    IP地址转换函数

    #include 
    in_addr_t inet_addr(const char *cp);字符串表示的IPV4地址转化为网络字节序
    char* inet_ntoa(struct in_addr in);/IPV4地址的网络字节序转化为字符串
    
    • 1
    • 2
    • 3

    数据读写

    TCP数据读写

    ssize_t recv(int sockfd, void *buff, size_t len, int flags);读取sockfd上的数据,buff和 len参数分别指定读缓冲区的位置和大小
    ssize_t scnd(int sockfd, const void *buff, size_t len, int flags);往socket上写入数据,buff和len参数分别指定写缓冲区的位置和数据长度flags参数为数据收发提供了额外的控制
    
    • 1
    • 2

    UDP数据读写

    ssize_t recvfrom(int sockfd, void  * buff, size_t len, int flags,struct sockaddr* src_addr, socklen_t * addrlcn);读取 sockfd上的数据,buff和len参数分别指定读缓冲区的位置和大小,src_addr记录发送端的socket地址addrlen指定该地址的长度
    ssize_t sendto(int sockfd, void * buff, size_t len, int flags,struct sockaddr * dest_addr, socklen_t addrlcn);往socket上写入数据,buff和 len参数分别指定写缓冲区的位置和数据长度dest_addr指定接收数据端的 socket地址addrlen指定该地址的长度
    
    • 1
    • 2

    TCP编程

    编程步骤:

    1.服务端

     1. socket()
     2. bind()
     3. listen()backlog的最大值不能超过128,它的值加1则是全连接队列的大小。非阻塞函数
     4. accept()是不是阻塞取决于socket是阻塞还是非阻塞模式,一般默认创建的是阻塞模式。
     5. recv() 是不是阻塞取决于socket是阻塞还是非阻塞模式,一般默认创建的是阻塞模式。
     6. send()是不是阻塞取决于socket是阻塞还是非阻塞模式,一般默认创建的是阻塞模式。
     7. close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.客户端

     - socket() 
     - connect() 
     - send() 
     - recv() 
     - close()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们先来看一下三次握手的状态图。首先当客户端和服务端进行连接时,它是一种过程,中间有很多状态。
    Linux内核会为维护两个队列

    • 为SYN_RCVD状态的socket维护一个队列(半连接队列)

      等待收到对方的ACK,接着把该socket放到全连接队列中
      
      • 1
    • 为处于ESTABLISTENsocket维护一个队列(全连接队列)

      等待服务端进行accept的时候从这个队列中取出socket。
      
      • 1

    在这里插入图片描述
    listen函数的backlog的值加1则是这个全连接队列的大小。
    对于半连接队列的大小,我们可以通过下面命令来查看,它同样可以修改。
    在这里插入图片描述

    因为队列在内核中维护,这个也需要消耗大量资源,所以backlog的大小不能太大,不能超过128。
    所以说listen中backlog的值并不是服务端能连接客户端的大小。

    客户端链接服务端成功的条件

    1.端口,ip地址,服务类型都正确
    2.服务器正在运行
    3.网络正常
    4.服务器资源充足

    多线程实现服务端并发

    服务端
    threadser.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int sock_init()
    {
      int socketfd = socket(AF_INET,SOCK_STREAM,0);
      if(socketfd == -1)
      {
       std::cout<<"socket erro"<<std::endl;
       exit(1);
      }
      struct sockaddr_in saddr,caddr;
      memset(&saddr,0,sizeof(saddr));
      saddr.sin_family = AF_INET;
      saddr.sin_port=htons(6000);
      saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
      int res = bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr));
      if(res == -1)
      {
       std::cout<<"bind erro "<<std::endl;
       exit(1);
      }
      res = listen(socketfd,5);
      if(res == -1)
      {
       std::cout<<"listen erro"<<std::endl;
        exit(1);
      }
     return socketfd;
    
    }
    void * fun(void *);
    int main()
    {
     int socketfd = sock_init();
     while(1)
     {
    
       struct sockaddr_in caddr;
       int len = sizeof(caddr);
       int c = accept(socketfd,(struct sockaddr *)&caddr,(socklen_t *)&len);
       if(c<=0)
       {
         std::cout<<"accept erro"<<std::endl;
         break;
       
       }
       std::cout<<"c=  "<<c<<"ip=  "<<inet_ntoa(caddr.sin_addr)<<"port=  "<<ntohs(caddr.sin_port)<<std::endl;;
       pthread_t id;
       pthread_create(&id,NULL,fun,(void *)(long)c);
     
       }
    }
    void * fun(void * arg)
    {
     int c = (int)(long)arg;
      while(1)
      {
       char buff[128]={0};
       int n = recv(c,buff,1,0);
       if(n<=0)
       {
        break;
       
       }
       std::cout<<"recive ="<<buff<<"len="<<n<<std::endl;
       send(c,"ok",2,0);
      }
      std::cout<<c<<"close"<<std::endl;
      close(c);
      return NULL;
    }
    
    • 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

    客户端
    cli.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
     int socketfd = socket(AF_INET,SOCK_STREAM,0);
     if(socketfd == -1)
     {
        std::cout<<"socket erro"<<std::endl;
         exit(1);
     
     }
    
     struct sockaddr_in saddr;
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family = AF_INET;
     saddr.sin_port =htons(6000);
     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
     int res = connect(socketfd,(struct sockaddr *)&saddr,sizeof(saddr));
     if(res == -1)
     {
       std::cout<<"connect erro"<<std::endl;
       exit(1);
     
     }
    
      while(1)
      {
    
       char buff[128]={0};
      std::cout<<"please input"<<std::endl;
       fgets(buff,128,stdin); 
       if(strncmp(buff,"end",3)==0)
       {
        break;
       }
       send(socketfd,buff,strlen(buff)-1,0);
       memset(buff,0,sizeof(buff));
       recv(socketfd,buff,127,0);
       std::cout<<"buff = "<<buff<<std::endl;
      }
       close(socketfd);
       exit(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

    运行结果
    客户端1
    在这里插入图片描述

    客户端2
    在这里插入图片描述

    服务端
    在这里插入图片描述

    多进程实现服务端并发

    服务端
    courseser.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    void fun(int sig)
    {
      wait(NULL);
    
    }
    int sock_init()
    {
      int socketfd = socket(AF_INET,SOCK_STREAM,0);
      if(socketfd == -1)
      {
        std::cout<<"socket erro"<<std::endl;
       exit(1);
      }
      struct sockaddr_in saddr,caddr;
      memset(&saddr,0,sizeof(saddr));
      saddr.sin_family = AF_INET;
      saddr.sin_port=htons(6000);
      saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
      int res = bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr));
      if(res == -1)
      {
        std::cout<<"bind erro "<<std::endl;
       exit(1);
      }
      res = listen(socketfd,5);
      if(res == -1)
      {
         std::cout<<"listen erro"<<std::endl;
        exit(1);
      }
     return socketfd;
    
    }
    int main()
    {
     signal(SIGCHLD,fun);
     int socketfd = sock_init();
     while(1)
     {
    
       struct sockaddr_in caddr;
       int len = sizeof(caddr);
       int c = accept(socketfd,(struct sockaddr *)&caddr,(socklen_t*)&len);
       if(c<=0)
       {
          std::cout<<"accept erro"<<std::endl;
         break;
       
       }
       std::cout<<"c="<<c<<"  ip="<<inet_ntoa(caddr.sin_addr)<<"  port="<<ntohs(caddr.sin_port)<<std::endl;;
       pid_t pid = fork();
      if(pid ==0)
      {
      
        while(1)
       {
         char buff[128]={0};
         int n = recv(c,buff,127,0);
         if(n<=0)
        {
          break;
       
        }
         std::cout<<"recive ="<<buff<<"  len="<<n<<std::endl;
         send(c,"ok",2,0);
      
       } 
      std::cout<<c<<"close"<<std::endl;
      close(c);
      }
     
     }
     exit(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

    客户端
    cli.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
     int socketfd = socket(AF_INET,SOCK_STREAM,0);
     if(socketfd == -1)
     {
        std::cout<<"socket erro"<<std::endl;
         exit(1);
     
     }
    
     struct sockaddr_in saddr;
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family = AF_INET;
     saddr.sin_port =htons(6000);
     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
     int res = connect(socketfd,(struct sockaddr *)&saddr,sizeof(saddr));
     if(res == -1)
     {
       std::cout<<"connect erro"<<std::endl;
       exit(1);
     
     }
    
      while(1)
      {
    
       char buff[128]={0};
      std::cout<<"please input"<<std::endl;
       fgets(buff,128,stdin); 
       if(strncmp(buff,"end",3)==0)
       {
        break;
       }
       send(socketfd,buff,strlen(buff)-1,0);
       memset(buff,0,sizeof(buff));
       recv(socketfd,buff,127,0);
       std::cout<<"buff = "<<buff<<std::endl;
      }
       close(socketfd);
       exit(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

    运行结果
    服务端
    在这里插入图片描述
    客户端1
    在这里插入图片描述
    客户端2
    在这里插入图片描述

    注意:

    关闭的时候要先关闭所以客户端,然后再关闭服务端,要不然运行服务端的时候会出现端口占用的情况,无法启动。

    UDP编程

    与TCP编程相比,UDP编程一定要每次都把数据读完,因为它是无连接的,如果没有读完的话,则会把剩余的数据丢掉。
    udp在局域网中很难丢数据

    编程步骤

    服务端

    1.socket()
    2.bind()
    3.recvfrom()
    4.sendto()
    5.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    客户端

    1.socket()
     //bind()可有可无
    2.sendto()
    3.recvfrom()
    4.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现

    ser.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
     int sockfd = socket(AF_INET,SOCK_DGRAM,0);
     if(sockfd == -1)
     {
       std::cout<<"sockfd erro"<<std::endl;
      exit(1);
     
     }
     struct sockaddr_in saddr,caddr;
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family = AF_INET;
     saddr.sin_port = htons(6000);
     saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
     int res =bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
     if(res == -1)
     {
       std::cout<<"bind erro"<<std::endl;
      exit(1);
     }
    
     while(1)
     {
       char buff[128]={0};
       int len = sizeof(caddr);
       int num = recvfrom(sockfd,buff,127,0,(struct sockaddr *)&caddr,(socklen_t*)&len);
       std::cout<<"datalength="<<num<<"  ip="<<inet_ntoa(caddr.sin_addr)<<"  buff="<<buff<<std::endl;
       sendto(sockfd,"ok",2,0,(struct sockaddr *)&caddr,sizeof(caddr));
     
     }
     close(sockfd);
     exit(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

    cli.cpp

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
     int sockfd = socket(AF_INET,SOCK_DGRAM,0);
     if(sockfd == -1)
     {
      std::cout<<"sockfd erro"<<std::endl;;
      exit(1);
     
     }
     struct sockaddr_in saddr;
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family = AF_INET;
     saddr.sin_port = htons(6000);
     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
     while(1)
     {
       std::cout<<"input"<<std::endl;
       char buff[128]={0};
       fgets(buff,128,stdin);
       if(strncmp(buff,"end",3) == 0)
       {
        break;
       }
       sendto(sockfd,buff,strlen(buff)-1,0,(struct sockaddr *)&saddr,sizeof(saddr));
       memset(buff,0,sizeof(buff));
      int len = sizeof(saddr);
      recvfrom(sockfd,buff,127,0,(struct sockaddr *)&saddr,(socklen_t*)&len);
      std::cout<<"recive ="<<buff<<std::endl;
     }
     close(sockfd);
     exit(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

    运行结果
    服务端
    在这里插入图片描述
    客户端1
    在这里插入图片描述
    客户端2
    在这里插入图片描述

    对于UDP编程来说,它是无连接的,发送数据的时候只需要知道对方的IP和端口就可以了

    TCP 可靠,开销大
    UDP 开销小,不可靠
    UDP和TCP可以同时复用一个端口

    一个进程中可以创建多个套接字,只要使用的端口不同就可以了

    netstat

    netstat -natp

    查看tcp连接的各种信息
    在这里插入图片描述

    netstat -naup

    在这里插入图片描述

  • 相关阅读:
    AI视频剪辑:批量智剪技巧大揭秘
    速卖通跨境智星靠谱吗?还有其他隐藏费用吗?
    鸿蒙tabbar ArkTS
    Android插件化技术的原理与实现
    C语言基础之负数是怎么存储的?(六十一)
    Error: 0x800701bc WSL 2 ?????????????????? https://aka.ms/wsl2kernel
    etcd选举源码分析和例子
    Flink(二)
    bug:Chrome插件SwitchyOmega安装时程序包无效:“CRX_HEADER_INVALID“问题
    在linux上把配置命令写出来
  • 原文地址:https://blog.csdn.net/aoeaoao/article/details/127978856