文章出處

基于無連接的UDP程序設計

       同樣,在開發基于UDP的應用程序時,其主要流程如下:

 

       對于面向無連接的UDP應用程序在開發過程中服務端和客戶端的操作流程基本差不多。對比面向連接的TCP程序,服務端少了listen和accept函數。前面我們也說過listen函數最主要的作用就是將一個socket套接字描述符轉為被動監聽模式,然后調用accept主要是用于等待客戶端(用connect)來連接服務器。connect函數不僅可以用于流式套接字還可用于數據報式套接字。在TCP中,客戶端調用connect函數會向服務器端觸發一個TCP的3次握手過程,去建立一條TCP連接;而在UDP中,客戶端調用該函數主要的作用是告訴后面將要調用的recvfrom函數,僅僅只接受在connect函數中指明的服務器發來的數據,這樣當后面調用recvfrom時最后兩個參數就可以置為NULL了。也就說對UDP編程來說,客戶端調用connect是可選的:如果調用了connect函數,recvfrom就可以省掉最后兩個參數;如果不調用connect則recvfrom必須指明從哪兒收數據。

       對于UDP的編程其實主要在數據的收發處理上,而面向無連接的UDP編程中收發數據用到的最多的函數就是recvfrom()和sendto(),其原型如下:

ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

ssize_t sendto(int s, const void *buf, size_t len, int flags, struct sockaddr *to, socklen_t tolen);

       recvfrom函數主要用于從s所指定的套接字中接收數據,并將其存儲在buf所指向的緩沖區里。如果from參數不為NULL,那么其中便會攜帶消息發送端的地址信息,fromlen則指明了信息發送方地址信息結構體的大小。如果接收方對發送發的地址不感興趣,將from和fromlen置為NULL即可。返回值:小于0,有錯誤;大于0,實際收到的字節數;等于0,對端主動關閉。

       sendto函數,主要是buf所指向的數據發送到套接字描述符s中,len為要發送的數據長度,to中存儲了對端的地址信息,即數據該發往何處,tolen為to所占的字節數。返回值:小于0,有錯誤;大于0,實際發送的字節數。

      另外我們還知道,sendto是可以用于面向連接的流式套接字的,在TCP開發章節我們已經提過。這里在羅嗦一點,如果sendto用于面向流式的套接字編程中,to和tolen參數都會被忽略,如果發送數據時連接還未建立相應的提示錯誤為ENOTCONN。

      這里也沒有哪個規定說是不準在TCP程序中用sendto,但我們一般都不這么做,自己體會一下就明白了,除非你的項目開發中有特殊需求必須用。一句話:記住sendto和recvfrom既可以用于面向連接的流式套接字中收發數據,也可以用于面向無連接的數據報式套接字。sendto()和recvfrom()一般用在面向無連接的數據報式套接字的程序開發中。

  UDP服務端代碼, server.cpp 

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAX_MSG_SIZE 1024

int main(int argc,char** argv){
    int skfd,addrlen,ret;
    struct sockaddr_in addr,cltaddr;
    char buf[MAX_MSG_SIZE]={0};
    char sndbuf[MAX_MSG_SIZE]={0};

    //創建數據報式套接字skfd
    if(0>(skfd=socket(AF_INET,SOCK_DGRAM,0))){
         perror("Create Error");
         exit(1);
    }

    bzero(&addr,sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr=htonl(INADDR_ANY);
    addr.sin_port=htons(atoi(argv[1]));

    //將socket文件描述符skfd和本地端口和地址綁定起來
    if(0>(bind(skfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)))){
         perror("Bind Error");
         exit(1);
    }

    //開始收發數據
    while(1){
         ret=recvfrom(skfd,buf,MAX_MSG_SIZE,0,(struct sockaddr*)&cltaddr,&addrlen);
         if(ret < 0){
            printf("recv data from %s:%d error!",inet_ntoa(cltaddr.sin_addr),ntohs(cltaddr.sin_port));
         }else if(ret == 0){
            perror("client has been closing socket!");
         }else{
            printf("From %s:%d,%s",inet_ntoa(cltaddr.sin_addr),ntohs(cltaddr.sin_port),buf);
            memset(sndbuf,0,MAX_MSG_SIZE);
            switch(buf[0]){
                  case 'a':
                       strcpy(sndbuf,"After u ,lady...");
                  break;
                  case 'b':
                       strcpy(sndbuf,"Before u ,sir...");
                  break;
                  case 'c':
                       strcpy(sndbuf,"Can u?");
                       break;
                  default:
                       strcpy(sndbuf,"I dont't know what u want!");
            }
            sendto(skfd,sndbuf,strlen(sndbuf),0,(struct sockaddr*)&cltaddr,addrlen);
         }
         memset(buf,0,MAX_MSG_SIZE);
    }
    return 0;
}

 

  UDP客戶端代碼, client.cpp

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAX_MSG_SIZE 1024

int main(int argc,char** argv){
    int skfd,ret,len;
    struct sockaddr_in srvaddr;
    char buf[MAX_MSG_SIZE]={0};
    char sndbuf[MAX_MSG_SIZE]={0};
    struct in_addr addr;

    //創建數據報式套接字skfd
    if(0>(skfd=socket(AF_INET,SOCK_DGRAM,0))){
         perror("Create Error");
         exit(1);
    }

    if(0 == inet_aton(argv[1],&addr)){
         perror("server addr invalid!");
         exit(1);
    }

    bzero(&srvaddr,sizeof(struct sockaddr_in));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_addr=addr;
    srvaddr.sin_port=htons(atoi(argv[2]));

    //我們的客戶端只接收從服務器地址是srvaddr的主機發來的數據
    if(0>(connect(skfd,(struct sockaddr*)&srvaddr,sizeof(struct sockaddr_in)))){
          perror("Connect Error");
          exit(1);
    }
    
    //開始收發數據
    while(1){
        memset(sndbuf,0,MAX_MSG_SIZE);
        len=read(0,sndbuf,MAX_MSG_SIZE);
        ret=sendto(skfd,sndbuf,strlen(sndbuf),0,(struct sockaddr*)&srvaddr,sizeof(struct sockaddr));
        if(ret == len){
              memset(buf,0,MAX_MSG_SIZE);
              //我們已經知道服務器地址信息了,所以最后兩個參數為NULL
              ret=recvfrom(skfd,buf,MAX_MSG_SIZE,0,NULL,NULL);
       
              if(ret < 0){
                     perror("read error from server!");
              }else if(ret == 0){
                     perror("server has been closing socket!");
              }else{
                     buf[ret]='\0';
                     printf("From Server:%s\n",buf);
              }
        }
    }
    return 0;
}

  測試結果:

      我們客戶端接收用戶命令行輸入的指令,然后將其發給UDP服務器端;服務器端收到不同的指令后給客戶端予以不同的提示信息,整個流程如上所示。

      udpclt.c的示例代碼中我是調用了connect,勤奮好學的童鞋可以動手試驗哈不調用connect然后將程序調通吧。

  轉載自:http://blog.chinaunix.net/uid-23069658-id-3276167.html


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()