【1】用户数据报协议(UDP)
UDP是一个简单的传输层协议,不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数据报只到达一次,UDP提供无连接的服务即UDP客户与服务器之间不必存在任何长期的关系。
【2】基本UDP套接字编程
【2.1】UDP客户/服务器程序套接字执行流程
注:sendto函数必须指定目的地(即服务器)的地址作为参数;recvfrom函数将与所接收的数据一道返回客户的协议地址;
【2.2】基本UDP通信示例程序
服务器端示例代码
#include "unp.h"
int
main(int argc, char **argv)
{
/**
* sockfd : socket 文件描述符;
* servaddr : 服务器 socket 地址结构;
* cliaddr : 客户端 socket 地址结构;
*/
int sockfd;
struct sockaddr_in servaddr, cliaddr;
/**
* 新建套接字
* AF_INET : IPv4协议;
* SOCK_DGRAM : 数据包套接字;
*/
sockfd = 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(SERV_PORT);
/**
* 绑定服务器地址
*/
Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
/**
* 调用服务器回射函数
*/
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}
void
dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
/**
* n : Recvfrom 函数调用的返回值,表示接收到的字节数;
* len : 接收到的客户端地址信息的大小
* mesg : 数据缓冲区
*/
int n;
socklen_t len;
char mesg[MAXLINE];
/**
* 不断循环将接收到的数据回射给客户端
*/
for ( ; ; ) {
len = clilen;
/**
* 从客户端接收数据到数据缓冲区
* pcliaddr : 指向客户端地址结构的指针
*/
n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
/**
* 将从客户端接收到的数据回射给客户端;
*/
Sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}
客户端示例代码
#include "unp.h"
int
main(int argc, char **argv)
{
/**
* socket : 套接字文件描述符;
* servaddr : 服务器 socket 地址结构;
*/
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: udpcli ");
/**
* 初始化服务器地址结构信息
*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
/**
* 新建套接字
* AF_INET : IPv4协议;
* SOCK_DGRAM : 数据包套接字;
*/
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
/**
* 调用客户端回射函数
*/
dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
exit(0);
}
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
/**
* n : Recvfrom 函数调用的返回值,表示接收到的字节数;
* sendline : 数据发送缓冲区
* recvline : 数据接收缓冲区
* len : 接收到的服务器地址信息的大小
* replyaddr : 发送数据的对端地址结构信息
*/
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr_in *replyaddr;
// 针对服务器地址结构分配内存空间
replyaddr = Malloc(servlen);
// 从文件中获取数据到发送缓冲区
while (Fgets(sendline, MAXLINE, fp) != NULL) {
// 发送给服务器
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
len = servlen;
// 从服务器接收回射数据
n = Recvfrom(sockfd, recvline, MAXLINE, 0, (SA *) replyaddr, &len);
// 打印数据发送端的地址与端口信息
printf("received reply from %s, port %d\n",
inet_ntoa(replyaddr->sin_addr), htons(replyaddr->sin_port));
// 接收缓冲区的结束标志
recvline[n] = 0; /* null terminate */
// 将接收到的数据输出到标准输出
Fputs(recvline, stdout);
}
}
示例程序说明:
【2.3】UDP的connect函数
UDP套接字调用connect,内核只是检查是否存在立即可知的错误,记录对端IP地址和端口号,然后立即返回到调用进程;
TCP,未连接UDP,已连接UDP套接字比较
套接字类型 | write或send | 不指定目的地址的sendto | 指定目的地址的sendto |
TCP套接字 | 可以 | 可以 | EISCONN |
UDP套接字,已连接 | 可以 | 可以 | EISCONN |
UDP套接字,未连接 | EDESTADDRREQ | EDESTADDRREQ | 可以 |
使用场合:UDP客户进程或服务器进程在使用自己的UDP套接字与确定的唯一对端进行通信的场合;
一个UDP套接字多次调用connect:1. 指定新的IP地址和端口号;2. 断开套接字;
【2.4】UDP套接字编程注意事项
【2.5】使用select函数的TCP 和 UDP服务器程序示例
【2.5.1】模型示意图
【2.5.2】示例程序
#include "unp.h"
int
main(int argc, char **argv)
{
/**
* listenfd : 监听套接字文件描述符
* connfd : 连接套接字文件描述符
* udpfd : UDP socket 文件描述符
* nready : 就绪描述符数目,select 函数返回值
* maxfdp1 : 指定待测试的描述符个数,它的值是待测试的最大描述符加1
* mesg : 数据缓冲区
* childpid : 子进程 ID
* rset : 读描述符集合
* n : 接收到的字节数
* len : socket 地址结构大小
* on : 开关变量
* cliaddr : 客户端地址结构
* servaddr : 服务器地址结构
* sig_chld : SIGCHLD 信号处理函数,处理僵死进程
*/
int listenfd, connfd, udpfd, nready, maxfdp1;
char mesg[MAXLINE];
pid_t childpid;
fd_set rset;
ssize_t n;
socklen_t len;
const int on = 1;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
/**
* 创建监听套接字
* AF_INET : IPv4协议
* SOCK_STREAM : 字节流套接字
*/
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器地址结构信息
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
// 设置 socket 套接字属性,此处允许重用本地地址
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
// 绑定 TCP 监听套接字到服务器地址
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
// 监听客户端连接
Listen(listenfd, LISTENQ);
/**
* 创建监听套接字
* AF_INET : IPv4协议
* SOCK_DGRAM : 数据包套接字
*/
udpfd = 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(SERV_PORT);
// 绑定 UDP 套接字到服务器地址
Bind(udpfd, (SA *) &servaddr, sizeof(servaddr));
// 指定 SIGCHLD 信号对应的处理函数
Signal(SIGCHLD, sig_chld); /* must call waitpid() */
/**
* 初始化读描述符集合 rset
* 初始化待测试的描述符个数 maxfdp1
*/
FD_ZERO(&rset);
maxfdp1 = max(listenfd, udpfd) + 1;
for ( ; ; ) {
// 注册 listenfd udpfd 到描述符集合中
FD_SET(listenfd, &rset);
FD_SET(udpfd, &rset);
// 监听发生的事件
if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
if (errno == EINTR)
continue; /* back to for() */
else
err_sys("select error");
}
// 监听套接字激活
if (FD_ISSET(listenfd, &rset)) {
len = sizeof(cliaddr);
// 接收连接并创建连接套接字
connfd = Accept(listenfd, (SA *) &cliaddr, &len);
/**
* 创建子进程并处理业务逻辑
* str_echo 函数 : 用于处理客户端的请求
*/
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
// UDP 套接字激活
if (FD_ISSET(udpfd, &rset)) {
len = sizeof(cliaddr);
// UDP 回射处理逻辑
n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len);
Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len);
}
}
}
void
sig_chld(int signo)
{
pid_t pid;
int stat;
/**
* 原型 : pid_t waitpid(pid_t pid,int * status,int options)
* 功能 : 如果在调用 waitpid() 时子进程已经结束,
* 则 waitpid() 会立即返回子进程结束状态值;
* 子进程的结束状态值会由参数 status 返回,
* 而子进程的进程识别码也会一起返回;
* 如果不在意结束状态值,则参数 status 可以设成 NULL;
* 参数 :
* 参数 pid 为欲等待的子进程识别码,
* 其数值意义如下:
* pid<-1 等待进程组识别码为 pid 绝对值的任何子进程;
* pid=-1 等待任何子进程,相当于 wait();
* pid=0 等待进程组识别码与目前进程相同的任何子进程;
* pid>0 等待任何子进程识别码为 pid 的子进程;
*
* 参数 options 提供了一些额外的选项来控制 waitpid
* WNOHANG 若 pid 指定的子进程没有结束,则 waitpid() 函数返回0,不予以等待;
* 若结束,则返回该子进程的ID;
* WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会;
* WIFSTOPPED(status) 宏确定返回值是否对应于一个暂停子进程;
*
* 子进程的结束状态返回后存于 status
*
* 相关宏介绍
* WIFEXITED(status) 如果若为正常结束子进程返回的状态,则为真;
* 对于这种情况可执行 WEXITSTATUS(status),取子进程传给 exit 或 _eixt 的低8位;
* WEXITSTATUS(status) 取得子进程 exit()返回的结束代码,
* 一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏;
* WIFSIGNALED(status) 若为异常结束子进程返回的状态,则为真;
* 对于这种情况可执行 WTERMSIG(status),取使子进程结束的信号编号;
* WTERMSIG(status) 取得子进程因信号而中止的信号代码,
* 一般会先用 WIFSIGNALED 来判断后才使用此宏;
* WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真;
* 对于这种情况可执行 WSTOPSIG(status),取使子进程暂停的信号编号;
* WSTOPSIG(status) 取得引发子进程暂停的信号代码,
* 一般会先用 WIFSTOPPED 来判断后才使用此宏;
*
* 返回值
* 如果执行成功则返回子进程识别码 (PID) , 如果有错误发生则返回返回值-1
* 且失败原因存于 errno 中;
*/
// 此处等待子进程结束,防止出现僵死进程
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
【3】高级UDP套接字编程
【3.1】接收标志、目的IP地址和接口索引
示例程序分析
#include "unp.h"
/**
* 相关宏介绍 :
* CMSG_FIRSTHDR() 宏
* 该宏用于返回 msg 所指向的 msghdr 结构体中的第一个 struct cmsghdr 结构体指针
*
* CMSG_NXTHDR() 宏
* 该宏用于返回下一个附属数据对象的struct cmsghdr 指针
*
* CMSG_SPACE()宏
* 该宏用于计算一个附属数据对象的所必需的空白
*
* CMSG_DATA 宏
* 该宏根据传入的 cmsghdr 指针参数, 返回其后面数据部分的指针
*
* CMSG_LEN 宏
* 该宏的入参是一个控制信息中的数据部分的大小
* 返回值是这个根据入参大小, 需要配置的 cmsghdr 结构体中 cmsg_len 成员的值
*/
#include /* ALIGN macro for CMSG_NXTHDR() macro */
/**
* 函数入参说明 :
* fd : socket 文件描述符
* ptr : 数据缓冲区指针
* nbytes : 数据缓冲区大小
* flagsp : 值传递方式的标志位,用于传入控制信息
* sa : 地址结构指针
* salenptr : 地址结构大小指针
* pktp : 函数 recvfrom_flags 返回信息结构体指针
*/
/**
* struct unp_in_pktinfo {
* struct in_addr ipi_addr; // 保存 IP 地址信息
* int ipi_ifindex; // 保存系统分配的索引
* };
*/
/**
* 接收数据并返回标志信息
*/
ssize_t
recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp,
SA *sa, socklen_t *salenptr, struct unp_in_pktinfo *pktp)
{
/**
* struct msghdr {
* void * msg_name; //协议地址
* socklen_t msg_namelen; //协议地址长度
* struct lovec * msg_lov; //输入输出缓冲区数组
* ssize_t msg_lovlen; //输入输出数组中缓冲区的个数
* void * msg_control; //辅助数据的起始位置指针
* socklen_t msg_controllen; //辅助数据大小
* int msg_flags; //引用传递,用于返回之前flags的控制信息
* }
*/
struct msghdr msg;
/**
* struct iovec{
* void *iov_base; // 起始地址指针
* size_t iov_len; // 区间长度
*};
*/
struct iovec iov[1];
/**
* n : 接收到的字节数
*/
ssize_t n;
#ifdef HAVE_MSGHDR_MSG_CONTROL
struct cmsghdr *cmptr;
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(struct in_addr)) +
CMSG_SPACE(sizeof(struct unp_in_pktinfo))];
} control_un;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
msg.msg_flags = 0;
#else
// 初始化 msg
bzero(&msg, sizeof(msg)); /* make certain msg_accrightslen = 0 */
#endif
// 初始化 msg
msg.msg_name = sa;
msg.msg_namelen = *salenptr;
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
// 接收数据,flagsp : 值传递方式的标志位,用于保存控制信息;
if ( (n = recvmsg(fd, &msg, *flagsp)) < 0)
return(n);
// 地址结构大小,用于回传结果信息;
*salenptr = msg.msg_namelen; /* pass back results */
// 回传信息的结构体
if (pktp)
bzero(pktp, sizeof(struct unp_in_pktinfo)); /* 0.0.0.0, i/f = 0 */
// 若本实现不支持 msg_control 成员,将待返回标记置为 0,并返回
#ifndef HAVE_MSGHDR_MSG_CONTROL
*flagsp = 0; /* pass back results */
return(n);
#else
*flagsp = msg.msg_flags; /* pass back results */
/**
* 情况如下 :
* 1. 没有控制信息;
* 2. 控制信息被截断;
* 3. 调用者不想返回一个 unp_in_pktinfo 结构;
*/
if (msg.msg_controllen < sizeof(struct cmsghdr) ||
(msg.msg_flags & MSG_CTRUNC) || pktp == NULL)
// MSG_CTRUNC : 本数据报的辅助数据被截断,
// 即内核预备返回的辅助数据超过进程事先分配的空间
return(n);
/**
* 遍历 msghdr 结构体,处理任意数目的辅助数据对象;
*/
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL;
cmptr = CMSG_NXTHDR(&msg, cmptr)) {
/**
* IP_RECVDSTADDR : 将目的 IP 地址作为控制信息返回;
* IPPROTO_IP | IP_RECVDSTADDR : 随 UDP 数据报接收目的地址;
*/
#ifdef IP_RECVDSTADDR
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_RECVDSTADDR) {
memcpy(&pktp->ipi_addr, CMSG_DATA(cmptr),
sizeof(struct in_addr));
continue;
}
#endif
/**
* IP_RECVIF : 将接收接口的索引作为控制信息返回;
* IPPROTO_IP | IP_RECVIF : 随 UDP 数据报接收接口索引;
*/
#ifdef IP_RECVIF
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_RECVIF) {
struct sockaddr_dl *sdl;
sdl = (struct sockaddr_dl *) CMSG_DATA(cmptr);
/**
* struct sockaddr_dl : 数据链路套接字地址结构
* sdl->sdl_index : 系统分配的索引
*/
pktp->ipi_ifindex = sdl->sdl_index;
continue;
}
#endif
err_quit("unknown ancillary data, len = %d, level = %d, type = %d",
cmptr->cmsg_len, cmptr->cmsg_level, cmptr->cmsg_type);
}
return(n);
#endif /* HAVE_MSGHDR_MSG_CONTROL */
}
/**
* recvfrom_flags 函数的包装函数
* 当 recvfrom_flags 函数发生错误时打印错误日志,当前进程退出
*/
ssize_t
Recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp,
SA *sa, socklen_t *salenptr, struct unp_in_pktinfo *pktp)
{
ssize_t n;
n = recvfrom_flags(fd, ptr, nbytes, flagsp, sa, salenptr, pktp);
if (n < 0)
err_quit("recvfrom_flags error");
return(n);
}
【3.2】数据报截断
【3.3】UDP代替TCP的时机
【3.4】给UDP应用添加可靠性
计算公式
示例代码分析
#include "unp.h"
ssize_t Dg_send_recv(int, const void *, size_t, void *, size_t,
const SA *, socklen_t);
// UDP 客户端程序
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
ssize_t n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
// 从文件读取数据
while (Fgets(sendline, MAXLINE, fp) != NULL) {
// 收发数据处理函数
n = Dg_send_recv(sockfd, sendline, strlen(sendline),
recvline, MAXLINE, pservaddr, servlen);
recvline[n] = 0;
// 打印收到的数据
Fputs(recvline, stdout);
}
}
#include "unprtt.h"
#include
#define RTT_DEBUG
/**
* rtt_info 数据结构 :
*
* struct rtt_info {
* float rtt_rtt; // most recent measured RTT, seconds
* float rtt_srtt; // smoothed RTT estimator, seconds
* float rtt_rttvar; // smoothed mean deviation, seconds
* float rtt_rto; // current RTO to use, seconds
* int rtt_nrexmt; // #times retransmitted: 0, 1, 2, ...
* uint32_t rtt_base; // #sec since 1/1/1970 at start
* };
*
* struct msghdr {
*
* // 消息的协议地址 协议地址和套接口信息,在非连接的 UDP 中,发送者要指定对方地址端口,
* 接受方用于的到数据来源,如果不需要的话可以设置为 NULL
* (在 TCP 或者连接的 UDP 中,一般设置为 NULL)
* void * msg_name ;
* socklen_t msg_namelen ; //地址的长度
* struct iovec * msg_iov ; //多 io 缓冲区的地址
* int msg_iovlen ; //缓冲区的个数
* void * msg_control ; //辅助数据的地址
* socklen_t msg_controllen ; //辅助数据的长度
* int msg_flags ; //接收消息的标识
* }
*
* #include
* struct iovec{
* void *iov_base; // Pointer to data
* size_t iov_len; // Length of data
* }
*/
static struct rtt_info rttinfo;
static int rttinit = 0;
static struct msghdr msgsend, msgrecv;
static struct hdr {
uint32_t seq;
uint32_t ts;
} sendhdr, recvhdr;
// 定时器信号处理函数,处理 SIGALRM 信号
static void sig_alrm(int signo);
static sigjmp_buf jmpbuf;
ssize_t
dg_send_recv(int fd, const void *outbuff, size_t outbytes,
void *inbuff, size_t inbytes,
const SA *destaddr, socklen_t destlen)
{
ssize_t n;
/**
* iovsend[ 0 ] : 存放信息头; iovsend[ 1 ] : 存放信息体;
* iovrecv[ 0 ] : 存放信息头; iovrecv[ 1 ] : 存放信息体;
*/
struct iovec iovsend[2], iovrecv[2];
if (rttinit == 0) {
// 初始化 rtt_info 结构体变量
rtt_init(&rttinfo);
rttinit = 1;
rtt_d_flag = 1;
}
// 初始化收发信息缓冲区结构体
sendhdr.seq++;
msgsend.msg_name = destaddr;
msgsend.msg_namelen = destlen;
msgsend.msg_iov = iovsend;
msgsend.msg_iovlen = 2;
iovsend[0].iov_base = &sendhdr;
iovsend[0].iov_len = sizeof(struct hdr);
iovsend[1].iov_base = outbuff;
iovsend[1].iov_len = outbytes;
msgrecv.msg_name = NULL;
msgrecv.msg_namelen = 0;
msgrecv.msg_iov = iovrecv;
msgrecv.msg_iovlen = 2;
iovrecv[0].iov_base = &recvhdr;
iovrecv[0].iov_len = sizeof(struct hdr);
iovrecv[1].iov_base = inbuff;
iovrecv[1].iov_len = inbytes;
// 指定 SIGALRM 信号的处理函数
Signal(SIGALRM, sig_alrm);
// 重置 rtt_nrexmt(重传次数)
rtt_newpack(&rttinfo);
// 定时时间到达 RTO 时,重新发送数据
sendagain:
#ifdef RTT_DEBUG
fprintf(stderr, "send %4d: ", sendhdr.seq);
#endif
// 更新 rtt_base
sendhdr.ts = rtt_ts(&rttinfo);
// 发送数据
Sendmsg(fd, &msgsend, 0);
// 启动定时器,定时时间为 RTO 的值
alarm(rtt_start(&rttinfo));
#ifdef RTT_DEBUG
// 打印调试信息
rtt_debug(&rttinfo);
#endif
/**
* sigsetjmp() 会保存目前堆栈环境,然后将目前的地址作一个记号,
* 而在程序其他地方调用 siglongjmp() 时便会直接跳到这个记号位置,
* 然后还原堆栈,继续执行程序;
*
* 调用 sigsetjmp 为信号处理函数建立了一个跳转缓冲区,
* 若 sigsetjmp 的返回不是由长跳转引起则调用 recvmsg 等待下一个数据报到达,
* 若 alarm 定时期满,sigsetjmp 便由长跳转返回1;
*/
if (sigsetjmp(jmpbuf, 1) != 0) {
// 判断是否发生了 timeout
if (rtt_timeout(&rttinfo) < 0) {
err_msg("dg_send_recv: no response from server, giving up");
rttinit = 0;
errno = ETIMEDOUT;
return(-1);
}
#ifdef RTT_DEBUG
// 若是 DEBUG 模式,则打印调试信息
err_msg("dg_send_recv: timeout, retransmitting");
#endif
goto sendagain;
}
do {
n = Recvmsg(fd, &msgrecv, 0);
#ifdef RTT_DEBUG
fprintf(stderr, "recv %4d\n", recvhdr.seq);
#endif
} while (n < sizeof(struct hdr) || recvhdr.seq != sendhdr.seq);
// 接收到正确的应答数据后,关闭定时器
alarm(0);
// 更新 RTT 的值
rtt_stop(&rttinfo, rtt_ts(&rttinfo) - recvhdr.ts);
// 返回数据体的字节数
return(n - sizeof(struct hdr));
}
static void
sig_alrm(int signo)
{
siglongjmp(jmpbuf, 1);
}
// 封装 dg_send_recv 函数,在出错时打印日志信息
ssize_t
Dg_send_recv(int fd, const void *outbuff, size_t outbytes,
void *inbuff, size_t inbytes,
const SA *destaddr, socklen_t destlen)
{
ssize_t n;
n = dg_send_recv(fd, outbuff, outbytes, inbuff, inbytes,
destaddr, destlen);
if (n < 0)
err_quit("dg_send_recv error");
return(n);
}
#include "unprtt.h"
int rtt_d_flag = 0; /* debug flag; can be set by caller */
/*
* Calculate the RTO value based on current estimators:
* smoothed RTT plus four times the deviation
*/
#define RTT_RTOCALC(ptr) ((ptr)->rtt_srtt + (4.0 * (ptr)->rtt_rttvar))
/**
* 将入参规范化到一定的范围内
*/
static float
rtt_minmax(float rto)
{
if (rto < RTT_RXTMIN)
rto = RTT_RXTMIN;
else if (rto > RTT_RXTMAX)
rto = RTT_RXTMAX;
return(rto);
}
// 初始化 rtt_info 结构体
void
rtt_init(struct rtt_info *ptr)
{
struct timeval tv;
Gettimeofday(&tv, NULL);
ptr->rtt_base = tv.tv_sec; /* # sec since 1/1/1970 at start */
ptr->rtt_rtt = 0;
ptr->rtt_srtt = 0;
ptr->rtt_rttvar = 0.75;
ptr->rtt_rto = rtt_minmax(RTT_RTOCALC(ptr));
}
// 重置 rtt_base
uint32_t
rtt_ts(struct rtt_info *ptr)
{
uint32_t ts;
struct timeval tv;
Gettimeofday(&tv, NULL);
ts = ((tv.tv_sec - ptr->rtt_base) * 1000) + (tv.tv_usec / 1000);
return(ts);
}
void
rtt_newpack(struct rtt_info *ptr)
{
ptr->rtt_nrexmt = 0;
}
// 返回 RTO 的值
int
rtt_start(struct rtt_info *ptr)
{
return((int) (ptr->rtt_rto + 0.5));
}
/*
* A response was received.
* Stop the timer and update the appropriate values in the structure
* based on this packet's RTT. We calculate the RTT, then update the
* estimators of the RTT and its mean deviation.
* This function should be called right after turning off the
* timer with alarm(0), or right after a timeout occurs.
*/
void
rtt_stop(struct rtt_info *ptr, uint32_t ms)
{
double delta;
// 以秒为单位,估计 RTT 的大小
ptr->rtt_rtt = ms / 1000.0;
/**
* 更新 RTT
*
* Update our estimators of RTT and mean deviation of RTT.
* See Jacobson's SIGCOMM '88 paper, Appendix A, for the details.
* We use floating point here for simplicity.
*/
delta = ptr->rtt_rtt - ptr->rtt_srtt;
ptr->rtt_srtt += delta / 8; /* g = 1/8 */
if (delta < 0.0)
delta = -delta; /* |delta| */
ptr->rtt_rttvar += (delta - ptr->rtt_rttvar) / 4; /* h = 1/4 */
ptr->rtt_rto = rtt_minmax(RTT_RTOCALC(ptr));
}
/**
* 判断是否发生了 timeout 事件
*
* A timeout has occurred.
* Return -1 if it's time to give up, else return 0.
*/
int
rtt_timeout(struct rtt_info *ptr)
{
ptr->rtt_rto *= 2; /* next RTO */
if (++ptr->rtt_nrexmt > RTT_MAXNREXMT)
return(-1); /* time to give up for this packet */
return(0);
}
// 打印 rtt_info 的信息以方便调试
void
rtt_debug(struct rtt_info *ptr)
{
if (rtt_d_flag == 0)
return;
fprintf(stderr, "rtt = %.3f, srtt = %.3f, rttvar = %.3f, rto = %.3f\n",
ptr->rtt_rtt, ptr->rtt_srtt, ptr->rtt_rttvar, ptr->rtt_rto);
fflush(stderr);
}
【3.5】绑定接口地址
示例程序分析
#include "unpifi.h"
void mydg_echo(int, SA *, socklen_t, SA *);
int
main(int argc, char **argv)
{
int sockfd;
const int on = 1;
pid_t pid;
struct ifi_info *ifi, *ifihead;
struct sockaddr_in *sa, cliaddr, wildaddr;
/**
* 调用 Get_ifi_info 获取所有 IPv4 地址包括别名地址
*/
for (ifihead = ifi = Get_ifi_info(AF_INET, 1);
ifi != NULL; ifi = ifi->ifi_next) {
/**
* 创建 UDP 套接字,并在其上绑定单播地址
* SO_REUSEADDR :
*/
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
/**
* 1. SO_REUSEADDR 允许启动一个监听服务器并捆绑其众所周知的端口,
即使以前建立的将该端口用作他们的本地端口的连接仍存在;
* 2. 允许在同一端口上启动同一服务器的多个实例,
只要每个实例捆绑一个不同的本地 IP 地址即可;
* 3. SO_REUSEADDR 允许单个进程捆绑同一端口到多个套接字上,
只要每次捆绑指定不同的本地 IP 地址即可;
* 4. SO_REUSEADDR 允许完全重复的捆绑:当一个 IP 地址和端口号
已绑定到某个套接字上时,如果传输协议支持,
* 同样的 IP 地址和端口还可以捆绑到另一个套接字上,
一般来说本特性仅支持UDP套接字;
*/
Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa = (struct sockaddr_in *) ifi->ifi_addr;
sa->sin_family = AF_INET;
sa->sin_port = htons(SERV_PORT);
Bind(sockfd, (SA *) sa, sizeof(*sa));
printf("bound %s\n", Sock_ntop((SA *) sa, sizeof(*sa)));
// 创建一个子进程,等待数据报到达并回射给发送者
if ( (pid = Fork()) == 0) {
mydg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr), (SA *) sa);
exit(0);
}
/**
* 若当前接口支持广播,则绑定广播地址;
*/
if (ifi->ifi_flags & IFF_BROADCAST) {
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa = (struct sockaddr_in *) ifi->ifi_brdaddr;
sa->sin_family = AF_INET;
sa->sin_port = htons(SERV_PORT);
if (bind(sockfd, (SA *) sa, sizeof(*sa)) < 0) {
/**
* 允许 bind 调用以 EADDRINUSE 错误返回失败的结果;
* 原因 : 若某个接口有多个处于同一个子网的地址(别名),
* 则这些单播地址需要对应同一个广播地址;
*/
if (errno == EADDRINUSE) {
printf("EADDRINUSE: %s\n",
Sock_ntop((SA *) sa, sizeof(*sa)));
Close(sockfd);
continue;
} else
err_sys("bind error for %s",
Sock_ntop((SA *) sa, sizeof(*sa)));
}
printf("bound %s\n", Sock_ntop((SA *) sa, sizeof(*sa)));
// 创建一个子进程,等待数据报到达并回射给发送者
if ( (pid = Fork()) == 0) {
mydg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr),
(SA *) sa);
exit(0);
}
}
}
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
bzero(&wildaddr, sizeof(wildaddr));
wildaddr.sin_family = AF_INET;
wildaddr.sin_addr.s_addr = htonl(INADDR_ANY);
wildaddr.sin_port = htons(SERV_PORT);
Bind(sockfd, (SA *) &wildaddr, sizeof(wildaddr));
printf("bound %s\n", Sock_ntop((SA *) &wildaddr, sizeof(wildaddr)));
// 创建一个子进程,等待数据报到达并回射给发送者
if ( (pid = Fork()) == 0) {
mydg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr), (SA *) sa);
exit(0);
}
// 主进程终止,派生出的所有子进程继续运行
exit(0);
}
void
mydg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, SA *myaddr)
{
int n;
char mesg[MAXLINE];
socklen_t len;
// 无限循环
for ( ; ; ) {
len = clilen;
// 接收数据
n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
printf("child %d, datagram from %s", getpid(),
Sock_ntop(pcliaddr, len));
printf(", to %s\n", Sock_ntop(myaddr, clilen));
// 发送接收到的数据,回射
Sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}
#include "unpifi.h"
#include "unproute.h"
/**
* 结构体结构 :
*
* struct if_msghdr {
* u_short ifm_msglen; // to skip over non-understood messages
* u_char ifm_version; // future binary compatability
* u_char ifm_type; // message type
*
* int ifm_addrs; // bitmask identifying sockaddrs in msg
* int ifm_flags; // value of if_flags
* u_short ifm_index; // index for associated ifp
* struct if_data ifm_data; // statistics and other data about if
* }
*
* struct ifa_msghdr {
* u_short ifam_msglen; // to skip over non-understood messages
* u_char ifam_version; // future binary compatability
* u_char ifam_type; // message type
*
* int ifam_addrs; // bitmask identifying sockaddrs in msg
* int ifam_flags; // value of ifa_flags
* u_short ifam_index; // index for associated ifp
* int ifam_metric; // value of ifa_metric
* }
* struct ifi_info {
* char ifi_name[IFI_NAME]; // interface name, null-terminated
* short ifi_index; // interface index
* short ifi_mtu; // interface MTU
* u_char ifi_haddr[IFI_HADDR]; // hardware address
* u_short ifi_hlen; // # bytes in hardware address: 0, 6, 8
* short ifi_flags; // IFF_xxx constants from
* short ifi_myflags; // our own IFI_xxx flags
* struct sockaddr *ifi_addr; // primary address
* struct sockaddr *ifi_brdaddr; // broadcast address
* struct sockaddr *ifi_dstaddr; // destination address
* struct ifi_info *ifi_next; // next of these structures
* }
*/
struct ifi_info *
get_ifi_info(int family, int doaliases)
{
int flags;
char *buf, *next, *lim;
size_t len;
struct if_msghdr *ifm;
struct ifa_msghdr *ifam;
struct sockaddr *sa, *rti_info[RTAX_MAX];
struct sockaddr_dl *sdl;
struct ifi_info *ifi, *ifisave, *ifihead, **ifipnext;
/**
* 检查路由表或接口清单
*/
buf = Net_rt_iflist(family, 0, &len);
ifihead = NULL;
ifipnext = &ifihead;
// lim : 返回信息的尾部
lim = buf + len;
for (next = buf; next < lim; next += ifm->ifm_msglen) {
ifm = (struct if_msghdr *) next;
/**
* RTM_IFINFO : 接口正在开工、停工等
* RTM_NEWADDR : 多播地址正在被增至接口
*
* IFF_POINTOPOINT : Interface is point-to-point link
* IFF_BROADCAST : Broadcast address valid
* IFF_UP : Interface is up
*/
if (ifm->ifm_type == RTM_IFINFO) {
if ( ((flags = ifm->ifm_flags) & IFF_UP) == 0)
continue;
sa = (struct sockaddr *) (ifm + 1);
/**
* 根据 ifm->ifm_addrs 的掩码将 sa 地址信息存储到 rti_info 中
*/
get_rtaddrs(ifm->ifm_addrs, sa, rti_info);
if ( (sa = rti_info[RTAX_IFP]) != NULL) {
/**
* 函数名 : calloc
* 函数原型 : void* calloc(unsigned int num,unsigned int size);
* 功能:在内存的动态存储区中分配 num 个长度为 size
的连续空间,函数返回一个指向分配起始地址的指针;
* 如果分配不成功,返回 NULL;
*/
ifi = Calloc(1, sizeof(struct ifi_info));
/**
* *ifipnext = ifi,ifipnext 的值为 ifi
* ifipnext = &ifi->ifi_next,ifipnext 指针指向 ifi->ifi_next 的地址,
* 即将 ifi 连接到了队列中;
*/
*ifipnext = ifi;
ifipnext = &ifi->ifi_next;
ifi->ifi_flags = flags;
/**
* 对于数据链路的套接字
* sockaddr_dl : 数据链路套接字地址结构
*/
if (sa->sa_family == AF_LINK) {
sdl = (struct sockaddr_dl *) sa;
ifi->ifi_index = sdl->sdl_index;
if (sdl->sdl_nlen > 0)
snprintf(ifi->ifi_name, IFI_NAME, "%*s",
sdl->sdl_nlen, &sdl->sdl_data[0]);
else
snprintf(ifi->ifi_name, IFI_NAME, "index %d",
sdl->sdl_index);
if ( (ifi->ifi_hlen = sdl->sdl_alen) > 0)
memcpy(ifi->ifi_haddr, LLADDR(sdl),
min(IFI_HADDR, sdl->sdl_alen));
}
}
} else if (ifm->ifm_type == RTM_NEWADDR) {
if (ifi->ifi_addr) {
if (doaliases == 0)
/**
* 对于多播地址若不需要记录别名,
* 则仅仅记录一个地址信息即可
*/
continue;
ifisave = ifi;
ifi = Calloc(1, sizeof(struct ifi_info));
*ifipnext = ifi;
ifipnext = &ifi->ifi_next;
ifi->ifi_flags = ifisave->ifi_flags;
ifi->ifi_index = ifisave->ifi_index;
ifi->ifi_hlen = ifisave->ifi_hlen;
memcpy(ifi->ifi_name, ifisave->ifi_name, IFI_NAME);
memcpy(ifi->ifi_haddr, ifisave->ifi_haddr, IFI_HADDR);
}
ifam = (struct ifa_msghdr *) next;
sa = (struct sockaddr *) (ifam + 1);
/**
* 根据 ifam->ifam_addrs 的掩码将 sa 地址信息存储到 rti_info 中
*/
get_rtaddrs(ifam->ifam_addrs, sa, rti_info);
if ( (sa = rti_info[RTAX_IFA]) != NULL) {
ifi->ifi_addr = Calloc(1, sa->sa_len);
memcpy(ifi->ifi_addr, sa, sa->sa_len);
}
if ((flags & IFF_BROADCAST) &&
(sa = rti_info[RTAX_BRD]) != NULL) {
ifi->ifi_brdaddr = Calloc(1, sa->sa_len);
memcpy(ifi->ifi_brdaddr, sa, sa->sa_len);
}
if ((flags & IFF_POINTOPOINT) &&
(sa = rti_info[RTAX_BRD]) != NULL) {
ifi->ifi_dstaddr = Calloc(1, sa->sa_len);
memcpy(ifi->ifi_dstaddr, sa, sa->sa_len);
}
} else
err_quit("unexpected message type %d", ifm->ifm_type);
}
return(ifihead);
}
/**
* 释放 ifi_info 结构内存
*/
void
free_ifi_info(struct ifi_info *ifihead)
{
struct ifi_info *ifi, *ifinext;
for (ifi = ifihead; ifi != NULL; ifi = ifinext) {
if (ifi->ifi_addr != NULL)
free(ifi->ifi_addr);
if (ifi->ifi_brdaddr != NULL)
free(ifi->ifi_brdaddr);
if (ifi->ifi_dstaddr != NULL)
free(ifi->ifi_dstaddr);
ifinext = ifi->ifi_next;
free(ifi);
}
}
/**
* 包装 get_ifi_info 函数,在发生错误时打印错误日志
*/
struct ifi_info *
Get_ifi_info(int family, int doaliases)
{
struct ifi_info *ifi;
if ( (ifi = get_ifi_info(family, doaliases)) == NULL)
err_quit("get_ifi_info error");
return(ifi);
}
#include "unproute.h"
char *
net_rt_iflist(int family, int flags, size_t *lenp)
{
int mib[6];
char *buf;
/**
* #include
* #include
* int sysctl( int *name, u_int namelen, void *oldp, size_t *oldenp, void *newp, size_t newlen );
* 返回 0:成功 -1:失败
*
* name 参数是指定名字的一个整数数组,namelen 参数指定了该数组中的元素数目;
* 该数组中的第一个元素指定本请求定向到内核的哪个子系统;
* 第二个及其后元素依次细化指定该系统的某个部分;
* 获取某个值 :
* oldp 参数指向一个供内核存放该值的缓冲区,oldlenp 则是一个值-结果参数:
函数被调用时,oldlenp 指向的值指定该缓冲区的大小;
* 函数返回时,该值给出内核存放在该缓冲区中的数据量,如果这个缓冲不够大,
函数就返回 ENOMEM 错误;
* 作为特例,oldp 可以是一个空指针,而 oldlenp 却是一个非空指针,
内核确定这样的调用应该返回的数据量,并通过 oldlenp 返回这个大小;
* 设置某个新值 :
* newp 参数指向一个大小为 newlen 参数值的缓冲区,
如果不准备指定一个新值,那么 newp 应为一个空指针,newlen 应为 0;
*
* mib[0] = CTL_NET : 获取网络子系统的信息;
* mib[1] = AF_ROUTE :
* AF_INET : 获取或者设置影响网际协议的变量,
下一级为使用某个 IPPROTO_XXX 常值指定的具体协议;
* AF_LINK : 获取或设置链路层信息;
* AF_ROUTE : 返回路由表或接口清单的信息;
* AF_UNSPEC : 获取或设置一些套接口层变量;
*
* name[] 返回IPv4路由表 返回IPv4ARP高速缓存 返回IPv6路由表 返回接口清单
* 0 CTL_NET CTL_NET CTL_NET CTL_NET
* 1 AF_ROUTE AF_ROUTE AF_ROUTE AF_ROUTE
* 2 0 0 0 0
* 3 AF_INET AF_INET AF_INET6 0
* 4 NET_RT_DUMP NET_RT_FLAGS NET_RT_DUMP NET_RT_IFLIST
* 5 0 RTF_LLINFO 0 0
*
* 1. NET_RT_DUMP 返回由 name[3] 指定的地址族的路由表,如果所指定的地址族为 0,
那么返回所有地址族的路由表;
* 路由表作为可变数目的 RTM_GET 消息返回,每个消息后跟最多 4 个套接口地址结构:
本路由表项目的地址,网关,网络掩码和克隆掩码;
* 相比直接读写路由套接口操作,sysctl 操作所有改动仅仅体现在内核通过
后者返回一个或者多个 RTM_GET 消息;
* 2. NET_RT_FLAGS 返回由 name[3] 指定的地址族的路由表,
但是仅仅限于那些所带标志(若干个 RTF_XXX 常值的逻辑或)
* 与由 name[5] 指定的标志匹配的路由表项,路由表中所有
ARP 高速缓存均设置了 RTF_LLINFO 标志位,
* 这种操作的信息返回格式和上一种操作的一致;
* 3. NET_RT_IFLIST 返回所有已配置接口的信息,如果 name[5] 不为 0,
它就是某个接口的索引,于是仅仅返回该接口的信息,
* 已赋予每个接口的所有地址也同时返回;不过如果 name[3] 不为 0,
那么仅限于返回指定地址族的地址;
*/
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = family; /* only addresses of this family */
mib[4] = NET_RT_IFLIST;
mib[5] = flags; /* interface index or 0 */
if (sysctl(mib, 6, NULL, lenp, NULL, 0) < 0)
return(NULL);
if ( (buf = malloc(*lenp)) == NULL)
return(NULL);
if (sysctl(mib, 6, buf, lenp, NULL, 0) < 0) {
free(buf);
return(NULL);
}
return(buf);
}
/**
* 包装 net_rt_iflist 函数,在发生错误时打印错误日志
*/
char *
Net_rt_iflist(int family, int flags, size_t *lenp)
{
char *ptr;
if ( (ptr = net_rt_iflist(family, flags, lenp)) == NULL)
err_sys("net_rt_iflist error");
return(ptr);
}
#include "unproute.h"
/*
* Round up 'a' to next multiple of 'size', which must be a power of 2
* a & (2^n-1) 表示 a 的低 n 位是否有值
* a | (2^n - 1) 表示 a 的低 n 位赋值为1
* 1 + a | (2^n -1) 表示离 a 最近的下一个 2^n 倍值
*/
#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
/*
* Step to next socket address structure;
* if sa_len is 0, assume it is sizeof(u_long).
*
* sizeof(u_long)
* 您的电脑是16位的比特数:
* unsigned long的比特数是16,数的范围是0~(2的16方 -1);
* 您的电脑是32位的比特数:
* unsigned long的比特数是32,数的范围是0~(2的32方 -1);
*/
#define NEXT_SA(ap) ap = (SA *) \
((caddr_t) ap + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof (u_long)) : \
sizeof(u_long)))
/**
* 根据 addrs 的掩码将 sa 地址信息存储到 rti_info 中
*/
void
get_rtaddrs(int addrs, SA *sa, SA **rti_info)
{
int i;
for (i = 0; i < RTAX_MAX; i++) {
if (addrs & (1 << i)) {
rti_info[i] = sa;
NEXT_SA(sa);
} else
rti_info[i] = NULL;
}
}
【3.6】并发UDP服务器
参考
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。
【1】UNIX网络编程
【2】Linux高性能服务器编程
【3】select函数在TCP和UDP回射服务器中的应用
【4】信号处理函数的返回sigsetjmp/siglongjmp
【5】《TCP/IP详解卷2:实现》笔记--选路请求和选路消息
【6】sysctl 函数
【7】UNP学习笔记(第十八章 路由套接字)
【8】网卡接口相关定义