简要介绍UDP原理,通过代码实例讲解。
本篇博客不强调server跟client 的概念,重在实现双方互通。
收的一方: socket()->bind()->recvfrom()->close()
发的一方:socket()->sendto()->close()
只有收数据的一方需要bind(),而发送的一方不需要bind()。由上图可以看出,bind()的一方只有收到消息(recvfrom)后才能给另一方发送消息。
同一台电脑如果要实现互发消息,则无法同时bind()同一个端口号,两方需要bind()不同的端口号才能实现自由互发,否则bind()的一方将会出现阻塞等待消息的现象。
下面代码中介绍UDP的C++实现。
1.设置两个端口实现互发数据
接受一方:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
//同一台电脑测试,需要两个端口
int port_in = 12321;
int port_out = 12322;
int sockfd;
// 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1==sockfd){
return false;
puts("Failed to create socket");
}
// 设置地址与端口
struct sockaddr_in addr;
socklen_t addr_len=sizeof(addr);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // Use IPV4
addr.sin_port = htons(port_out); //
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// Time out
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; // 200 ms
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));
// Bind 端口,用来接受之前设定的地址与端口发来的信息,作为接受一方必须bind端口,并且端口号与发送方一致
if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){
printf("Failed to bind socket on port %d\n", port_out);
close(sockfd);
return false;
}
char buffer[128];
memset(buffer, 0, 128);
int counter = 0;
while(1){
struct sockaddr_in src;
socklen_t src_len = sizeof(src);
memset(&src, 0, sizeof(src));
int sz = recvfrom(sockfd, buffer, 128, 0, (sockaddr*)&src, &src_len);
if (sz > 0){
buffer[sz] = 0;
printf("Get Message %d: %s\n", counter++, buffer);
}
else{
puts("timeout");
}
}
close(sockfd);
return 0;
}
发送一方:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
int port_in = 12321;
int port_out = 12322;
int sockfd;
// 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1==sockfd){
return false;
puts("Failed to create socket");
}
// 设置地址与端口
struct sockaddr_in addr;
socklen_t addr_len=sizeof(addr);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // Use IPV4
addr.sin_port = htons(port_in); //
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// Time out
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; // 200 ms
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));
// 绑定获取数据的端口,作为发送方,不绑定也行
if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){
printf("Failed to bind socket on port %d\n", port_in);
close(sockfd);
return false;
}
int counter = 0;
while(1){
addr.sin_family = AF_INET;
addr.sin_port = htons(port_out);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
sendto(sockfd, "hello world", 11, 0, (sockaddr*)&addr, addr_len);
printf("Sended %d\n", ++counter);
sleep(1);
}
close(sockfd);
return 0;
}
2.设置一个端口号实现互发数据
server方必须收到一次client方的数据后,才能实现随意互发数据。
使用方法为:运行server->运行client->键盘在client输入->随意互发数据。
server:
//只有在server接收到消息后才能实现互发数据
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 8888//唯一端口号
int main(){
int ser_sockfd;
int len;
fd_set rfds;
socklen_t addrlen;
char seraddr[100];
struct sockaddr_in ser_addr;
int retval, maxfd;
//建立socket
ser_sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(ser_sockfd<0){
printf("I cannot socket success\n");
return 1;
}
//设置地址与端口
addrlen=sizeof(struct sockaddr_in);
bzero(&ser_addr,addrlen);
ser_addr.sin_family=AF_INET;
ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
ser_addr.sin_port= htons (SERVER_PORT);
//server绑定,才能接受client的数据
if(bind(ser_sockfd,(struct sockaddr *)&ser_addr,addrlen)<0){
printf("connect");
return 1;
}
while(1){
bzero(seraddr,sizeof(seraddr));
len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen);
/*显示client端的网络地址*/
printf("receive from %s\n",inet_ntoa(ser_addr.sin_addr));
/*显示客户端发来的字串*/
printf("recevce:%s",seraddr);
/*输入字串返回给client端*/
while(1)
{
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把标准输入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(ser_sockfd, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < ser_sockfd)
maxfd = ser_sockfd;
retval = select(maxfd+1, &rfds, NULL, NULL, NULL);
if(FD_ISSET(ser_sockfd,&rfds))//client发消息来会出发进入
{
len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen); printf("recevce:%s",seraddr);
}
if(FD_ISSET(0, &rfds))//键盘输入会触发进入
{
len=read(STDIN_FILENO,seraddr,sizeof(seraddr));
sendto(ser_sockfd,seraddr,len,0,(struct sockaddr*)&ser_addr,addrlen);
}
}
}
return 0;
}
client:
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 8888 //唯一端口
int main(int argc,char **argv){
int cli_sockfd;
int len;
fd_set rfds;
socklen_t addrlen;
struct sockaddr_in cli_addr;
char buffer[256];
char buffer1[256];
int retval, maxfd;
//创建socket
cli_sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(cli_sockfd<0){
printf("I cannot socket success\n");
return 1;
}
//配置地址与端口
addrlen=sizeof(struct sockaddr_in);
bzero(&cli_addr,addrlen);
cli_addr.sin_family=AF_INET;
cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);//任何主机地址
cli_addr.sin_port=htons(SERVER_PORT);
//这里作为发送方,不需要绑定bind()
while(1)
{
bzero(buffer,sizeof(buffer));
/* 从标准输入设备取得字符串*/ len=read(STDIN_FILENO,buffer,sizeof(buffer));
/* 将字符串传送给server端*/
sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
/* 接收server端返回的字符串*/
while(1)
{
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把标准输入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(cli_sockfd, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < cli_sockfd)
maxfd = cli_sockfd;
retval = select(maxfd+1, &rfds, NULL, NULL, NULL);
if(FD_ISSET(cli_sockfd,&rfds))//server发来数据将会触发进入循环
{
len=recvfrom(cli_sockfd,buffer1,sizeof(buffer1),0,(struct sockaddr*)&cli_addr,&addrlen);
printf("receive: %s",buffer1);
}
if(FD_ISSET(0, &rfds))//键盘输入会触发进入
{
len=read(STDIN_FILENO,buffer,sizeof(buffer));
sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
}
}
}
close(cli_sockfd);
return 0;
}
补充:inet_addr(“127.0.0.1”)将一个点分十进制的IP转换成一个长整数型数(u_long类型)。
WALDM