【Linux网络编程】UDP 套接字编程

【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 <IPaddress>");/*** 初始化服务器地址结构信息*/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);}
}

示例程序说明:

  • 数据报丢失

    • 若一个客户端数据报丢失,客户将阻塞于dg_cli函数中的recvfrom调用一直等待一个永远不会到达的服务器应答;
    • 若客户端数据报到达服务器,但服务器的应答丢失,客户也将永远阻塞于recvfrom调用;
    • 解决方案:recvfrom调用设置超时;
  • 服务器进程未运行
    • 客户将阻塞于recvfrom调用,等待一个永不会出现的服务器应答;
    • 客户主机能够往服务器发送UDP数据报之前,需要一次ARP请求和应答的交换;
    • 由于服务器进程关闭,服务器返回端口不可达ICMP消息,称为ICMP异步错误;
    • 基本规则:对于一个UDP套接字,由其引发的异步错误却并不返回给它,除非该UDP套接字已连接;
    • 解决方法:仅在进程已将其UDP套接字连接到恰恰一个对端后,才会返回异步错误;

【2.3】UDP的connect函数

UDP套接字调用connect,内核只是检查是否存在立即可知的错误,记录对端IP地址和端口号,然后立即返回到调用进程;

  • 不用指定输出操作的IP地址和对端端口号,不能使用sendto,而改用write和send,写到已连接套接字对应的任何内容将会自动发送到connect的套接字上;
  • 不必使用recvfrom以获取数据报的发报者,而改用read,recv或recvmsg,在一个已连接的套接字上,内核的输入操作返回的数据报将会只来自connect指定的协议地址的数据报;
  • 已连接的UDP套接字中发生的ICMP错误将会返回给进程,而未连接的ICMP错误将不会返回的进程;

TCP,未连接UDP,已连接UDP套接字比较

套接字类型 write或send 不指定目的地址的sendto 指定目的地址的sendto
TCP套接字 可以 可以 EISCONN
UDP套接字,已连接 可以 可以 EISCONN
UDP套接字,未连接 EDESTADDRREQ EDESTADDRREQ 可以

使用场合:UDP客户进程或服务器进程在使用自己的UDP套接字与确定的唯一对端进行通信的场合;

一个UDP套接字多次调用connect:1. 指定新的IP地址和端口号;2. 断开套接字;

【2.4】UDP套接字编程注意事项

  • UDP缺乏流量控制,容易产生数据报的丢失

    • 解决方案:
    • 使用SO_RCVBUF套接字选项修改套接字接收缓冲区大小,该方案只能缓解UDP缺乏流量控制而导致的问题;
  • UDP中的外出接口的确定
    • 对于已连接的UDP套接字可以用于确定某个特定目的地的外出接口;
    • 在UDP套接字上调用connect并不会给对端主机发送任何信息,只是保存了对端的IP地址和端口号,完全属于本地操作;

【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() */elseerr_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    <sys/param.h>     /* 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_CONTROLstruct 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// 初始化 msgbzero(&msg, sizeof(msg));    /* make certain msg_accrightslen = 0 */
#endif// 初始化 msgmsg.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_RECVDSTADDRif (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_RECVIFif (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;}
#endiferr_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】数据报截断

  • 处理方式

    • 丢弃超出部分的字节并向应用程序返回MSG_TRUNC标志;
    • 丢弃超出部分的字节但不告知应用进程;
    • 保留超出部分的字节并在同一套接字上后续的读操作中返回超出部分的字节;

【3.3】UDP代替TCP的时机

  • UDP的优势

    • UDP支持广播与多播;
    • UDP没有连接建立和拆除;
  • 相关概念,最小事务处理时间 = RTT(客户与服务器之间往返时间) + SPT(客户请求的服务器处理时间);
  • UDP不具备的TCP特性
    • 正面确认,丢失分组重传,重复分组检测,给被网络打乱次序的分组排序;
    • 窗口式流量控制;
    • 慢启动和拥塞避免;
  • 相关建议
    • 对于广播与多播应用程序必须使用UDP;
    • 对于简单的请求-应答应用程序可以使用UDP,但需要在应用程序中添加错误检测功能;
    • 对于海量数据传输不应该使用UDP;

【3.4】给UDP应用添加可靠性

  • 请求-应答式应用程序中使用UDP添加的特性

    • 序列号:提供客户验证一个应答是否匹配相应的请求;

      • 序列号实现方式:客户为每个请求冠以一个序列号,服务器必须在返送给客户的应答中回射该序列号;
    • 超时和重传:用于处理丢失的数据报;
      • 超时和重传实现方式
      • 影响往返时间(RTT)的因素:距离、网络速度、拥塞;
      • 方法1:计算RTO,当重传超时期满时,必须对下一个RTO应用某个指数回退,需要解决重传二义性问题;
      • 方法2:对于每个请求,服务器必须回复一个序列号以及时间戳,当客户端收到一个应答时,从当前时间减去服务器在应答中回复的时间戳从而得到RTT;

计算公式

示例代码分析

#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    <setjmp.h>#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 <sys/uio.h>* 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_DEBUGfprintf(stderr, "send %4d: ", sendhdr.seq);
#endif// 更新 rtt_basesendhdr.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) {// 判断是否发生了 timeoutif (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");
#endifgoto sendagain;}do {n = Recvmsg(fd, &msgrecv, 0);
#ifdef  RTT_DEBUGfprintf(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】绑定接口地址

  • get_ifi_info函数用途:监视本地主机所有接口以便获悉某个数据报在何时以及哪个端口到达UDP应用程序;
  • 可通过get_ifi_info函数获取UDP数据报的目的地址;

示例程序分析

#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;} elseerr_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 <net/if.h>*     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]);elsesnprintf(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);}} elseerr_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<sys/param.h>* #include<sys/sysctl.h>* 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);} elserti_info[i] = NULL;}
}

【3.6】并发UDP服务器

  • 情形一:服务器读入客户请求并发送一个应答之后,与该客户便不再相关了;

    • 解决方式:服务器创建子进程处理请求,当子进程处理完毕后,将应答直接发送给客户端;
    • 处理示意图

  • 情形二:UDP服务器与客户交换多个数据报;

    • 待解决的问题:如何区分接收到的数据报是来自该客户同一个请求的后续数据报还是来自其他客户请求的数据报;
    • 解决方式:服务器为每个用户创建新的套接字,在其上绑定临时端口,然后使用该套接字发送对该客户的所有应答;客户端解析接收到的第一个应答中的源端口号,并将请求的后续数据报发送到该端口;
    • 处理示意图

参考
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。

【1】UNIX网络编程

【2】Linux高性能服务器编程

【3】select函数在TCP和UDP回射服务器中的应用

【4】信号处理函数的返回sigsetjmp/siglongjmp

【5】《TCP/IP详解卷2:实现》笔记--选路请求和选路消息

【6】sysctl 函数

【7】UNP学习笔记(第十八章 路由套接字)

【8】网卡接口相关定义

【Linux网络编程】UDP 套接字编程相关推荐

  1. Linux网络编程——原始套接字编程

    Linux网络编程--原始套接字编程 转自:http://blog.csdn.net/tennysonsky/article/details/44676377 原始套接字编程和之前的 UDP 编程差不 ...

  2. 网络编程---TCP/UDP套接字编程原理

    本篇介绍的是Linux下的网络编程,故有些接口是不适用于Windows的,但是具体概念和实现方法是大体一致的 本篇重在讲解原理,具体实现请戳这里->UDP套接字编程实现 介绍 网络编程套接字(s ...

  3. Linux IPv6 UDP套接字编程示例

    udp ipv6套接字编程和ipv4接口类似,参数略有不同,流程都包括创建套接字.绑定地址.发送等. 下面是一个udp ipv6 demo, 包括创建ipv6套接字.绑定地址和发送数据等. 首先先在l ...

  4. linux udp套接字编程获取报文源地址和源端口(二)

    之前项目中涉及udp套接字编程,其中一个要求是获取客户端发过来报文的端口和ip地址,功能很简单,只是对这一块不很熟.之前使用的方法是通过调用recvmsg这个接口,并通过参数msg里面的msg_nam ...

  5. 网络基础:套接字编程,UDP和TCP通信程序

    文章目录 字节序 套接字编程 客户端与服务端 ★ netstat命令 ★ UDP通信程序 通信流程: 接口: 流程外的重要接口: 服务端代码 客户端代码 TCP通信程序 通信流程: 接口: 代码实例: ...

  6. 《网络编程》基本 UDP 套接字编程

    在前面文章中介绍了<UDP 协议>和<套接字数据传输>.UDP 协议和 TCP 协议不同,它是一种面向无连接.不可靠的传输层协议.在基于 UDP 套接字编程中,数据传输可用函数 ...

  7. TCP和UDP套接字编程

    一.Socket简单介绍 如果要在应用层调用传输层的服务,进行相关程序的设计,就要涉及到套接字编程.套接字也称之为Socket,本质上它就是利用传输层提供的一系列Api来进行网络应用程序的设计. 网络 ...

  8. UDP套接字编程——Python语言描述

    首先,回顾一下.在我们使用Socket编程之前的一些网络的概念. IP:它是用来标识处于Internet之中的端系统的. MAC:它是用于在同一局域网中标识不同的计算机的. 端口号:它是用来标识同一台 ...

  9. 计网实验原理-TCP/UDP套接字编程

    计算机网络自顶向下结构--第7版 第二章实验,套接字编程 代码运行环境:window10,python 3.8.对于书上代码略作修改. 进程与计算机网络之间的接口 多数应用程序是由通信进程队组成的,每 ...

最新文章

  1. ITK:翻译矢量图像
  2. Android日期对话框NumberPicker的用法教程
  3. shell unset之后数组元素个数为_PHP删除数组中指定值的元素的方法
  4. 【转】excel表格导出集锦repeater实用,和普通用法
  5. 锁屏界面显示某些设置已隐藏_iOS 14 隐藏功能,只要轻点手机背面就能截屏
  6. 用Notepad++来编写第一个HTML网页程序,你也可以!!!
  7. 关闭tcp服务器_2020年2月起,不支持TCP和UDP协议的DNS服务器将被强制关闭
  8. hello ,test livewriter
  9. 【调参】Cyclic Learning Rates和One Cycle Policy-Keras
  10. 正则判断手机号地区_国内外手机号码正则表达式汇总
  11. 关于形而上学与形而下学之区别及关系
  12. iOS 免越狱修改微信运动步数
  13. 脉搏波形分析_国家脉搏2020年美国总统大选的推特分析
  14. Heavy Transportation - dijkstra
  15. 软件架构入门及分类——微内核架构
  16. 基于easyTrader部署自动化交易(一)
  17. ios设备home键 锁屏键区分
  18. iphone11文件连接服务器教程,新入手iPhone 11?手把手教你快速上手熟悉IOS系统
  19. lcg_magic算法笔记:插入排序
  20. JavaScript 基础1入门、变量、运算符、表达式、进制

热门文章

  1. 网络——传输层(详细版)
  2. 双路微型计算机,十二核心至尊 Intel Xeon E5-2697 v2
  3. 自动写诗APP项目、基于python+Android实现(技术:LSTM+Fasttext分类+word2vec+Flask+mysql)第三节
  4. 2000-2013年工企专利匹配数据库
  5. Infiniband基本知识
  6. 从《战狼2》到Oracle数据库,这中间有几个云的距离?
  7. preg_match 函数使用及正则表达式各种示例
  8. 猿创征文|计算机学生必须掌握的学习工具
  9. 苹果一体机卸载原装OSX系统,只安装win10系统操作过程2019-11
  10. 电子漫画系列更新4张震撼美图,共计62张,迫不及待想分享给你们!