1、UDP网络编程主要流程
UDP协议的程序设计框架,客户端和服务器之间的差别在于服务器必须使用bind()函数来绑定侦听的本地UDP端口,而客户端则可以不进行绑定,直接发送到服务器地址的某个端口地址。框图如图1.3所示
UDP协议的服务器端流程
服务器流程主要分为下述6个部分,即建立套接字、设置套接字地址参数、进行端口绑定、接收数据、发送数据、关闭套接字等。
(1)建立套接字文件描述符,使用函数socket(),生成套接字文件描述符。
(2)设置服务器地址和侦听端口,初始化要绑定的网络地址结构。
(3)绑定侦听端口,使用bind()函数,将套接字文件描述符和一个地址类型变量进行绑定。
(4)接收客户端的数据,使用recvfrom()函数接收客户端的网络数据。
(5)向客户端发送数据,使用sendto()函数向服务器主机发送数据。
(6)关闭套接字,使用close()函数释放资源。UDP协议的客户端流程
UDP协议的客户端流程
UDP协议的客户端流程分为套接字建立、设置目的地址和端口、向服务器发送数据、从服务器接收数据、关闭套接字等5个部分。流程如下:
(1)建立套接字文件描述符,socket();
(2)设置服务器地址和端口,struct sockaddr;
(3)向服务器发送数据,sendto();
(4)接收服务器的数据,recvfrom();
(5)关闭套接字,close()。
图1.3 UDP编程流程
2、相关函数
(1) int socket(AF_INET, SOCK_DGRAM, 0);
创建udp socket,返回套接字描述符,UDP协议建立套接字的方式同TCP方式一样,使用socket()函数,只不过协议的类型使用SOCK_DGRAM,而不是SOCK_STREAM。
(2) int sendto(int sockfd, const void *data, int data_len, unsigned int flags, struct sockaddr *remaddr,sock_lenremaddr_len)
功能:基于UDP发送数据报,返回实际发送的数据长度,出错时返回-1
参数说明:
sockfd:套接字描述符
data:指向要发送数据的指针
data_len:数据长度
flags:通常为0
remaddr:远端地址:IP地址和端口号
remaddr_len:地址长度
(3) int recvfrom(int sockfd, void *buf,int buf_len,unsigned int flags,struct sockaddr *from,sock_len *fromlen);
功能:从UDP接收数据,返回实际接收的字节数,失败时返回-1
参数说明:
Sockfd:套接字描述符
buf:指向内存块的指针
buf_len:内存块大小,以字节为单位
flags:一般为0
from:远端的地址,IP地址和端口号
fromlen:远端地址长度
(4) ssize_t recv(int s, void*buf,size_t len, int flags);
连接的UDP可调用recv从服务器读取数据。
ssize_tsend(int s, const void*buf, size_t len, int flags);
连接的UDP可调用send向服务器发送数据。
3、UDPSocket客户服务器通信实例
下面依照通信流程,我们来实现一个UDP回射客户/服务器。图1.4 UDP回射客户/服务器流程
服务器代码:
-
#include<stdio.h>
-
#include<stdlib.h>
-
#include<unistd.h>
-
#include<errno.h>
-
#include<sys/types.h>
-
#include<sys/socket.h>
-
#include<netinet/in.h>
-
#include<string.h>
-
-
#define MYPORT 8887
-
-
-
#define ERR_EXIT(m) \
-
do { \
-
perror(m); \
-
exit(EXIT_FAILURE); \
-
} while (0)
-
-
void echo_ser(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 <=
0)
-
{
-
-
if (errno == EINTR)
-
continue;
-
-
ERR_EXIT(
“recvfrom error”);
-
}
-
else
if(n >
0)
-
{
-
printf(
“接收到的数据:%s\n”,recvbuf);
-
sendto(sock, recvbuf, n,
0,
-
(struct sockaddr *)&peeraddr, peerlen);
-
printf(
“回送的数据:%s\n”,recvbuf);
-
}
-
}
-
close(sock);
-
}
-
-
int main(void)
-
{
-
int sock;
-
if ((sock = socket(PF_INET, SOCK_DGRAM,
0)) <
0)
-
ERR_EXIT(
“socket error”);
-
-
struct sockaddr_in servaddr;
-
memset(&servaddr,
0,
sizeof(servaddr));
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_port = htons(MYPORT);
-
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
-
printf(
“监听%d端口\n”,MYPORT);
-
if (bind(sock, (struct sockaddr *)&servaddr,
sizeof(servaddr)) <
0)
-
ERR_EXIT(
“bind error”);
-
-
echo_ser(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 MYPORT 8887
-
char* SERVERIP =
"127.0.0.1";
-
-
#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(MYPORT);
-
servaddr.sin_addr.s_addr = inet_addr(SERVERIP);
-
-
int ret;
-
char sendbuf[
1024] = {
0};
-
char recvbuf[
1024] = {
0};
-
while (fgets(sendbuf,
sizeof(sendbuf),
stdin) !=
NULL)
-
{
-
-
printf(
"向服务器发送:%s\n",sendbuf);
-
sendto(sock, sendbuf,
strlen(sendbuf),
0, (struct sockaddr *)&servaddr,
sizeof(servaddr));
-
-
ret = recvfrom(sock, recvbuf,
sizeof(recvbuf),
0,
NULL,
NULL);
-
if (ret ==
-1)
-
{
-
if (errno == EINTR)
-
continue;
-
ERR_EXIT(
"recvfrom");
-
}
-
printf(
"从服务器接收:%s\n",recvbuf);
-
-
memset(sendbuf,
0,
sizeof(sendbuf));
-
memset(recvbuf,
0,
sizeof(recvbuf));
-
}
-
-
close(sock);
-
-
-
}
-
-
int main(void)
-
{
-
int sock;
-
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异步错误
7、UDP connect
8、UDP外出接口的确定
9、太大的UDP包可能出现的问题
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。比如 如果发送端速度较快,而接收端较慢,很可能会产生 ICMPSource Quench Error,丢弃一些数据包。