在使用UDP编写应用程序时与TCP编写应用程序有着本质的差异;其原因在于这两个传输层之间的差异;UDP是无连接不可靠的数据报协议;不同于TCP提供的面向连接的可靠字节流。然而有些场合更适合使用UDP;使用UDP编写的一些流行的应用程序有DNS;域名系统;、NFS;网络文件系统;、SNMP;简单网络管理协议;。
客户端无需与服务器端建立连接;但是需要指定目的地址;服务器地址;;并且客户端只管给服务器发送数据报。
服务器端不接受来自客户端的连接;而是只管等待来自某个客户端的数据。
如图 3.3所示给出了典型的UDP客户端与服务器端程序的函数使
bind函数把一个本地协议地址赋予一个套接字;协议地址的含义只取决于协议本身。对于网际协议;协议地址是32位的IPv4地址或128位IPv6地址与16位的TCP或UDP端口的组合。调用bind函数可以指定IP地址或端口;可以两者都指定;也可以都不指定。
#include <sys/socket.h>
int bind(int s, const struct sockaddr *name, socklen_t namelen);
函数bind原型分析;
此函数成功返回0;失败返回-1并设置错误号;
参数s是套接字;socket函数返回;;
参数name是一个指向特定协议域的sockaddr结构体类型的指针;
参数namelen 表示name结构的长度。
如图 所示给出了典型的UDP客户端与服务器端程序中recvfrom与sendto使用过程;类似于标准的read与write函数;不过需要额外三个参数;。
#include <sys/socket.h>
ssize_t recvfrom(int s, void *mem, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen);
函数recvfrom原型分析;
此函数成功时返回读取到数据的字节数;失败时返回-1并设置错误号;
参数s是套接字;socket函数返回;;
参数mem是指向读入缓冲区的指针;
参数len表示读取数据的字节长度;
参数flags用于指定消息类型;当不关心此参数时可以将其设置为0;如果需要关心此参数请将其值配置为以下值;
MSG_PEEK;数据预读但不删除数据;
MSG_WAITALL;等待所有数据到达后才返回;
MSG_OOB;带外数据;
MSG_DONTWAIT;不阻塞的接收数据;
MSG_MORE;有更多的数据需要发送。
参数from用于表示UDP数据报发送者的协议地址;例如IP地址及端口号;;
参数fromlen用于指定from地址大小的指针。
由于UDP是无连接的;因此recvfrom函数返回值为0也是有可能的。如果from参数是一个空指针;那么相应的长度参数fromlen也必须是一个空指针;表示我们并不关心数据发送者的协议地址。
#include <sys/socket.h>
ssize_t sendto(int s, const void *data, size_t size, int flags,
const struct sockaddr *to, socklen_t tolen);
函数sendto原型分析;
此函数成功时返回读取到数据的字节数;失败时返回-1并设置错误号;
参数s是套接字;socket函数返回;;
参数data是指向写入数据缓冲区的指针;
参数size表示写入数据的字节长度;
参数flags用于指定消息类型;当不关心此参数时可以将其设置为0;如果需要关心此参数请将其值配置为以下值;
MSG_PEEK;数据预读但不删除数据;
MSG_WAITALL;等待所有数据到达后才返回;
MSG_OOB;带外数据;
MSG_DONTWAIT ;不阻塞的接收数据;
MSG_MORE;有更多的数据需要发送。
参数to用于表示UDP数据报接收者的协议地址;例如IP地址及端口号;;
参数tolen用于指定to地址长度。
sendto函数写一个长度为0的数据报是可行的;这导致一个只包含IP头部;对于IPv4通常为20个字节;对于IPv6通常为40个字节;和一个8字节UDP头部但没有数据的IP数据报。
UDP回射程序模型;如下图所示。客户端与服务器遵循该流程完成回射数据的接收与回显。
客户端程序使用sendto函数将“SylixOS Hello;”发送给服务器;并使用recvfrom读回服务器的回射;最后将收到的回射信息“SylixOS Hello;”输出。
服务器程序使用recvfrom函数读入来自客户端的“SylixOS Hello;”数据;并通过sendto把收到的数据发送给客户端程序。
UDP回射服务器程序
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#define __UDP_ECHO_TYPE_CLIENT 1 /* 客户端模式 */
#define __UDP_ECHO_TYPE_SERVER 2 /* 服务器模式 */
/* 当前模式选择 */
#define __UDP_ECHO_TYPE (__UDP_ECHO_TYPE_SERVER)
/* 客户端 IP 地址 */
#define __UDP_ECHO_IP_CLIENT ;192.168.1.16;
/* 服务器 IP 地址 */
#define __UDP_ECHO_IP_SERVER ;192.168.1.17;
#define __UDP_ECHO_PORT_CLIENT 8000 /* 客户端端口号 */
#define __UDP_ECHO_PORT_SERVER 8001 /* 服务器端口号 */
#define __UDP_ECHO_BUFF_SIZE_CLIENT 257 /* 客户端接收缓冲区大小 */
#define __UDP_ECHO_BUFF_SIZE_SERVER 257 /* 服务器接收缓冲区大小 */
static int __UdpEchoServer (void)
{
int iRet = -1; /* 操作结果 */
int sockFd = -1; /* socket 描述符 */
/* 地址结构大小 */
socklen_t uiAddrLen = sizeof(struct sockaddr_in);
register ssize_t sstRecv = 0; /* 接收到的数据长度 */
/* 接收缓冲区 */
char cRecvBuff[__UDP_ECHO_BUFF_SIZE_SERVER] ={0};
struct sockaddr_in sockaddrinLocal; /* 本地地址 */
struct sockaddr_in sockaddrinRemote; /* 远端地址 */
fprintf(stdout, ;UDP echo server start.
;);
/* 创建 socket */
sockFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockFd < 0) { /* 操作失败 */
printf(;UDP echo server socket error.
;);
return (-1); /* 错误返回 */
}
/*
* 初始化本地地址结构
*/
/* 清空地址信息 */
memset(&sockaddrinLocal, 0, sizeof(sockaddrinLocal));
/* 地址结构大小 */
sockaddrinLocal.sin_len = sizeof(struct sockaddr_in);
sockaddrinLocal.sin_family = AF_INET; /* 地址族 */
/* 网络地址 */
sockaddrinLocal.sin_addr.s_addr = INADDR_ANY;
/* 绑定服务器端口 */
sockaddrinLocal.sin_port = htons(__UDP_ECHO_PORT_SERVER);
iRet = bind(sockFd,
(struct sockaddr *)&sockaddrinLocal,
sizeof(sockaddrinLocal)); /* 绑定本地地址与端口 */
if (iRet < 0) { /* 绑定操作失败 */
close(sockFd); /* 关闭已经创建的 socket */
fprintf(stderr, ;UDP echo server bind error.
;);
return (-1); /* 错误返回 */
}
for (;;) {
/* 清空接收缓冲区 */
memset(&cRecvBuff[0], 0, __UDP_ECHO_BUFF_SIZE_SERVER);
sstRecv = recvfrom(sockFd,
(void *)&cRecvBuff[0],
__UDP_ECHO_BUFF_SIZE_SERVER,
0,
(struct sockaddr *)&sockaddrinRemote,
&uiAddrLen); /* 从远端接收数据 */
if (sstRecv <= 0) { /* 接收数据失败 */
if ((errno != ETIMEDOUT ) &&
(errno != EWOULDBLOCK)) { /* 非超时与非阻塞 */
close(sockFd); /* 关闭已经创建的 socket */
fprintf(stderr, ;UDP echo server recvfrom error.
;);
return (-1);
}
continue;
}
sendto(sockFd,
(const void *)&cRecvBuff[0],
sstRecv,
0,
(const struct sockaddr *)&sockaddrinRemote,
uiAddrLen);
}
return (0);
}
UDP回射客户端程序
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#define __UDP_ECHO_TYPE_CLIENT 1 /* 客户端模式 */
#define __UDP_ECHO_TYPE_SERVER 2 /* 服务器模式 */
/* 当前模式选择 */
#define __UDP_ECHO_TYPE (__UDP_ECHO_TYPE_CLIENT)
/* 客户端 IP 地址 */
#define __UDP_ECHO_IP_CLIENT ;192.168.1.16;
/* 服务器 IP 地址 */
#define __UDP_ECHO_IP_SERVER ;192.168.1.17;
#define __UDP_ECHO_PORT_CLIENT 8000 /* 客户端端口号 */
#define __UDP_ECHO_PORT_SERVER 8001 /* 服务器端口号 */
#define __UDP_ECHO_BUFF_SIZE_CLIENT 257 /* 客户端接收缓冲区大小 */
#define __UDP_ECHO_BUFF_SIZE_SERVER 257 /* 服务器接收缓冲区大小 */
static int __UdpEchoClient (void)
{
int sockFd = -1; /* socket 描述符 */
/* 地址结构大小 */
socklen_t uiAddrLen = sizeof(struct sockaddr_in);
register ssize_t sstRecv = 0; /* 接收到的数据长度 */
register ssize_t sstSend = 0; /* 接收到的数据长度 */
/* 需要发送的字符串 */
const char *pcSendData = ;SylixOS Hello!
;;
/* 接收缓冲区 */
char cRecvBuff[__UDP_ECHO_BUFF_SIZE_CLIENT] ={0};
struct sockaddr_in sockaddrinRemote; /* 远端地址 */
fprintf(stdout, ;UDP echo client start.
;);
/* 创建 socket */
sockFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockFd < 0) {
fprintf(stderr, ;UDP echo client socket error.
;);
return (-1);
}
/*
* 初始化远端地址结构
*/
memset(&sockaddrinRemote, 0, sizeof(sockaddrinRemote));
/* 地址转换错误 */
if (!inet_aton(__UDP_ECHO_IP_SERVER, &sockaddrinRemote.sin_addr)) {
close(sockFd); /* 关闭已经创建的 socket */
fprintf(stderr, ;UDP echo client get addr error.
;);
return (-1); /* 错误返回 */
}
/* 地址结构大小 */
sockaddrinRemote.sin_len = sizeof(struct sockaddr_in);
sockaddrinRemote.sin_family = AF_INET; /* 地址族 */
/* 绑定服务器端口 */
sockaddrinRemote.sin_port = htons(__UDP_ECHO_PORT_SERVER);
for (;;) {
fprintf(stdout, ;Send Data: %s;, pcSendData);
sstRecv = strlen(pcSendData); /* 获取发送字符串长度 */
sstSend = sendto(sockFd,
(const void *)pcSendData,
sstRecv,
0,
(const struct sockaddr *)&sockaddrinRemote,
uiAddrLen); /* 发送数据到指定的服务器端 */
if (sstSend <= 0) { /* 发送数据失败 */
if ((errno != ETIMEDOUT ) &&
(errno != EWOULDBLOCK)) { /* 非超时与非阻塞 */
close(sockFd); /* 关闭已经创建的 socket */
fprintf(stderr, ;UDP echo client sendto error.
;);
return (-1); /* 错误返回 */
}
continue; /* 超时或非阻塞后重新运行 */
}
memset(&cRecvBuff[0], 0, __UDP_ECHO_BUFF_SIZE_CLIENT);
sstRecv = recvfrom(sockFd,
(void *)&cRecvBuff[0],
__UDP_ECHO_BUFF_SIZE_SERVER,
0,
(struct sockaddr *)&sockaddrinRemote,
&uiAddrLen); /* 从远端接收数据 */
if (sstRecv <= 0) { /* 接收数据失败 */
if ((errno != ETIMEDOUT ) &&
(errno != EWOULDBLOCK)) { /* 非超时与非阻塞 */
close(sockFd); /* 关闭已经创建的 socket */
fprintf(stderr, ;UDP echo client recvfrom error.
;);
return (-1); /* 错误返回 */
}
continue; /* 超时或非阻塞后重新运行 */
}
fprintf(stdout, ;Recv Data: ;);
cRecvBuff[sstRecv] = 0;
fprintf(stdout, %s
;, &cRecvBuff[0]);
sleep(5); /* 休眠一段时间 */
}
return (0);
}