• 2019年8月19日星期一(网络编程 udp协议 非诸塞io 多路复用 网络超时接收 广播)


    2019819日星期一

    . UDP协议通信

    1. UDP协议的特点?

    UDP协议是面向于无连接的通信方式,用户只需要知道服务器的IP地址就可以发送数据给服务器,但是数据容易造成丢失。

    2. UDP协议服务器过程?

    1)创建一个UDP协议的套接字

    int sockfd = socket(AF_INET,SOCK_DGRAM,0);

    2)绑定IP地址,协议,端口号到套接字上

    struct sockaddr_in srvaddr;

    socklen_t len = sizeof(srvaddr);

    bzero(&srvaddr,len);

          

    srvaddr.sin_family = AF_INET;

    srvaddr.sin_port = htons(atoi(argv[1]));

    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取主机的IP地址 

          

    bind(sockfd,(struct sockaddr *)&srvaddr,len);

    3)不断等待对端的连接

    recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&Jackaddr,&len);

    4)断开套接字

    close(sockfd);

     

    3. UDP协议的客户端实现过程?

    1)创建一个UDP协议的套接字

    int sockfd = socket(AF_INET,SOCK_DGRAM,0);

    2)发送数据给服务器

    sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&Roseaddr,len);

    3)关闭套接字

    close(sockfd);

    . 非阻塞IO模型

    1. 阻塞IO与非阻塞IO区别?

    阻塞  -> 一直等待到有数据为止读取数据。  read(fd);

    非阻塞IO  -> 先询问有没有数据,如果有,则读取出来,如果没有则马上返回失败。  read(fd);

    2. 设置非阻塞IO模式?

    由于文件描述符天生是阻塞IO,所以阻塞就不用设置了,但是非阻塞模式需要用户自己添加。

    1)建立文件描述符             -> 文件描述符是阻塞。

    2)设置非阻塞属性给文件描述符  -> 文件描述符是非阻塞。

    3)再调用read()/recv()/read()/recvfrom()  -> 非阻塞读取。

    3. 如何设置非阻塞IO  -> fcntl()  -> man 2 fcntl

    使用格式:

           #include <unistd.h>

            #include <fcntl.h>

           int fcntl(int fd, int cmd, ... /* arg */ );

           fd:需要设置属性的文件描述符

           cmd:命令选项   -> 例如: 非阻塞IO模式

                  F_GETFL  -> 获取文件描述符的属性  -> arg参数可以省略!

                  F_SETFL  -> 设置属性到文件描述符中  ->

           arg:额外的参数

                  需要设置的属性,例如: O_NONBLOCK |

           返回值:

                  成功:  F_GETFL  -> 返回文件的属性    F_SETFL -> 0

                  失败:  -1

    例子:给一个文件描述符在原来的基础上添加非阻塞属性

    int main(int argc,char *argv[])

    {

           //1. 先建立文件描述符

           int sockfd = socket(AF_INET,SOCK_STREAM,0);

          

           //2. 设置非阻塞属性给文件描述符

           int state;

           state = fcntl(sockfd,F_GETFL);  //state就是这个文件描述符原来的属性

           state |= O_NONBLOCK;

           fcntl(sockfd,F_SETFL,state);

          

           //3. 接下使用sockfd时就是非阻塞的。

           return 0;

    }

    练习1:使用非阻塞IO读取TCP套接字中的数据。

    #include "head.h"

     

    //任务: 负责发送数据给客户端。

    void *routine(void *arg) //arg = &connfd

    {

           int connfd = *(int *)arg;

          

           char buf[50];

           while(1)

           {

                  bzero(buf,sizeof(buf));

                  fgets(buf,50,stdin);

                  send(connfd,buf,strlen(buf),0);

                  if(strncmp(buf,"quit",4) == 0)

                  {

                         exit(0);

                  }

           }

    }

     

    int main(int argc,char *argv[])  // ./server 50001

    {

           //1. 创建未连接套接字

           int sockfd;

           sockfd = socket(AF_INET,SOCK_STREAM,0);

           printf("sockfd = %d ",sockfd); //3

          

           //2. 绑定IP地址,端口号等到未连接套接字中

           struct sockaddr_in srvaddr;

           socklen_t len = sizeof(srvaddr);

           bzero(&srvaddr,len);

          

           srvaddr.sin_family = AF_INET; //协议

           srvaddr.sin_port = htons(atoi(argv[1])); //端口号

           srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务器的IP

          

           bind(sockfd,(struct sockaddr *)&srvaddr,len);

          

           //3. 设置监听套接字

           listen(sockfd,5);

          

           //4. 等待连接

           struct sockaddr_in cliaddr;

           int connfd;

           connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //阻塞!

           if(connfd > 0)

           {

                  printf("new connection:%s ",(char *)inet_ntoa(cliaddr.sin_addr));

           }

          

           //4.4 给已连接套接字设置一个非阻塞的属性

           int state;

           state = fcntl(connfd,F_GETFL);

           state |= O_NONBLOCK;

           fcntl(connfd,F_SETFL,state);

          

           //4.5 创建线程

           pthread_t tid;

           pthread_create(&tid,NULL,routine,(void *)&connfd);

          

           //5. 不断接收客户端的消息

           char buf[50];

           int ret;

           while(1)

           {

                  usleep(500000);

                  bzero(buf,sizeof(buf));

                  if(recv(connfd,buf,sizeof(buf),0)>0)

                  {

                         printf("from Jack:%s",buf);

                  }

                 

                  if(strncmp(buf,"quit",4) == 0)

                  {

                         break;

                  }

                 

           }

          

           //6. 挂断

           close(connfd);

           close(sockfd);

          

           return 0;

    }

    下午练习:

    写一个TCP协议服务器,最多可以接收20个客户端的连接,可以随时接收客户端的连接,客户端数据存放数据,只要连接到该服务器的客户端有数据到达,就会将该数据打印在服务器。

    . 多路复用模型

    1. 什么是多路复用?

    就是先将需要监听的文件描述符加入到一个集合中,然后在规定的时间内/无限等待去监听这个集合。如果在规定的时间内,集合中的文件描述符有数据到达,则其他没有数据到达的文件描述符就会自动被剔除到集合之外,我们用户只需要观察集合中有没有文件描述符在就可以。

    2. 如何实现多路复用?  -> select()  -> man 2 select

     

           #include <sys/select.h>

           #include <sys/time.h>

           #include <sys/types.h>

           #include <unistd.h>

           int select(int nfds, fd_set *readfds, fd_set *writefds,

                      fd_set *exceptfds, struct timeval *timeout);  

           nfds: 所有正在监测的套接字的最大值加1

           readfds: 读就绪的集合  -> 需要监听的集合。

           writefds: 写就绪的集合

           exceptfds: 异常就绪的集合

           timeout: 超时控制

    struct timeval {

             long    tv_sec;         -> 秒  

             long    tv_usec;        -> 微秒  

    };

           返回值:

                  成功:就绪文件描述符总数(当超时返回时为0)

                  失败:-1

    关于集合操作函数:

    void FD_CLR(int fd, fd_set *set);  -> 删除集合中的一个文件描述符

    int  FD_ISSET(int fd, fd_set *set);  -> 测试下文件描述符是否在集合中

       返回值:

           成功:1  -> 在集合中

           失败:0  -> 不在集合中

    void FD_SET(int fd, fd_set *set);  -> 添加一个文件描述符到集合中

    void FD_ZERO(fd_set *set);   -> 清空集合中所有的文件描述符

    server端:

    #include "head.h"

     

    int main(int argc,char *argv[])  // ./server 50001

    {

           //1. 创建未连接套接字

           int sockfd;

           sockfd = socket(AF_INET,SOCK_STREAM,0);

           printf("sockfd = %d ",sockfd); //3

          

           //2. 绑定IP地址,端口号等到未连接套接字中

           struct sockaddr_in srvaddr;

           socklen_t len = sizeof(srvaddr);

           bzero(&srvaddr,len);

          

           srvaddr.sin_family = AF_INET; //协议

           srvaddr.sin_port = htons(atoi(argv[1])); //端口号

           srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务器的IP

          

           bind(sockfd,(struct sockaddr *)&srvaddr,len);

          

           //3. 设置监听套接字

           listen(sockfd,5);

          

           //4. 等待连接

           struct sockaddr_in cliaddr;

           int connfd;

           connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //阻塞!

           if(connfd > 0)

           {

                  printf("new connection:%s ",(char *)inet_ntoa(cliaddr.sin_addr));

           }

          

           //5. 设置一个集合,并将文件描述符加入到集合中

           fd_set rset;

           int maxfd = connfd > STDIN_FILENO ? connfd : STDIN_FILENO;

           char buf[50];

          

           //6. 不断监听集合

           while(1)

           {

                  FD_ZERO(&rset);

                  FD_SET(connfd,&rset);

                  FD_SET(STDIN_FILENO,&rset);

                  select(maxfd+1,&rset,NULL,NULL,NULL); //只要有数据到达,就会返回!

                 

                  if(FD_ISSET(connfd,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         recv(connfd,buf,sizeof(buf),0);

                         printf("from client:%s",buf);

                  }

                 

                  if(FD_ISSET(STDIN_FILENO,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         fgets(buf,sizeof(buf),stdin);

                         send(connfd,buf,strlen(buf),0);

                  }

           }

          

           close(connfd);

           close(sockfd);

          

           return 0;

    }     

    client端:

    #include "head.h"

     

    int main(int argc,char *argv[]) //./client 192.168.90.2 50001

    {

           //1. 创建一个未连接套接字

           int sockfd;

           sockfd = socket(AF_INET,SOCK_STREAM,0);

           printf("sockfd = %d ",sockfd); //3

          

           //2. 直接发起连接

           struct sockaddr_in srvaddr;

           socklen_t len = sizeof(srvaddr);

           bzero(&srvaddr,len);

          

           srvaddr.sin_family = AF_INET;//协议

           srvaddr.sin_port = htons(atoi(argv[2]));

           inet_pton(AF_INET,argv[1],&srvaddr.sin_addr);

          

           int ret = connect(sockfd,(struct sockaddr *)&srvaddr,len);

           if(ret == -1)

                  printf("connect error! ");

          

           //5. 设置一个集合,并将文件描述符加入到集合中

           fd_set rset;

           int maxfd = sockfd > STDIN_FILENO ? sockfd : STDIN_FILENO;

           char buf[50];

          

           //6. 不断监听集合

           while(1)

           {

                  FD_ZERO(&rset);

                  FD_SET(sockfd,&rset);

                  FD_SET(STDIN_FILENO,&rset);

                  select(maxfd+1,&rset,NULL,NULL,NULL); //只要有数据到达,就会返回!

                 

                  if(FD_ISSET(sockfd,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         recv(sockfd,buf,sizeof(buf),0);

                         printf("from client:%s",buf);

                  }

                 

                  if(FD_ISSET(STDIN_FILENO,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         fgets(buf,sizeof(buf),stdin);

                         send(sockfd,buf,strlen(buf),0);

                  }

           }

          

           close(sockfd);

          

           return 0;

    }

    . 网络超时接收。

    1. 使用多路复用select()函数的最后一个参数。

    struct timeval {

             long    tv_sec;         -> 秒  

             long    tv_usec;        -> 微秒  

    };

    -> 每次select完,都需要重新设定一个新的时间。

    -> 用户可以设置一个时间,在规定的时间的内,没有数据到达,select函数就会返回。

    select(maxfd+1,&rset,NULL,NULL,NULL);  -> 如果有数据到达,则select()返回!

                                       -> 如果一直都没有数据到达,则select()会无限等待!

    struct timeval v;

    v.tv_sec = 5;

    v.tv_usec = 0;

    select(maxfd+1,&rset,NULL,NULL,&v);  -> 如果在5秒内,有数据到达,则select函数马上返回

                                     -> 如果在5秒内,没有数据到达,则select函数还是会返回0

      例题: 监听键盘与已连接套接字,如果在规定的5秒内,没有数据到达,则打印"timeout!"

      

    #include "head.h"

     

    void *routine(void *arg)

    {

           int i = 0;

           while(1)

           {

                  printf("%d ",i++);

                  sleep(1);

           }

    }

     

    int main(int argc,char *argv[])  // ./server 50001

    {

           //计算时间流逝

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

          

           //1. 创建未连接套接字

           int sockfd;

           sockfd = socket(AF_INET,SOCK_STREAM,0);

           printf("sockfd = %d ",sockfd); //3

          

           //2. 绑定IP地址,端口号等到未连接套接字中

           struct sockaddr_in srvaddr;

           socklen_t len = sizeof(srvaddr);

           bzero(&srvaddr,len);

          

           srvaddr.sin_family = AF_INET; //协议

           srvaddr.sin_port = htons(atoi(argv[1])); //端口号

           srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务器的IP

          

           bind(sockfd,(struct sockaddr *)&srvaddr,len);

          

           //3. 设置监听套接字

           listen(sockfd,5);

          

           //4. 等待连接

           struct sockaddr_in cliaddr;

           int connfd;

           connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //阻塞!

           if(connfd > 0)

           {

                  printf("new connection:%s ",(char *)inet_ntoa(cliaddr.sin_addr));

           }

          

           //5. 设置一个集合,并将文件描述符加入到集合中

           fd_set rset;

           struct timeval v;

           int maxfd = connfd > STDIN_FILENO ? connfd : STDIN_FILENO;

           char buf[50];

           int ret;

          

           //6. 不断监听集合

           while(1)

           {

                  FD_ZERO(&rset);

                  FD_SET(connfd,&rset);

                  FD_SET(STDIN_FILENO,&rset);

                 

                  bzero(&v,sizeof(v));

                  v.tv_sec = 5;

                  v.tv_usec = 0;

                 

                  ret = select(maxfd+1,&rset,NULL,NULL,&v); //只要有数据到达,就会返回!

                  if(ret == 0)

                         printf("timeout! "); //将所有的文件描述符剔除到集合之外!

                 

                  if(FD_ISSET(connfd,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         recv(connfd,buf,sizeof(buf),0);

                         printf("from client:%s",buf);

                  }

                 

                  if(FD_ISSET(STDIN_FILENO,&rset))

                  {

                         bzero(buf,sizeof(buf));

                         fgets(buf,sizeof(buf),stdin);

                         send(connfd,buf,strlen(buf),0);

                  }

           }

          

           close(connfd);

           close(sockfd);

          

           return 0;

    }

    2. 设置套接字的属性为超时接收。

    1)机制如何?

    如果不给套接字设置属性,那么读取这个套接字时就会无限等待!

    如果设置了超时属性给套接字,那么读取这个套接字数据时,就会有时间的限制。

    2)如何设置属性给套接字?  -> 具体用法:setsockopt.txt

    setsockopt设置属性函数

           #include <sys/types.h>          /* See NOTES */

           #include <sys/socket.h>

    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 

    sockfd:套接字

    level:优先级

           SOL_SOCKET:套接字

           IPPROTO_IP:IP优先级

           IPPRO_TCP:TCP优先级

    optname:选项名字  SO_RCVTIMEO

    optval:值,使能为1,不使能为0  -> 看最后一栏   struct timeval v

    optlen:值类型大小

    ===========================SOL_SOCKET=======================

    optname选项名字                                              optlen的大小

    SO_BROADCAST       允许发送广播数据            int

    SO_DEBUG        允许调试                int

    SO_DONTROUTE      不查找路由               int

    SO_ERROR        获得套接字错误             int

    SO_KEEPALIVE      保持连接                int

    SO_LINGER        延迟关闭连接              struct linger

    SO_OOBINLINE      带外数据放入正常数据流         int

    SO_RCVBUF        接收缓冲区大小             int

    SO_SNDBUF        发送缓冲区大小             int

    SO_RCVLOWAT       接收缓冲区下限             int

    SO_SNDLOWAT       发送缓冲区下限             int

    SO_RCVTIMEO       接收超时                struct timeval

    SO_SNDTIMEO       发送超时                   struct timeval

    SO_REUSEADDR       允许重用本地地址和端口          int

    SO_TYPE         获得套接字类型             int

    SO_BSDCOMPAT      与BSD系统兼容              int

    =========================IPPROTO_IP==========================

    IP_HDRINCL       在数据包中包含IP首部          int

    IP_OPTINOS       IP首部选项               int

    IP_TOS         服务类型

    IP_TTL         生存时间                int

    IP_ADD_MEMBERSHIP       加入组播                             struct ip_mreq

    =========================IPPRO_TCP===========================

    TCP_MAXSEG       TCP最大数据段的大小           int

    TCP_NODELAY       不使用Nagle算法             int

      阻塞情况:

           connfd = accept(sockfd);

           recv(connfd);  -> 一直阻塞,无限等待!

      设置套接字属性

           connfd = accept(sockfd);

           setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO....);  -> connfd本身就多了一个超时的属性

           recv(connfd);  -> 在规定的时间内,如果没有数据到达,则超时!

     

    #include "head.h"

     

    void *routine(void *arg)

    {

           int i = 0;

           while(1)

           {

                  printf("%d ",i++);

                  sleep(1);

           }

    }

     

    int main(int argc,char *argv[])  // ./server 50001

    {

           //计算时间流逝

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

          

           //1. 创建未连接套接字

           int sockfd;

           sockfd = socket(AF_INET,SOCK_STREAM,0);

           printf("sockfd = %d ",sockfd); //3

          

           //2. 绑定IP地址,端口号等到未连接套接字中

           struct sockaddr_in srvaddr;

           socklen_t len = sizeof(srvaddr);

           bzero(&srvaddr,len);

          

           srvaddr.sin_family = AF_INET; //协议

           srvaddr.sin_port = htons(atoi(argv[1])); //端口号

           srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务器的IP

          

           bind(sockfd,(struct sockaddr *)&srvaddr,len);

          

           //3. 设置监听套接字

           listen(sockfd,5);

          

           //4. 等待连接

           struct sockaddr_in cliaddr;

           int connfd;

           connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //阻塞!

           if(connfd > 0)

           {

                  printf("new connection:%s ",(char *)inet_ntoa(cliaddr.sin_addr));

           }

          

           //5. 设置超时属性给套接字

           struct timeval v;

           v.tv_sec = 5;

           v.tv_usec = 0;

          

           setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&v,sizeof(v));

          

           char buf[50];

           int ret;

          

           while(1)

           {

                  bzero(buf,sizeof(buf));

                  ret = recv(connfd,buf,sizeof(buf),0);

                  if(ret > 0) //说明在5秒内有数据到达

                  {

                         printf("from client:%s",buf);

                  }

                 

                  if(ret == -1) //说明已经超时

                  {

                         printf("timeout! ");

                  }

                 

                  if(strncmp(buf,"quit",4) == 0)

                  {

                         break;

                  }

           }

          

           //6. 挂断

           close(sockfd);

           close(connfd);

          

           return 0;

    }

    . 广播属性

    1. 什么是广播?

    广播属于套接字的一种属性,刚创建的套接字不允许广播,所以想使用广播属性,首先必须要先设置广播属性给套接字!

    单播  -> 点对点

    广播  -> 点对多

    2. 广播特点

    1)只有UDP协议才能实现广播。

    2)广播不是循环给每一个点发送数据,而是给广播地址发送数据。

    3. 广播地址是哪个?

    gec@ubuntu:/mnt/hgfs/GZ1934/10 网络编程/02/code$ ifconfig

    eth0      Link encap:Ethernet  HWaddr 00:0c:29:5d:9c:76 

              inet addr:192.168.90.4  -> Ubuntu主机地址

             Bcast:192.168.90.255    -> 广播地址

             Mask:255.255.255.0      -> 子网掩码

    4. 如何使得UDP协议客户端发送广播数据?

    1)创建UDP套接字

       sockfd = socket(UDP);

    2)给UDP套接字设置一个广播属性

       int on = 1;

       setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));

    例子:客户端!!!!

    #include "head.h"

    int main(int argc,char *argv[]) // ./Rose 192.168.90.4 50002

    {

           //1. 创建一个UDP协议的套接字

           int sockfd = socket(AF_INET,SOCK_DGRAM,0);

          

           //1.5 设置属性

           int on = 1;

            setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));

          

           //2. 直接写信

           char buf[50];

           struct sockaddr_in Roseaddr;

           socklen_t len = sizeof(Roseaddr);

          

           Roseaddr.sin_family = AF_INET;

           Roseaddr.sin_port = htons(atoi(argv[2]));

           inet_pton(AF_INET,argv[1],&Roseaddr.sin_addr);

          

           while(1)

           {

                  bzero(buf,sizeof(buf));

                  fgets(buf,50,stdin);

                  sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&Roseaddr,len);

                  if(strncmp(buf,"quit",4) == 0)

                  {

                         break;

                  }

           }

          

           //3. 关闭套接字

           close(sockfd);

    }

  • 相关阅读:
    正则表达式详解<一>
    multimap详讲
    map详讲<二>
    map详解<一>
    priority_queue详解
    容器适配器(一):queue
    用 input() 函数返回的数据是字符串类型
    学习python的基本了解
    学习oracle的SQL语句 练习
    oracle 练习题
  • 原文地址:https://www.cnblogs.com/zjlbk/p/11378663.html
Copyright © 2020-2023  润新知