目录
socket编程(十四)
UDP特点
无连接
基于消息的数据传输服务
不可靠
一般情况下UDP更加高效
UDP客户/服务基本模型
recvfrom和sendto函数
函数原形:
#include<sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, const struct sockaddr *from, socklen_t *addrlen); ssize_t sendto(int sockfd, const void* buf, size_t nbytes, int flags, const struct sockaddr* to, sockelen_t addrlen);
参数说明:
前三个参数sockfd,buf和nbytes等同于read和write函数的三个参数:描述符、指向读入或写出缓冲区的指针和读写字节数。 recvfrom: from参数指向一个将由该函数在返回时填写的数据报发送者的协议地址的套接字地址结构, 而在该套接字结构中填写的字节数则在addrlen参数所指的整数中返回给调用者 sendto to参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构, 其大小是由addrlen参数指定。 一般情况下,flags总是设置为0。
UDP回射客户/服务器
服务端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_srv(int sock)
{
//不断接受客户端发送过来的一行数据
char recvbuf[1024]={0};
//客户端的地址信息
struct sockaddr_in peeraddr;
socklen_t peerlen;
//接受到的字节数
int n;
while(1)
{
peerlen=sizeof(peeraddr);
memset(recvbuf,0,sizeof(recvbuf));
n=recvfrom(sock,recvbuf,sizeof(recvbuf),0,(struct sockaddr*)&peeraddr,&peerlen);
if(n==1)
{
if(errno==EINTR)
continue;
ERR_EXIT("recvfrom");
}
//回射回去
else if(n>0)
{
//打印在服务端
fputs(recvbuf,stdout);
sendto(sock,recvbuf,n,0,(struct sockaddr*)&peeraddr,peerlen);
}
}
close(sock);
}
int main(void)
{
int sock;
//创建一个套接字,第二个参数是UDP套接口
if((sock=socket(PF_INET,SOCK_DGRAM,0))<0)
ERR_EXIT("socket");
//初始化一个地址绑定它
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
//本机任意的一个地址
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");
//没有三次握手,所以不需要监听
//回射服务器
echo_srv(sock);
return 0;
}
客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_cli(int sock)
{
//初始化一个地址绑定它
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
//指定对方的IP地址
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//用于说明异步的错误能返回给已连接的套接字
//UDP在调用connect的时候是不做三次握手的
connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr));
int ret;
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
//第一次调用的时候就对地址进行了绑定
//端口的绑定是在第一次调用sendto,connect仅仅只是确认了外出接口的地址
sendto(sock,sendbuf,strlen(sendbuf),0,(struct sockaddr*)&servaddr,sizeof(servaddr));
//如果是有connect的话,就可以连接到地址了
//sendto(sock,sendbuf,strlen(sendbuf),0,NULL,0);
/*
send也可以用来做发送
send(sock,sendbuf,strlen(sendbuf),0);
*/
//回射接受时,可以不指定对方的IP,因为上面已经绑定过了
ret=recvfrom(sock,recvbuf,sizeof(recvbuf),0,NULL,NULL);
if(ret==-1)
{
if(errno==EINTR);
continue;
ERR_EXIT("recvfrom");
}
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
//创建一个套接字,第二个参数是UDP套接口
if((sock=socket(PF_INET,SOCK_DGRAM,0))<0)
ERR_EXIT("socket");
echo_cli(sock);
return 0;
}
UDP注意点
1、UDP报文可能会丢失、重复
2、UDP报文可能会乱序
3、UDP缺乏流量控制
4、UDP协议数据报文截断
5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。
6、ICMP异步错误
先启动客户端,然后客户端从键盘接收一行输入,调用sendto发送出去,此时程序阻塞在recvfrom的地方, 但是服务端并没有启动,但是程序无法捕捉这一信息。 这是因为产生了一个异步错误,异步错误不会返回给套接口。 sendto的信息是无法到达对等方的,此时有一个ICMP的错误,但是这个错误是在recvfrom的时候才能产生,所以叫做异步ICMP错误。 TCP/IP规定异步错误不能返回给未连接的套接字,所以recvfrom也得不到通知,导致一直阻塞。
7、UDP connect
8、UDP外出接口的确定
9、太大的UDP包可能出现的问题
socket编程(十五)
udp聊天室实现