基于UDP协议的接收和发送
一、UDP编程框架
UDP进行程序设计可以分为客户端和服务器端两部分:
-
服务器端主要包含建立套接字、将套接字与地址结构进行绑定、读写数据、关闭套接字几个过程。
-
客户端包括建立套接字、读写数据、关闭套接字几个过程。
- 服务器端和客户端两个流程之间的主要差别在于对地址的绑定(bind())函数,客户端可以不用进行地址和端口的绑定操作。
-
1.UDP编程框图
UDP协议的程序设计框架如下图所示,客户端和服务器之间的差别在于服务器必须使用bind()
函数来绑定侦听的本地UDP端口,而客户端则可以不进行绑定,直接发送到服务器地址的某个端口地址。
与TCP程序设计相比较,UDP缺少了connect()、listen()及accept()函数,这是用于UDP协议无连接的特性,不用维护TCP的连接、断开等状态。
①.UDP协议的服务器端流程
-
UDP协议的服务器端程序设计的流程分为套接字建立、套接字与地址结构进行绑定、收发数据、关闭套接字等过程,分别对应于函数socket()、bind()、sendto()、recvfrom ()和close()。
-
建立套接字过程使用
socket()
函数,这个过程与TCP协议中的含义相同,不过建立的套接字类型为数据报套接字。 -
地址结构与套接字文件描述符进行绑定的过程中,与 TCP 协议中的绑定过程不同的是地址结构的类型。
-
当绑定操作成功后,可以调用
recvfrom()
函数从建立的套接字接收数据或者调用sendto()
函数向建立的套接字发送网络数据。当相关的处理过程结束后,需要调用 close()函数关闭套接字。
②.UDP协议的客户端流程
-
UDP协议的客户端端程序设计的流程分为套接字建立、收发数据、关闭套接字等过程,分别对应于函数 socket()、sendto()、recvfrom()和close()。
-
建立套接字过程使用 socket()函数,这个过程与TCP 协议中的含义相同,不过建立的套接字类型为数据报套接字。
-
建立套接字之后,可以调用函数
sendto()
向建立的套接字发送数据或者调用recvfrom()
函数从建立的套接字收网络数据。 -
当相关的处理过程结束后,需要调用
close()
函数关闭套接字。
③.UDP协议服务器和客户端之间的交互
-
UDP协议中服务器和客户端的交互存在于数据的收发过程中。
-
进行网络数据收发的时候,服务器和客户端的数据是对应的:客户端发送数据的动作,对服务器来说是接收数据的动作;客户端接收数据的动作,对服务器来说是发送数据的动作。
-
UDP协议服务器与客户端之间的交互,与TCP 协议的交互相比较,缺少了二者之间的连接。这是由于 UDP 协议的特点决定的,因为UDP 协议不需要流量控制、不保证数据的可靠性收发,所以不需要服务器和客户端之间建立连接的过程。
2.UDP服务器编程框架
服务器流程主要分为下述6个部分,即建立套接字、设置套接字地址参数、进行端口绑定、接收数据、发送数据、关闭套接字等。
- 建立套接字文件描述符,socket()函数生成套接字文件描述符:
int s = socket(AF_INET, SOCK_DGRAM, 0);
- 设置服务器地址和侦听端口,初始化要绑定的网络地址结构:
struct sockaddr addr_serv;
addr_serv.sin_family = AF_INET; /*地址类型为AF_INET*/
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
addr_serv.sin_port = htons(PORT_SERV); /*服务器端口*/
-
绑定侦听端口,使用bind()函数,将套接字文件描述符和一个地址类型变量进行绑定:
bind(s, (struct sockaddr*)&addr_serv, sizeof(addr_serv));
-
recvfrom()函数接收客户端的网络数据。
-
sendto()函数向服务器主机发送数据。
-
close()函数释放资源。
3.UDP客户端编程框架
-
UDP协议的客户端流程分为套接字建立、设置目的地址和端口、向服务器发送数据、从服务器接收数据、关闭套接字5个部分。与服务器端的框架相比,少了 bind()部分。
-
客户端程序的端口和本地的地址可以由系统在使用时指定。
- 在使用 sendto()和recvfrom ()的时候,网络协议栈会临时指定本地的端口和地址,流程如下:
-
- 建立套接字文件描述符, socket();
- 设置服务器地址和端口, struct sockaddr;
- 向服务器发送数据, sendto();
- 接收服务器的数据, recvfrom();
- 关闭套接字, close()。
二、UDP协议程序设计的常用函数
UDP协议常用的函数有 recv()/recvfrom ()、send()/sendto()、socket()、bind()
等。当然这些函数同样可以用于TCP协议的程序设计。
1.建立套接字socket()和绑定套接字bind()
UDP 协议建立套接字的方式同TCP 方式一 样,使用socket()
函数,只不过协议的类型使用 SOCK_DGRAM
, 而不是SOCK_STREAM
。例如下面是建立一 个UDP 套接字文件描述符的代码。
int s;
s = socket(AF_INET,SOCK_DGRAM,0);
UDP协议使用bind()函数和TCP没有差别,将一个套接字描述符与地址结构绑定在一起,例:
struct sockaddr_in local;//本地地址信息
int from_len = sizeof(from);//地址结构的长度
local.sin_family = AF_INET; //协议族
local.sin_port = htons(8888);// 本地端口
local.sin_addr.s_addr = htonl(INADDR_ANY); //任意本地地址
s = socket(AF_INET, SOCK_DGRAM, 0); //初始化一 个IPv4族的数据报套接字
if (s == -1) {
//检查是否正常初始化socket
perror("socket");
exit(EXIT_FAILURE);
}
bind(s, (struct sockaddr*)&local, sizeof(local)); //套接字绑定
绑定函数 bind()使用的时机,函数bind()的作用是将一个套接字文件描述符与一个本地地址绑定在一 起,即把发送数据的端口地址和IP 地址进行了指定。例如在发送数据的时候,如果不进行绑定,则会临时选择一个随机端口。
2.接收数据recvfrom()/recv()
当客户端成功建立了一个套接字文件描述符并构建了合适的struct sockaddr
结构或者服务器端成功地将套接字文件描述符和地址结构绑定后,可以使用 recv()或者 recvfrom ()
来接收到达此套接字文件描述符上的数据,或者在这个套接字文件描述符上等待数据的到来。
①.rrecv()函数和recvfrom()函数
函数原型:
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int s, void* buf, size_t len, int flags);
ssize_t recvfrom(int s, void* buf, size_t len, int flags, struct sockaddr* from, socklen_t* fromlen);
//s:正在监听端口的套接字文件描述符,由socket()生成。
//buf:接收数据缓冲区
//len:接收数据缓冲区大小,设置大小防止溢出
//from:指向本地的数据结构sockaddr_in的指针,接收数据时发送的地址信息放在这个结构中
//fromlen:所指内容的长度,可使用sizeof(struct sockaddr_in)获得。
recv() 函数和recvfrom()函数的返回值在出错的时候返回-1; 错误值保存在 errno 中,如下表。
成功的时候,函数将返回接收到的数据长度,数据的长度可以为0, 因此如果函数返回值为0, 并不表示发生了错误,仅能表示此时系统中接收不到数据。
注:函数 recvfrom()中的参数 from和fromlen均为指针
,不要直接将地址结构类型和地址类型的长度传入函数中,需要进行取地址的运算。
②.使用recvfrom()函数的例子
先建立一个数据报套接字文件描述符s,在地址结构local设置完毕后,将套接字s与地址结构local绑定一起。
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
int main(int argc,char *argv[])
{
int s;//套接字文件描述符
struct sockaddr_in from;//发送方的地址信息
struct sockaddr_in local;//本地的地址信息
int from_len = sizeof(from);//地址结构的长度
int n;//接收到的数据长度
char buff[128];//接收数据缓冲区
s = socket(AF_INET,SOCK_DGRAM,0);//初始化一 个IPv4族的数据报套接字
if(s == -1){
//检查是否正常初始化socket
perror("socket");
exit(EXIT_FAILURE);
}
local.sin_family = AF_INET; //协议族
local.sin_port = htons(8888);// 本地端口
local.sin_addr.s_addr = htonl(INADDR_ANY); //任意本地地址
bind(s, (struct sockaddr*)&local,