UDP编程效率高,不需要差错校验,在视频点播场景应用高
基于UDP协议客户端和服务端的编程模型,和TCP模型有点类似,有些发送接收函数不同,TCP是之间调用I/O函数read0或write()进行读写操作,而UDP是用sendto()和readfrom()等封装好了的函数进行数据接收发送
1.UDP服务器端:
socket 填写SOCK_DGRAM
bind(),套接字和地址和端口进行绑定
readfrom()获取接收客户端发送过来的报文,是对read()的封装,也是阻塞性的读写,如果接收不到客户端发送过来的数据报文,就阻塞
sendto() 向对方发送数据报文,
close()关闭套接字
2.客户端:socket()
1.socket()创建套接字
2.bind(),客户端一般不需要绑定
3.sendto()客户端向服务端发送数据报文
4.readfrom()读取服务端返回的数据报文
5.close()关闭套接字
3.数据传输
1.发送数据 成功调用返回发送字节数,出错返回-1
send()函数,
第一个参数,套接字描述符
第二个参数,发送的内容
第三个,第二个参数占的字节内存
第四个通常为0
sendto()函数,比send()多了两个参数,
第五个传入通用地址结构体,里面封装了接收方地址包括端口信息,目的地的信息
第六个参数,第五个参数的大小
sendmsg() 把发送的数据报文封装在叫做msghdr的结构体中
这个结构体第三个参数,可以把具体的数据报文放在里面
2.接收数据
recv()函数
第一个参数,套接字描述符
第二个参数,接收到的内存
第三个参数,第二个参数大小
第四个,一般设置为0
recvfrom()函数,
第五个参数传入通用地址结构体,用来存放发送方的一些地址信息,(接收方接收到发送方发过来的数据报文,接收方这方可用这个结构体存放发送方的地址和占用端口,
第六个参数就是第五个参数占用几个字节
recvmsg()函数
将接收的数据报文都存放在msghdr这个结构体当中
4.基于UDP端的服务器编程
客户端连接后,服务端返回一个系统时间
time_udp_server.c
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>//UDP服务器端,返回实时时间
//udp是无连接的,不会做应答和差错校验
int sockfd;void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");close(sockfd);exit(1);}
}//输出客户端的一些ip和端口信息
void out_addr(struct sockaddr_in *clientaddr)
{char ip[16];int port;memset(ip, 0, sizeof(ip));//网络字节序转换为点分十进制字节序,是字符串形式inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));port = ntohs(clientaddr->sin_port);printf("client: %s(%d)\n", ip, port);}//负责和客户端进行通讯
void do_service(int fd)
{struct sockaddr_in clientaddr;socklen_t len =sizeof(clientaddr);char buffer[1024]; //缓存memset(buffer, 0, sizeof(buffer));//接收客户端信息,传送的地址结构体用来接收//发送方的地址信息if(recvfrom(sockfd, buffer, sizeof(buffer),0, (struct sockaddr*)&clientaddr, &len) < 0){perror("recvfrom error");}else{out_addr(&clientaddr);//输出客户端的一些相关信息printf("client send into: %s\n",buffer);//向客户端发送数据报文//t为1970年经过的秒数long int t = time(0);//ctime输入秒数,返回现在时间//的字符串char *ptr = ctime(&t);size_t size = strlen(ptr) * sizeof(char);if(sendto(sockfd, ptr, size, 0, (struct sockaddr*)&clientaddr, len) < 0){perror("sendto error");}}}int main(int argc, char *argv[])
{struct timeval timeout;//超时结构体timeout.tv_sec = 10; // 10 秒timeout.tv_usec = 0; // 0 微秒if(argc < 2){printf("usage: %s port\n", argv[0]);exit(1);}if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error\n");exit(1);}/*步骤1:创建socket*/sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror("socket error\n");exit(1);}/*设置套接字相关选项,服务器停掉,启动服务器端会在指定端口监听,如果服务器断掉监听的端口不会马上使用,把服务器停掉后这些端口能够马上使用,我们最好设置套接字的选项*///udp服务器端设置超时设置int ret;int opt = 1; //打开//设置套接字的选项,第二个参数表明是通用的套接字层次选项//第三个参数表明设置端口停掉后,下次重新绑定这个端口//这个端口可以重新继续使用if((ret = setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt))) < 0){perror("setsocketopt erroe");exit(1);}//设置接收超时设置10sret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout));if (ret < 0) {perror("setsockopt SO_RCVTIMEO error");close(sockfd);exit(1);}/*步骤2:调用bind函数对socket和地址进行绑定*/struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET; //IPV4serveraddr.sin_port = htons(atoi(argv[1])); //端口serveraddr.sin_addr.s_addr = INADDR_ANY; //获得所有ip请求if(bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){perror("bind error");exit(1);}/*步骤3,和客户端进行双向数据通信*/while(1){do_service(sockfd);}
}
time_udp_client.c
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>int sockfd;void sig_handler(int signo)
{if(signo == SIGINT){printf("client close\n");close(sockfd);exit(0);}
}//udp端客户端程序
//步骤1创建,绑定,读写,发送和接收数据报文int main(int argc, char *argv[])
{if(argc < 3){printf("usage: %s ip port\n", argv[0]);exit(0);}if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal error");exit(1);}/*步骤1:创建socket*/sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror("socket error");exit(1);}/*步骤2:调用recvfrom和sendto函数和服务器进行双向通信*///定义服务端的结构体地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));//点分十进制转换为网络字节序inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr);//客户端向服务端发送数据报文//要向服务器发送的内容//在udp编程中,能否调用connect//在tcp中调用connect经过三次握手//在udp中调用connect后内核中记录了服务器端//的地址信息,包括了服务器端ip和端口//调用了connect后,可以用send(),而不是sendto()//if(connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0){perror("connect error");exit(1);}char buffer[1024] = "hello iotek";//if(sendto(sockfd, buffer, sizeof(buffer), 0// , (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0){//调用了connect内核中记录了服务器的地址和端口,发送时会自动发给内核记录地址if(send(sockfd, buffer, sizeof(buffer), 0) < 0){perror("sendto error");exit(1);}else{//如果成功发送,接收服务器发送报文memset(buffer, 0, sizeof(buffer));size_t size;//开始接收服务方发送返回信息//recv和recvfrom()都是阻塞性的//调用connect成功后,接收会接收connect好的地址的报文if((size = recv(sockfd, buffer,sizeof(buffer), 0)) < 0){perror("recv error");exit(1);}else{ //成功接收,显示到标准输出printf("%s\n", buffer);}}return 0;
}
运行结果:
服务端
客户端: