在前面文章中介绍了《UDP 协议》和《套接字数据传输》。UDP 协议和 TCP 协议不同,它是一种面向无连接、不可靠的传输层协议。在基于 UDP 套接字编程中,数据传输可用函数 sendto 和 recvfrom。以下是基本 UDP 套接字编程过程:

sendto 与 recvfrom 函数

这两个函数的功能类似于 write 和 read 函数,可用无连接的套接字编程。其定义如下:

/* 函数功能:发送数据;* 返回值:若成功则返回已发送的字节数,若出错则返回-1;* 函数原型:*/
#include <sys/socket.h>ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags,const struct sockaddr *destaddr, socklen_t addrlen);/* 说明:* 该函数功能类似于write函数,除了有标识符flags和目的地址信息之外,其他参数一样;** flags标识符取值如下:* (1)MSG_DONTROUTE   勿将数据路由出本地网络* (2)MSG_DONTWAIT    允许非阻塞操作* (3)MSG_EOR         如果协议支持,此为记录结束* (4)MSG_OOB         如果协议支持,发送带外数据** 若sendto成功,则只是表示已将数据无错误的发送到网络,并不能保证正确到达对端;* 该函数通过指定目标地址允许在无连接的套接字之间发送数据(例如UDP套接字);*//* 函数功能:接收数据;* 返回值:以字节计数的消息长度,若无可用消息或对方已经按序结束则返回0,若出错则返回-1;* 函数原型:*/
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,struct sockaddr *addr, socklen_t *addrlen);/* 说明:* 该函数功能与read类似;* 若addr为非空时,它将包含数据发送者的套接字地址;** flags标识符取值如下:* (1)MSG_WAITALL     等待所有数据可用* (2)MSG_DONTWAIT    允许非阻塞操作* (3)MSG_PEEK        查看已读取的数据* (4)MSG_OOB         如果协议支持,发送带外数据*/

基于 UDP 套接字编程

下面我们使用 UDP 协议实现简单的功能,客户端从标准输入读取数据并把它发送给服务器,服务器接收到数据并把该数据回射给客户端,然后客户端收到从服务器回射的数据把它显示到标准输出。其功能实现如下图所示:

服务器程序

/* UDP 服务器 */
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>#define SERV_PORT 9877 /* 通用端口号 */extern void err_sys(const char *, ...);
extern void dg_echo(int sockfd, struct sockaddr *addr, socklen_t addrlen);int main(int argc, char **argv)
{int sockfd;int err;struct sockaddr_in servaddr, cliaddr;/* 初始化服务器地址信息 */bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERV_PORT);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);/* 创建套接字,并将服务器地址绑定到该套接字上 */if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)err_sys("socket error");err =bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));if(err < 0)err_sys("bind error");/* 服务器处理函数:读取套接字文本行,并把它回射给客户端 */dg_echo(sockfd, (struct sockaddr*) &cliaddr, sizeof(cliaddr));}

处理函数

#include "unp.h"void
dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{int            n;socklen_t len;char        mesg[MAXLINE];for ( ; ; ) {len = clilen;n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);Sendto(sockfd, mesg, n, 0, pcliaddr, len);}
}

客户端程序

/* UDP 客户端 */
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define SERV_PORT 9877 /* 通用端口号 */extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void dg_cli(FILE *fd, int sockfd, struct sockaddr *addr, socklen_t addrlen);int main(int argc, char **argv)
{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);if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)err_sys("socket err");
/* 客户端处理函数:从标准输入读入文本行,发送给服务器;接收来自服务器的回射文本,并把它显示到标准输出 */dg_cli(stdin, sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));exit(0);
}

客户端处理函数

#include "unp.h"void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{int    n;char  sendline[MAXLINE], recvline[MAXLINE + 1];while (Fgets(sendline, MAXLINE, fp) != NULL) {
/* 把从标准输入读取的文本行发送给服务器套接字 */Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
/* 接收来自服务器回射的文本行 */n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);recvline[n] = 0;    /* null terminate */Fputs(recvline, stdout);}
}
 $./serv &
[1] 17911
$ ./client 127.0.0.1
sending text based on UDP
sending text based on UDP
goodbyte..
goodbyte..

数据报丢失

由于 UDP 是一种不可靠的传输协议。在上面的客户端 / 服务器 程序中,若数据报在传输的过程中丢失,那么客户端就是阻塞于 dg_cli 处理函数中的 recvfrom 函数调用,等待一个永远都不会达到的服务器应答。也有可能是,客户端数据报成功到达服务器,但是服务器的应答数据报丢失,同样,客户端也将永远阻塞于  recvfrom 函数调用。一般来说,会给客户端  recvfrom 函数调用设置一个超时时钟,但是超时时钟并不能确定是客户端数据报不能到达服务器还是服务器应答不能到达客户端。所以我们可以采用验证接收到的响应。即在  recvfrom 函数调用以返回数据报发送者的 IP 地址和端口号,保留来自数据报所发往服务器的应答。

UDP 中使用 connect 函数

在没有启动 UDP 服务器的情况下,客户端键入文本行之后,并不会回显该文本行。此时客户端永远阻塞于它的 recvfrom 调用,等待一个永远不会出现的服务器应答。由于服务器没有启动,因此会响应一个端口不可到达的 ICMP 错误消息(即异步错误),但是该 ICMP 错误消息并不会到达客户端进程,因此客户端进程根本不知道发生什么,一直阻塞于它的 recvfrom 调用。为了能使这个异步错误到达客户端进程,我们可以在 UDP 中调用 connect 函数,使其成为一个已连接的 UDP 套接字,但是该链接不会像 TCP 那样引起三次握手过程。内核只是检查是否存在立即可知的错误,并记录对端的 IP 地址和端口号,然后立即返回到调用进程。

下面要区分 未连接 UDP 套接字 和 已连接 UDP 套接字:

  1. 未连接 UDP 套接字:新创建 UDP 套接字默认为该情况;
  2. 已连接 UDP 套接字:对 UDP 套接字调用 connect 函数的结果;

已连接 UDP 套接字 相对于 未连接 UDP 套接字 会有以下的变化:

  1. 不能给输出操作指定目的 IP 地址和端口号(因为调用 connect 函数时已经指定),即不能使用 sendto 函数,而是使用 write 或 send 函数。写到已连接 UDP 套接字上的内容都会自动发送到由 connect 指定的协议地址;
  2. 不必使用 recvfrom 函数以获悉数据报的发送者,而改用 read、recv 或 recvmsg 函数。在一个已连接 UDP 套接字上,由内核为输入操作返回的数据报只有那些来自 connect 函数所指定的协议地址的数据报。目的地为这个已连接 UDP 套接字的本地协议地址,发源地不是该套接字早先 connect 到的协议地址的数据报,不会投递到该套接字。即只有发源地的协议地址与 connect 所指定的地址相匹配才可以把数据报传输到该套接字。这样已连接 UDP 套接字只能与一个对端交换数据报;
  3. 由已连接 UDP 套接字引发的异步错误会返回给它们所在的进程,而未连接 UDP 套接字不会接收任何异步错误;

UDP 客户端进程或服务器进程只在使用自己的 UDP 套接字与确定的唯一对端通信时,才可以调用 connect 函数。调用 connect 函数的通常是 UDP 客户端。以下是调用 connect 函数的客户端处理函数:

#include "unp.h"void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{int        n;char  sendline[MAXLINE], recvline[MAXLINE + 1];Connect(sockfd, (SA *) pservaddr, servlen);while (Fgets(sendline, MAXLINE, fp) != NULL) {Write(sockfd, sendline, strlen(sendline));n = Read(sockfd, recvline, MAXLINE);recvline[n] = 0;    /* null terminate */Fputs(recvline, stdout);}
}

此时若不启动服务器,只启动客户端,并键入文本行时,客户端会接收到 异步错误。

$ ./client 127.0.0.1
message...
read error: Connection refused

参考资料:

《Unix 网络编程》

《网络编程》基本 UDP 套接字编程相关推荐

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

    [Linux网络编程]UDP 套接字编程 [1]用户数据报协议(UDP) UDP是一个简单的传输层协议,不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数 ...

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

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

  3. TCP和UDP套接字编程

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

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

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

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

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

  6. 计算机网络实验二:UDP套接字编程实现多人聊天

    一.实验目的 1. 实现一个能够在局域网中进行点对点聊天的实用程序. 2. 熟悉c++.Java等高级编程语言网络编程的基本操作. 3. 基本了解对话框应用程序的编写过程. 4. 实现UDP套接字编程 ...

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

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

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

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

  9. JAVA UDP套接字编程

    JAVA UDP套接字编程 UDP套接字 无连接 非可靠传输 面向数据报 package com.lius.udp;import java.io.IOException; import java.ne ...

最新文章

  1. 操作SQLite数据库
  2. 把Array说透(续一)
  3. cad新手必练300图_[CAD]平面练习图,CAD新手练技术练速度的好去处
  4. 根据mysql数据库日志恢复删除数据
  5. 日志 查看匹配内容的前后几行
  6. C#程序只允许运行一个实例的解决方案
  7. 线性代数思维导图_线性代数入门级思维导图
  8. CodeForces - 1360G A/B Matrix(最大流)
  9. php日期相减函数,倒计时函数_计算两个时间相差值_PHP函数
  10. python 数组和列表的区别
  11. 互联网日报 | 滴滴全球日订单首次突破5000万;抖音直播间将不支持第三方来源商品;拼多多“开学季”上线...
  12. 如何判断微信定位服务器,如何快速找到自己的微信定位,完成实操作业?分享一下我的思路...
  13. 在windows下如何批量转换pvr,ccz为png或jpg
  14. 太湖2018年渔业产值达7.3亿元 今年大闸蟹产量将降低
  15. 查杀病毒实战----------------》ddg.223 and AnXQV
  16. 计算机文档翻页怎么设置,PDF文档翻页设置
  17. 浅谈 Facade 模式
  18. 基于MATLAB的条形码识别系统
  19. 微服务架构——马丁弗勒
  20. 文竹越长越乱?教你7种修剪方法可保持文竹株形优美,矮壮浓密

热门文章

  1. 五子棋游戏html5界面设计,HTML5制作黑白五子棋游戏教程
  2. Cesium 地图分级分片显示
  3. “360安全卫士优化后,输入法图标丢失”解决方法
  4. node项目实战-用node-koa2-mysql-bootstrap搭建一个前端论坛
  5. PS和CDR之间的主要区别是什么?
  6. CSS3图像和背景的学习
  7. Axure PR9左侧页面概要元件母版不见了,在哪开启
  8. 企业安全建设之API网关kong的搭建
  9. 无线网络原理知识总结
  10. 100首好听的英文歌