udp是无连接的,对于服务器,它只需要创建套接字,并绑定到地址:端口上,然后等待接收消息到来,对于客户端,只需要创建套接字然后向服务器发送消息。
udp服务器一般是迭代的。
下面是一个使用udp的简单echo程序:
/* *udp_server.c */
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#include <string.h>
#define PORT 8888
#define BUFSIZE 1024
int
main (int argc, char **argv)
{
struct sockaddr_in servaddr, cliaddr;
int servfd;
servfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
/*绑定地址结构到套接字描述符*/
if (bind(servfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
{
printf("bind error\n");
return -1;
}
udpserv_echo(servfd, (struct sockaddr *) &cliaddr); //回显处理程序
return 0;
}
/* *udp_client.c */
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h> //包含socket, bind
#include <unistd.h>
#include <linux/in.h> //包含struct sockaddr_in
#include <string.h>
#define PORT 8888
#define BUFSIZE 1024
int
main (int argc, char **argv)
{
struct sockaddr_in servaddr;
int clifd;
if (argc != 2)
{
printf("usage: client <IPaddress>");
return -1;
}
/*设置服务器地址*/
bzero(&servaddr, sizeof(servaddr)); //清零
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
{
printf("inet_pton: error\n");
return -1;
}
if ((clifd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("socket error\n");
return -1;
}
udpclie_echo(clifd, (struct sockaddr*) &servaddr);
close(clifd);
return 0;
}
/* *udp_process.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>
#define BUFSIZE 256
void
udpserv_echo(int fd, struct sockaddr* cliaddr)
{
ssize_t size = 0;
char buffer[BUFSIZE];
int clilen;
for (;;)
{
clilen = sizeof(*cliaddr);
//接收数据放在buffer中,并获取客户端地址.
//注意,recvfrom返回0是可接受的.
if ( (size = recvfrom(fd, buffer, BUFSIZE, 0, cliaddr, &clilen)) < 0 )
{
printf("recvfrom: error\n");
return;
}
if ( sendto(fd, buffer, size, 0, cliaddr, clilen) < 0 ) //发回给客户端
{
printf("sendto: error\n");
return;
}
}
}
void
udpclie_echo(int fd, struct sockaddr* to)
{
struct sockaddr_in from;
char buffer[BUFSIZE];
int len = sizeof(*to);
ssize_t size = 0;
for (;;)
{
size = read(0, buffer, BUFSIZE);
if (size > 0)
{
buffer[size++] = '\0';
if ( sendto(fd, buffer, size, 0, to, len) < 0)
{
printf("sendto: error\n");
return;
}
if ( recvfrom(fd, buffer, BUFSIZE, 0, (struct sockaddr*) &from, &len) < 0 )
{
printf("recvfrom: error\n");
return;
}
printf("recved:%s\n", buffer);
}
}
}
UDP是无连接的,不能保证发送数据的正确到达,因此在需要确认的情况下,我们得自己处理超时重传。
针对这一点,我们可以采用发送端在数据段中加入数据报序号的方法。
UDP协议中使用connect函数仅仅表示确定了另一方的地址,它并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态。
使用connect函数绑定套接字后,发送操作不能再使用sendto(recvfrom)函数,要使用write(read)函数直接操作套接字描述符,不再指定目标地址和端口号。
在使用多次connect函数的时候,会用新绑定的地址和套接字代替旧的,原有的绑定状态失效。我们可以使用这种特性来断开原来的连接。
当缓冲区满的时候,后面到来的数据会覆盖之前的数据而造成数据的丢失。
解决UDP接收缓冲区溢出的现象需要根据实际情况确定,一般可以用增大接收数据缓冲区和接收方接收单独处理的方法来解决局部的UDP数据接收缓冲区溢出问题。
当使用UDP协议接收数据的时候,如果应用程序传入的接收缓冲区的大小小于到来的数据大小时,接收缓冲区会保存最大可能接收到的数据,其他的数据将会丢失,并且有MSG_TRUNC的标志。
因此服务器和客户端程序需要进行配合,接收的缓冲区要比发送的数据大一些。
知道客户临时端口号的任何进程都可以往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。为解决这个问题,我们可以在recvfrom中返回数据报发送者的IP地址和端口号,只保留来自数据报所发往的服务器的应答,忽略其他数据报。
但是这样存在一些缺陷,当服务器主机有两个网卡和IP地址时,比如ip为172.24.37.94和135.197.17.100,当我们往135.197.17.100发送数据之后,服务器主机内核中的路由功能可能选择172.24.37.94作为外出接口(因为我们没有在套接字上绑定一个实际的IP地址)。
解决方法:
1.客户通过recvfrom得到服务器ip地址后,通过DNS查询主机名字来验证域名而不是IP地址。
2.为服务器的每个IP创建一个套接字,用bind绑定每个IP,然后在这些套接字上使用select。