UDP程序设计

简介: UDP程序设计

无连接,不可靠的数据报协议 --> 简单,快捷

域名系统,简单网络管理协议(SNMP), 网络文件系统(NFS), 动态主机配置协议( DHCP), 实时传输协议RTP

服务器端:

  1. 创建socket
  2. bind到本地地址
  3. recvfrom, 接收数据并保存发送方的地址
  4. sendto 发送数据

没有连接建立,维护,终止所带来的开销

使用UDP的应用程序要自己负责数据的重传以及确认,解决可靠性相关的问题

socket

建立的socket用于之后的函数调用中标识套接口,为套接口分配了要使用的资源

SOCKET WSAAPI socket(int af,int type, int protocol);
  • return
    成功返回类型为SOCKET的描述符,是已经被创建的新的套接口
    失败返回INVALID_SOCKET, 可以调用WSAGetLastError() 得到具体的错误码
  • af地址族
    早期设想使用PF_*表示协议簇,而用AF_*表示地址簇, 实际上,直到现在支持多个地址簇的协议簇还没有出现
    在WinSock实现中,把所有的PF_*都定义成了AF_*对应的值
    常用的地址簇有AF_INET、AF_INET6、AF_LOCAL

套接字跨平台: 协议平台

  • type 协议类型
    功能的三要素: 服务 协议 功能
  • protocol 套接口使用的特定协议
    参数protocol与type是相关, type规定的是大的类别,而protocol是这一类中具体的协议
    当套接口类型只支持一个协议时,protocol可以设置为0,如SOCK_STREAM、SOCK_DGRAM

bind

bind函数给套接口指定一个本地地址( IP 地址 + Port)

函数bind既可以用于面向连接的socket,也可以用于无连接socket,它要在connect或listen前调用。

int WSAAPI bind(SOCKET s,const struct sockaddr FAR * name, int namelen);
  • return
    成功返回0, 失败返回SOCKET_ERROR, 可以调用WSAGetLastError得到具体的错误码
  • name指向sockaddr结构, 是一个通用的地址结构
  • 成员sa_family指明协议对应的地址簇 --> 不同的协议簇可以使用不同的地址格式

编程时使用与协议相关的地址结构,但在传递给socket接口函数时,需要把它转换成通用socket地址结构

  • Internet协议地址包括三部分:地址簇、主机地址和端口号 --> 地址簇sa_family为常量AF_INET
  • 不关心本地地址时,它可以把地址设置为常量INADDR_ANY
  • 如果没有指定端口,即端口值为0,TCP/IP协议将为应用程序分配一个唯一端口号
  1. 如果应用程序想知道系统为它分配的地址和端口,可以在调用bind后使用getsockname函数
  2. 在多宿主机上, 地址设置为INADDR_ANY,除非socket已经连接,调用了connect函数或者UDP发送了数据,系统才为socket选择一个本地地址,否则getsockname不一定能够得到本地地址,因为主机上有多个地址都是有效的。
  • 如果不调用bind,当调用connect或listen函数时,系统会为socket选择本地地址和临时端口 --> 客户端常见
  • IP地址: 服务器一般不明确指定,而是由系统帮助选择,根据目的地址为数据报选择一条路由
  • 客户端程序通常不关心本地地址和端口,可以不调用bind函数,本地地址和端口全由系统选择

sendto

sendto把数据发送到特定的目的地址 --> 常用于无连接socket(UDP)

int WSAAPI sendto(SOCKET s, const char FAR* buf,int len, int flags,const struct sockaddr FAR*to,int tolen);
  • return
    成功返回发送数据的字节长度(可能小于参数len要求发送的长度)
    失败返回SOCKET_ERROR, 可以调用 WSAGetLastError() 得到具体的错误码
  • s 套接口描述符
  • buf 要发送的数据
  • len 数据的长度
  • flags 规定了调用的方式
  • to 发送的目的地址
    参数to规定数据报要发送到的目的地址
    参数to可以是任何有效的地址,包括广播或者多播

为了向广播地址发送数据,应用程序必须调用setsockopt(SO_BROADCAST)开启广播功能,广播地址由宏INADDR_BROADCAST定义,to中的IP地址要设置为该值,发送到广播地址的数据报不建议分片,数据的部分最好不要超过512字节

  • tolen 地址to 的长度

说明:

  1. 调用sendto时,如果socket还没有绑定,系统将为socket分配一个本地地址,并把socket标识为已经绑定,应用程序可以调用getsockname得到绑定的本地地址
  2. 对于数据报socket,一次可以发送的数据量受底层网络最大分组大小的限制,可以调用getsockopt(SO_MAX_MSG_SIZE)得到
    如果程序发送的数据长度超过了底层协议的限制,则sendto返回WSAEMSGSIZE,并且不会发送数据。
  3. sendto成功并不表示数据已经发送到网络上,只意味着系统已经接受了应用程序的数据,并保存到socket的缓冲区队列中,数据什么时候发送出去,由系统决定,应用程序并不知道。
  4. 如果传输协议没有空间保存应用程序的数据,除非用户已经把socket设置为非阻塞模式,否则将阻塞,直到有空间保存用户的数据。
  5. 发送一个长度为0的数据也是合理的,sendto将返回0,对于数据报socket,它将传送一个0长度的数据报,只包含协议首部,没有用户数据。
  6. sendto也可用于面向连接的socket,这种情况,to和tolen将被忽略,sendto等价于send。
recvfrom

接收数据并得到数据的源地址,通常用于无连接socket,并且已经绑定了本地地址

int WSAAPI recvfrom(SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR* from,int FAR * fromlen);
  • return
    调用成功返回接收的字节数
    返回0时,对于面向连接的socket表示对方正常关闭了连接,对于无连接socket,表示接收到了对方发送的0长度的数据报
    发生错误时返回SOCKET_ERROR
  • s : 标识了一个socket
  • buf 接收数据的缓冲区
  • len buf的长度
  • flag 规定了调用的方式
    影响recvfrom函数的行为, 可以是0或者下表中一个或者多个常量值的逻辑或

  • from : 可选,成功时返回源地址
  • fromlen : from地址缓冲区的长度
    当from非空时,发出数据的主机地址被复制到from中,fromlen是复制到from中地址的长度

说明:

  1. 无连接socket接收数据时常用recvfrom,把输入队列中的第一个数据报复制到buf中,如果数据报的长度比缓冲区大,将只把数据报前面len字节的数据复制到buf中,多余的数据会丢失,recvfrom产生错误码WSAEMSGSIZE
  2. 返回0, 表示接收到了一个0长度的数据报,因为UDP是无连接的,没有关闭连接操作。
  3. 面向连接socket,如类型为SOCK_STREAM,接收数据通常使用recv,但也可以使用recvfrom,除了最后两个参数被忽略外,它与recv功能是一样的。
    面向连接,当协议中的数据大于recvfrom提供的缓冲区长度时,系统将len字节的数据复制到buf中,其他的数据仍然保留在队列缓冲区中,不会像无连接socket一样把超过的数据抛弃。
  4. 调用recvfrom时,如果系统中没有数据
  • 在阻塞模式下,recvfrom一直等待,直到接收到了数据或发生了错误
  • 非阻塞模式下,会返回错误SOCKET_ERROR,调用WSAGetLastError得到的错误码是WSAEWOULDBLOCK。
    可以用select或WSAAsyncSelect确定什么时候数据到达
closesocket

关闭socket,释放占用的资源

int WSAAPI closesocket(SOCKET s);
  • return
    成功返回0
    发送错误时返回SOCKET_ERROR
  • s 套接口描述符,标识了要关闭的socket

说明:

  1. 程序成功调用socket后,应该总是调用closesocket,释放地址信息,抛弃排队的数据,取消正在进行的阻塞操作或异步调用,应用程序将不会收到通知消息或事件信号
  2. 关闭之后,不能再使用这个套接口描述符,也不能再把它作为send、recv等函数的参数,错误码是WSAENOTSOCK
  3. 对于使用UDP协议的socket,没有连接,关闭操作非常简单,只是释放本地资源,并立即返回
  4. 使用TCP协议的socket,通信双方要维护连接,关闭操作受选项SO_LINGER的影响,情况比较复杂应用程序想控制关闭的行为,需要创建一个struct linger结构
struct linger{
    u_short l_onoff; // 打开或者关闭选项
    u_short l_linger; // 延迟时间,单位s
}
  • SO_LINGER起作用,要调用setsockopt函数,把l_onoff设置为非零值,并且l_linger为0或者是期望的以秒为单位的超时值
  • 要让SO_DONTLINGER生效,结构中的l_onoff应该为0
  • SO_LINGER和SO_DONTLINGER是互斥的,一个生效后,另一个自然就无效了

  • SO_DONTLINGER是socket的默认行为
    这种情况调用closesocket会立即返回,如果队列中还有尚未发送的数据,底层协议不会立即关闭socket,而是先发送数据,数据发送完成后转换到关闭状态,双方都同意关闭后,两端才真正关闭socket,这被称作“优美关闭”。
    造成的影响是底层协议在一段时间内并不关闭socket,释放相关的资源,因此应用程序在这段时间内也不能使用这个socket。
Daytime程序

Daytime是互联网上的一个标准协议,在RFC867中有详细的描述

Daytime服务器只是简单地把当前的日期和时间作为一个字符串发送给客户端,它既可以使用TCP,也可以使用UDP协议,知名端口号是13。

客户端

UDP协议是不可靠的,发送的数据可能丢失,为了尽量避免这种情况,最好发送多次

setsockopt(SO_RCVTIMEO)设置接收数据的超时值 --> 防止客户端一直阻塞在recvfrom函数

设置接收的超时选项,在指定的时间内收不到数据时,recvfrom函数会返回SOCKET_ERROR,错误码为WSAETIMEDOUT

#include <stdio.h>
#include <winsock2.h>
#define DAYTIME_DEF_PORT    13  /* Daytime默认端口 */
#define DAYTIME_BUF_SIZE    64  /*缓冲区的大小 */
#define DAYTIME_DEF_COUNT    2  /* 发送的次数 */
int main(int argc, char **argv)
{
  WSADATA wsaData;
  /* 初始化WinSock资源 */
  WSAStartup(WINSOCK_VERSION, &wsaData); 
  /* Daytime socket句柄 */
  SOCKET  time_soc = 0;  
  struct sockaddr_in peer_addr, serv_addr;
  /* 接收超时, 2秒 */
  int timeout = 2000; 
  int i, result, send_len, addr_len = sizeof(serv_addr);
  char *dest = "127.0.0.1", *send_data = "Hello, Daytime!";
    char recv_buf[DAYTIME_BUF_SIZE];
  /* 服务器地址 */
  if (argc == 2) 
    dest = argv[1];
  send_len = strlen(send_data);
  /* 服务器地址 */
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(DAYTIME_DEF_PORT);
  serv_addr.sin_addr.s_addr = inet_addr(dest);
  if (serv_addr.sin_addr.s_addr == INADDR_NONE)
  {
     printf("[Daytime] invalid address\r\n");
     return -1;
  };
  /* 创建Daytime使用的socket */
  time_soc = socket(AF_INET, SOCK_DGRAM, 0);
  result = setsockopt(time_soc, SOL_SOCKET, SO_RCVTIMEO,(char*)&timeout, sizeof(timeout));
  for (i = 0; i < DAYTIME_DEF_COUNT; i++)
  {
      result = sendto(time_soc, send_data, send_len, 0,
              (struct sockaddr *)&serv_addr, sizeof(serv_addr));
      result = recvfrom(time_soc, recv_buf, DAYTIME_BUF_SIZE, 0,
              (struct sockaddr *)&peer_addr, &addr_len);
      if (result >= 0)
      {
          recv_buf[result] = 0;
          printf("[Daytime] recv: \"%s\", from %s\r\n",
                  recv_buf, inet_ntoa(peer_addr.sin_addr));
      }
  }
  closesocket(time_soc);
  WSACleanup();
  return 0;
}
服务器端

函数recvfrom的返回值大于等于0,表示收到了对方的数据

#include <stdio.h>
#include <time.h>
#include <winsock2.h>
#define DAYTIME_DEF_PORT    13     /* Daytime默认端口 */
#define DAYTIME_BUF_SIZE    64     /* 缓冲区的大小 */
int main(int   argc, char **argv)
{
  WSADATA wsaData;
  /* 初始化WinSock资源 */
  WSAStartup(WINSOCK_VERSION, &wsaData);
    /* 服务器的socket句柄 */
    SOCKET srv_sock = 0; 
    /* Daytime服务器地址 */
  struct sockaddr_in srv_addr, clnt_addr; /* 客户端地址 */      
  unsigned short port = DAYTIME_DEF_PORT;
    int  result, addr_len = sizeof(srv_addr);
    char *time_str, recv_buf[DAYTIME_BUF_SIZE];
    time_t now_time;
    if (argc == 2) /* 端口 */
       port = atoi(argv[1]);
    /* 创建Daytime使用的socket */
    srv_sock = socket(AF_INET, SOCK_DGRAM, 0);
    /* Daytime服务器地址 */
  srv_addr.sin_family = AF_INET;
  srv_addr.sin_port = htons(port);
    srv_addr.sin_addr.s_addr = INADDR_ANY;
    result = bind(srv_sock, (struct sockaddr *)&srv_addr, addr_len);
    if (result == SOCKET_ERROR)
    {
        printf("[Daytime] bind error : %d", WSAGetLastError());
        closesocket(srv_sock);
        return -1;
    }
    printf("[Daytime] The server is running ... ...\r\n");
  while (1)
  {
        result = recvfrom(srv_sock, recv_buf, DAYTIME_BUF_SIZE, 0,
                          (struct sockaddr *)&clnt_addr, &addr_len);
        if (result >= 0)
        {
            recv_buf[result] = 0;
            printf("[Daytime] recv: \"%s\", from %s\r\n",
                   recv_buf, inet_ntoa(clnt_addr.sin_addr));
            now_time = time(0); /* 得到当前时间 */
            time_str = ctime(&now_time);
            /* 向客户端发送当前的日期和时间字符串 */
            sendto(srv_sock, time_str, strlen(time_str), 0,
                  (struct sockaddr *)&clnt_addr, addr_len);
       }
    }
    closesocket(srv_sock);
    WSACleanup();
    return 0;
}
说明

Devc++ 添加winsock链接库

相关文章
|
网络协议 Linux 程序员
Linux下的UDP通信程序设计
任务 设计一个基于UDP的文件下载工具,client可从server处下载其路径下的文件 编码前的前置背景知识 实现两端上传下载的步骤 server端 接收将被下载的文件名称,并将该名称发送给client
281 0
|
3月前
|
存储 网络协议 算法
UDP 协议和 TCP 协议
本文介绍了UDP和TCP协议的基本结构与特性。UDP协议具有简单的报文结构,包括报头和载荷,报头由源端口、目的端口、报文长度和校验和组成。UDP使用CRC校验和来检测传输错误。相比之下,TCP协议提供更可靠的传输服务,其结构复杂,包含序列号、确认序号和标志位等字段。TCP通过确认应答和超时重传来保证数据传输的可靠性,并采用三次握手建立连接,四次挥手断开连接,确保通信的稳定性和完整性。
93 1
UDP 协议和 TCP 协议
|
4月前
|
消息中间件 网络协议 算法
UDP 和 TCP 哪个更好?
【8月更文挑战第23天】
218 0
|
1月前
|
网络协议 算法 网络性能优化
|
21天前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
29 4
|
27天前
|
缓存 负载均衡 网络协议
面试:TCP、UDP如何解决丢包问题
TCP、UDP如何解决丢包问题。TCP:基于数据块传输/数据分片、对失序数据包重新排序以及去重、流量控制(滑动窗口)、拥塞控制、自主重传ARQ;UDP:程序执行后马上开始监听、控制报文大小、每个分割块的长度小于MTU
|
2月前
|
网络协议 前端开发 物联网
TCP和UDP区别?
本文首发于微信公众号“前端徐徐”,详细介绍了TCP和UDP两种传输层协议的核心概念、连接性和握手过程、数据传输和可靠性、延迟和效率、应用场景及头部开销。TCP面向连接、可靠、有序,适用于网页浏览、文件传输等;UDP无连接、低延迟、高效,适用于实时音视频传输、在线游戏等。
50 1
TCP和UDP区别?
|
2月前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
54 10
|
2月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
40 1
|
2月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
40 0
Linux C/C++之TCP / UDP通信