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\n",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\n",(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\n",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\n",(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\n",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!\n");

      

       //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\n",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\n",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\n",(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!\n"); //将所有的文件描述符剔除到集合之外!

             

              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\n",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\n",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\n",(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!\n");

              }

             

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

}

转载于:https://www.cnblogs.com/zjlbk/p/11378663.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值