基于無連接的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然后將程序調通吧。
文章列表