前面我们写了关于TCP的客户/服务器模式,现在我们写关于UDP的客户/服务器模式。

       基于TCP编写的应用程序和基于TCP编写的应用程序之间存在一些本质的差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,不同于TCP提供的面向连接的可靠字节流。

       我们先来说一下简单的模型:在基于UDP的应用程序中,客户不与服务器建立连接,而只是使用sendto函数给服务器发送数据报,其中必须指定目的地(即服务器)的地址作为参数。当然,在服务器端不接受来自客户的连接,只是使用recvfrom函数,等待来自某个客户的数据到达。

接下来我们来说说将要用到的两个重要的函数:

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

        其中前3个参数分别就是我们所创建的套接字描述符,指向读入或写出的缓冲区指针和读写字节数;

        第4个参数我们当下暂时赋值为0;

        第5个参数src_addr指向一个将由该函数在返回时填写数据报发送者的协议地址的套接字地址结构,而在该套接字地址结构中填写的字节数则放在addrlrn参数所指定的整数中返回给调用者。

      

         ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);  

         前三个参数和recvfrom参数一样;

         第4个参数指向一个含有数据包接收者的协议地址(IP地址及端口号)的套接字地址结构,其大小同样由第6个参数决定。

注意:sendto最后一个参数是一个整数值,而recvfrom的最后一个参数是一个指向整数值的指针(即输入输出形参数)。recvfrom的最后两个参数类似于accept的最后两个参数,返回时其中套接字地址结构的内容告诉我们是谁发送了数据报(UDP)或是谁发起了连接(TCP)。sendto的最后两个参数类似于connect的最后两个参数,调用时其中套接字地址结构被我们填入数据报将发往(UDP)或与之建立连接(TCP)的协议地址。


下面我们来具体看看代码:

服务器端:


  

 #include <stdio.h>
   #include <string.h>
   #include <errno.h>
   #include<stdlib.h>
   #include <netinet/in.h>
   #include <arpa/inet.h>
   #include <sys/types.h>
   #include <sys/socket.h>
   
  void usage(const char* arg)
  {
      printf("%s [ip][port\n",arg);
  }
  
  int main(int argc,char *argv[])
  {
      if(argc != 3)
      {
          usage(argv[0]);
          exit(0);
      }
  
      int port=atoi(argv[2]);
      char *ip=argv[1];
  
      int sock=socket(AF_INET,SOCK_DGRAM,0);//面向数据报
      if(sock<0)
      {
          perror("socket");
          exit(1);
      }
  
      struct sockaddr_in local;                                                                                                                                       
      local.sin_family=AF_INET;
      local.sin_port=htons(port);
      local.sin_addr.s_addr=inet_addr(ip);
      local.sin_port=htons(port);
      local.sin_addr.s_addr=inet_addr(ip);
  
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {
          perror("bind");                                                                                                                                             
          exit(2);
      }
  
      struct sockaddr_in client;
      socklen_t len=sizeof(client);
      char buf[1024];
      while(1)
      {
          memset(buf,'\0',sizeof(buf));
          ssize_t _s =recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
          if(_s>0)
          {
              buf[_s]='\0';
              printf("[%s %d]#:%s",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
          }
         else if(_s == 0)
          {
              printf("client close...\n");
              break;
          }
          else{//recv fail
  
          }
      }
      return 0;
  }

再来看看客户端:

 

void usage(const char* arg)
  {
      printf("%s [remote_ip][remote_port\n",arg);
  }
  
  int main(int argc,char *argv[])
  {
      if(argc != 3)
      {
          usage(argv[0]);
          exit(0);
      }
  
      int port=atoi(argv[2]);
      char *ip=argv[1];                                                                                                                                               
  
      int sock=socket(AF_INET,SOCK_DGRAM,0);//面向数据报
      if(sock<0)
      {
          perror("socket");
          exit(1);
      }
  
      struct sockaddr_in remote;
      remote.sin_family=AF_INET;
      remote.sin_port=htons(port);
      remote.sin_addr.s_addr=inet_addr(ip);
  
      char buf[1024];
      while(1)
      {
          printf("please enter: ");
          fflush(stdout);
          ssize_t _s=read(0,buf,sizeof(buf)-1);
          buf[_s]='\0';
          _s=sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&remote,sizeof(remote));
      }
      return 0;
  }

运行结果如下:

wKiom1dFORyiYEcKAABmVGWhLfY714.png


     上图测试是在一台主机的情况下,我们可以看到客户端发送的消息被服务端收到了,实现了基于UDP的简单通信。