UDP程序设计
UDP套接口是无连接的、不可靠的数据报协议;既然他不可靠为什么还要用呢?
其一:当应用程序使用广播或多播时只能使用UDP协议;
其二:由于他是无连接的,所以速度快。
因为UDP套接口是无连接的,如果一方的数据报丢失,那另一方将无限等待,解决办法是设置一个超时。
建立UDP套接口时socket函数的第二个参数应该是SOCK_DGRAM,说明是建立一个UDP套接口;由于UDP是无连接的,所以服务器端并不需要listen或accept函数。使用UDP套接字编程可以实现基于TCP/IP协议的面向无连接的通信,它分为服务器端和客户端两部分,其主要实现过程如下图所示:
UDP客户/服务器的套接字函数
1、socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。
第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);
/* * Address families. */ #define AF_UNSPEC 0 /* unspecified */ #define AF_UNIX 1 /* local to host (pipes, portals) */ #define AF_INET 2 /* internetwork: UDP, TCP, etc. */ #define AF_IMPLINK 3 /* arpanet imp addresses */ #define AF_PUP 4 /* pup protocols: e.g. BSP */ #define AF_CHAOS 5 /* mit CHAOS protocols */ #define AF_IPX 6 /* IPX and SPX */ #define AF_NS 6 /* XEROX NS protocols */ #define AF_ISO 7 /* ISO protocols */ #define AF_OSI AF_ISO /* OSI is ISO */ #define AF_ECMA 8 /* european computer manufacturers */ #define AF_DATAKIT 9 /* datakit protocols */ #define AF_CCITT 10 /* CCITT protocols, X.25 etc */ #define AF_SNA 11 /* IBM SNA */ #define AF_DECnet 12 /* DECnet */ #define AF_DLI 13 /* Direct data link interface */ #define AF_LAT 14 /* LAT */ #define AF_HYLINK 15 /* NSC Hyperchannel */ #define AF_APPLETALK 16 /* AppleTalk */ #define AF_NETBIOS 17 /* NetBios-style addresses */ #define AF_VOICEVIEW 18 /* VoiceView */ #define AF_FIREFOX 19 /* FireFox */ #define AF_UNKNOWN1 20 /* Somebody is using this! */ #define AF_BAN 21 /* Banyan */ #define AF_MAX 22
第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);
/* * Types */ #define SOCK_STREAM 1 /* stream socket */ #define SOCK_DGRAM 2 /* datagram socket */ #define SOCK_RAW 3 /* raw-protocol interface */ #define SOCK_RDM 4 /* reliably-delivered message */ #define SOCK_SEQPACKET 5 /* sequenced packet stream */
如果套接口类型不是原始套接口,那么第三个参数就为0。
2、bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen); //返回:0---成功 -1---失败
第一个参数是socket函数返回的套接口描述字;第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。
3、recvfrom函数:UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明目的地址。
#include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen); //返回接收到数据的长度---成功 -1---失败
前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数。
4、sendto函数:UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。
#include <sys/types.h> #include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen); //返回发送数据的长度---成功 -1---失败
前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。参数to指明数据将发往的协议地址,他的大小由addrlen参数来指定。
参考程序(udpserver.c):
Linux下UDP服务器套接字程序,服务器接收客户端发送的信息并显示,同时显示客户的IP地址、端口号,并向客户端发送信息。如果服务器接收的客户信息为“bye”,则退出循环,并关闭套接字。
#include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<stdlib.h> #include<netinet/in.h> #include<arpa/inet.h> #define PORT 1234 #define MAXDATASIZE 100 int main() { int sockfd; struct sockaddr_in server; struct sockaddr_in client; socklen_t addrlen; int num; char buf[MAXDATASIZE]; if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("Creatingsocket failed."); exit(1); } bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr= htonl (INADDR_ANY); if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) { perror("Bind()error."); exit(1); } addrlen=sizeof(client); while(1) { num =recvfrom(sockfd,buf,MAXDATASIZE,0,(struct sockaddr*)&client,&addrlen); if (num < 0) { perror("recvfrom() error\n"); exit(1); } buf[num] = '\0'; printf("You got a message (%s%) from client.\nIt's ip is%s, port is %d.\n",buf,inet_ntoa(client.sin_addr),htons(client.sin_port)); sendto(sockfd,"Welcometo my server.\n",22,0,(struct sockaddr *)&client,addrlen); if(!strcmp(buf,"bye")) break; } close(sockfd); }
//执行命令./ udpserver,观察结果
参考程序(udpclient.c):
1、客户根据用户提供的IP地址将用户从终端输入的信息发送给服务器,然后等待服务器的回应。
2、服务器接收客户端发送的信息并显示,同时显示客户的IP地址、端口号,并向客户端发送信息。如果服务器接收的客户信息为“bye”,则退出循环,并关闭套接字。
3、客户接收、显示服务器发回的信息,并关闭套接字。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define PORT 1234 #define MAXDATASIZE 100 int main(int argc, char *argv[]) { int sockfd, num; char buf[MAXDATASIZE]; struct hostent *he; struct sockaddr_in server,peer; if (argc !=3) { printf("Usage: %s <IP Address><message>\n",argv[0]); exit(1); } if ((he=gethostbyname(argv[1]))==NULL) { printf("gethostbyname()error\n"); exit(1); } if ((sockfd=socket(AF_INET, SOCK_DGRAM,0))==-1) { printf("socket() error\n"); exit(1); } bzero(&server,sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr= *((struct in_addr *)he->h_addr); sendto(sockfd, argv[2],strlen(argv[2]),0,(struct sockaddr *)&server,sizeof(server)); socklen_t addrlen; addrlen=sizeof(server); while (1) { if((num=recvfrom(sockfd,buf,MAXDATASIZE,0,(struct sockaddr *)&peer,&addrlen))== -1) { printf("recvfrom() error\n"); exit(1); } if (addrlen != sizeof(server) ||memcmp((const void *)&server, (const void *)&peer,addrlen) != 0) { printf("Receive message from otherserver.\n"); continue; } buf[num]='\0'; printf("Server Message:%s\n",buf); break; } close(sockfd); }
//执行命令./ udpclient 127.0.0.1 hello
实验结果:
服务器端:
客户端: