`

UDP套接字编程基础

阅读更多
    下图显示了使用 UDP 套接字编写客户/服务器程序时的大致流程。

    UDP 中的客户不需要与服务器建立连接,而是采用 sendto 和 recvfrom 函数来发送和接收数据。
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
               const struct sockaddr *to, socklen_t addrlen);
                    /* 两个函数的返回值:若成功则为读或写的字节数;否则为 -1 */

    这两个函数的前三个参数等同于 read 和 write 的三个参数:描述符、缓冲区和读写字节数,flag 参数将在后面讨论 recv、send 等函数时再介绍,目前先将其置为 0。
    sendto 的 to 参数代表数据报接收者的地址,其大小由 addrlen 参数指定。recvfrom 的 from 参数代表数据报发送者的地址,其大小被放置在 addrlen 参数指向的位置中(注意,如果 from 是空指针,则 addrlen 也必须是一个空指针,表示不关心发送者。这样做存在一个风险:知道客户地址和临时端口号的任何进程都可以冒充服务器的应答向本客户发送数据报)。这两个函数的最后两个参数分别类似于 connect 和 accept 的最后两个参数。尽管这两个函数也可以用于 TCP,不过通常没必要。
    在 UDP 中,写一个长度为 0 的数据报会形成一个只包含 IP 首部(IPv4 通常为 20 个字节,IPv6 通常为 40 个字节)和一个 8 字节的 UDP 首部而没有数据的 IP 数据报。这也意味着 recvfrom 返回 0 值是可接受的,而并不像 TCP 套接字上 read 返回 0 值那样表示对端已关闭连接。
    下面是使用 UDP 套接字编写的简单回射服务器示例(省略了对函数返回值的检测)。
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define SERV_PORT	9877
typedef struct sockaddr SA;

void dgram_echo(int);

int main(void){
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	bind(sockfd, (SA *)&servaddr, sizeof(servaddr));

	dgram_echo(sockfd);
}

#define MAXLINE	2048

void dgram_echo(int sockfd){
	struct sockaddr_in	cliaddr;
	socklen_t addrlen = sizeof(cliaddr);
	char msg[MAXLINE];
	for(;;){
		socklen_t len = addrlen;
		int n = recvfrom(sockfd, msg, MAXLINE, 0, (SA *)&cliaddr, &len);
		sendto(sockfd, msg, n, 0, (SA *)&cliaddr, len);
	}
}

    由于无需连接,所以大多数 UDP 服务器提供的都是一个迭代服务器,而不是像 TCP 服务器那样需要提供一个并发服务器。
    每个 UDP 套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个缓冲区。当进程调用 recvfrom 时,缓冲区中的下一个数据报就以 FIFO 的顺序递送给进程。
    接下来也给出了客户端的代码示例。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERV_PORT	9877
#define MAXLINE 2048
typedef struct sockaddr	SA;

void dgram_cli(FILE *fp, int sockfd, const SA *paddr, socklen_t addrlen){
	char buf[MAXLINE+1];
	struct sockaddr *preply_addr = malloc(addrlen);
	socklen_t len = addrlen;
	while(fgets(buf, MAXLINE, fp) != NULL){
		sendto(sockfd, buf, strlen(buf), 0, paddr, addrlen);
		int n = recvfrom(sockfd, buf, MAXLINE, 0, prepy_addr, &len);
		if(len != addrlen || memcmp(paddr, preply_addr, len) != 0)
			continue;    // 过滤可能来自非请求服务器的数据
		buf[n] = 0;		// null terminate
		fputs(buf, stdout);
	}
	free(preply_addr);
}

int main(int argc, char *argv[]){
	if(argc != 2){
		printf("Usage: %s <IPAddress>\n", argv[0]);
		exit(1);
	}
	struct sockaddr_in	servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	dgram_cli(stdin, sockfd, (SA *)&servaddr, sizeof(servaddr));
	exit(0);
}

    要注意的是,这里的 UDP 客户/服务器例子是不可靠的。因为如果一个客户数据报或者服务器的应答丢失了,客户都将永远阻塞于 recvfrom 调用上。防止这样永久阻塞的一般方法是给客户的 recvfrom 设置一个超时,但这也不是完整的解决办法,因为这会使得我们无从判定超时原因是客户的数据报没有到达服务器,还是服务器的应答没有回到客户。后面会再继续讨论如何给 UDP 客户/服务器程序增加可靠性。
    另外,为了避免收到来自非请求服务器的数据报,客户程序对 recvfrom 返回的数据报发送者的地址进行了过滤(或直接使用 connect,见下)。不过如果服务器主机是多宿的,这种方式可能会使服务器的某些应答数据报丢失。因为大多数 IP 实现接受目的地址为本主机任一 IP 地址的数据报,而不管数据报到达的接口(RFC 称之为弱端系统模型。反之,强端系统模型则只接受到达接口与目的地址一致的数据报),所以当服务器绑定到其套接字上的是通配 IP 地址时,内核会自行为封装应答的 IP 数据报选择一个源地址作为外出接口的主 IP 地址,而这可能不是客户请求的那个地址,因而被客户程序误过滤掉了。对该问题的一个解决办法是:客户得到由 recvfrom 返回的 IP 地址后,通过在 DNS 中查找服务器主机的名字来验证该主机的域名而非 IP 地址。另一个解决办法是:UDP 服务器给服务器主机上配置的每个 IP 地址都创建一个套接字,并绑定每个 IP 地址到各自的套接字。然后使用 select 来等待其中任何一个变得可读,再从可读的套接字给出应答。
    在 UDP 套接字中,对于像由 sendto 等函数引起的异步错误(如向一个未运行的服务器程序发送数据报时会产生“端口不可达”的 ICMP 错误信息,该错误由 sendto 引起,但是 sendto 本身却能成功返回)并不返回给它,除非它已连接。尽管 UDP 是不需要连接的,不过也确实可以在 UDP 套接字上调用 connect,只是它没有三路握手过程,内核只是检查是否存在立即可知的错误,并记录对端的 IP 地址和端口后,然后就立即返回。
    已连接的 UDP 套接字与默认的未连接的 UDP 套接字相比,发生了以下三个变化。
    (1)不能再给输出操作指定目的地址和端口号,即不使用 sendto(或者将其第五个和第六个参数分别置为空指针和 0)而改用 write 或 send。写到已连接 UDP 套接字上的任何内容都自动发送到由 connect 指定的地址。
    (2)同理,不必使用 recvfrom 以获悉数据报的发送者,而改用 read、recv 或 recvmsg,因为内核只返回那些来自 connect 所指定的目的地址的数据报。
    (3)会返回异步错误。
    一般调用 connect 的通常是 UDP 客户,不过有些 UDP 服务器(如 TFTP)会与单个客户长时间通信,在这种情况下,客户和服务器都可能调用 connect。
    下图的 DNS 是一个调用 connect 的例子。

    通常通过在 /etc/resolv.conf 中列出服务器主机的 IP 地址,一个 DNS 客户就能被配置成使用一个或多个 DNS 服务器。如果列出的是单个服务器主机(如图中第一个客户),客户进程就可以调用 connect,但如果列出的是多个服务器主机(如图中第二个客户),客户进程就不能调用 connect。另外,DNS 服务器进程通常是处理多个客户请求的,因此不能调用 connect。
    此外,拥有一个已连接 UDP 套接字的进程可出于下列两个目的之一再次调用 connect:
    (1)指定新的 IP 地址和端口号。这种情况对于 TCP 套接字,connect 只能调用一次。
    (2)断开套接字。为了断开一个已连接的 UDP 套接字,可以在再次调用 connect 时把套接字地址结构的地址族成员 sin_family 或 sin6_family 设置成 AF_UNSPEC,这么做可能会返回一个 EAFNOSUPPORT 错误,不过没有关系。使套接字断开连接的是在已连接 UDP 套接字上调用 connect 的进程。
    当应用进程知道自己要给同一目的地址发送多个数据报时,显示连接套接字效率会更高。因为在一个未连接的 UDP 套接字上发送数据报时,源自 Berkeley 的内核会暂时连接该套接字,然后发送数据报,之后又断开该连接,即使下一个数据报是发送到同一地址也是如此。
  • 大小: 61.6 KB
  • 大小: 12.6 KB
分享到:
评论

相关推荐

    Python基于UDP协议的套接字通信,网络编程必看

    - 首先,导入socket模块并创建一个UDP套接字对象,使用socket.SOCK_DGRAM指定UDP协议。 - 绑定本地IP地址和端口号,这使得服务端可以监听特定的网络接口和端口,等待来自客户端的数据。 - 使用socket的recvfrom...

    基于Python进行TCP套接字编程的基础知识 实验报告

    熟悉基于Python进行UDP套接字编程的基础知识,掌握使用UDP套接字发送和接收数据包,以及设置正确的套接字超时,了解Ping应用程序的基本概念,并理解其在简单判断网络状态,例如计算数据包丢失率等统计数据方面的意义...

    Qt套接字编程源码

    **二、UDP套接字编程** 1. **QUdpSocket**:QUdpSocket类用于UDP通信,它不保证数据的顺序和完整性,但具有较低的延迟。与TCP套接字不同,UDP通信不需要建立连接,而是直接使用writeDatagram()发送数据,...

    Java 套接字编程Java 套接字编程

    ### Java套接字编程详解 #### 一、引言 随着互联网技术的不断发展与普及,网络编程成为现代软件开发中不可或缺的一部分。Java作为一种广泛应用的编程语言,提供了丰富的API支持网络编程,尤其是对于套接字(Socket)...

    网络套接字编程

    总之,网络套接字编程是构建网络应用的基础,通过学习和掌握相关函数和概念,开发者能够实现自定义的客户端和服务端应用程序。而在MFC环境下,CAsyncSocket类提供了一种简洁的方式来处理套接字通信,使开发工作更为...

    UDP-chat.rar_UDP 套接字 聊天

    UDP(User Datagram Protocol)是一种无...总的来说,"UDP-chat.rar"项目提供了一个学习和实践UDP套接字编程的平台,通过实际操作,可以加深对UDP协议和套接字编程的理解,为将来处理更复杂的网络应用打下坚实的基础。

    套接字编程总结

    2. **UDP套接字编程**: - UDP是一种无连接的协议,通信双方不需要先建立连接就可以直接发送数据。 - **服务器端**: - 创建套接字,类型为SOCK_DGRAM(UDP)。 - `bind()`函数同样用于绑定IP地址和端口,但因为...

    套接字socket编程文档

    UDP套接字编程相对简单,无需建立连接。服务器和客户端都可以直接使用sendto()和recvfrom()发送和接收数据。由于UDP是无连接的,因此需要处理数据包乱序、丢失等问题。 五、跨平台兼容性 套接字编程通常遵循POSIX...

    MFC套接字编程

    在MFC中,我们可以利用其提供的类来实现套接字编程,以便构建网络应用,如客户端(Client)和服务器端(Server)。 套接字编程主要涉及以下知识点: 1. 套接字概念:套接字是网络上的两个进程之间进行通信的一种...

    简单的套接字编程

    本文将详细讲解"简单的套接字编程",包括基础概念、服务端实现、客户端交互以及如何使用Python进行实际操作。 首先,我们要理解套接字的基本概念。套接字是操作系统提供的一种接口,用于在网络中实现进程间通信...

    多线程和套接字编程程序

    在IT领域,多线程和套接字编程是两个至关重要的概念,特别是在网络通信和并发处理方面。这里,我们深入探讨这两个主题,并结合你提供的文件列表进行解析。 首先,让我们来了解一下多线程编程。多线程是操作系统提供...

    windows下的udp套接字c源码用例例子

    在Windows环境下,UDP(User Datagram Protocol)套接字是网络编程中的一个重要概念,它主要用于实现无...通过理解这些基础概念和函数,你可以分析并学习这两个源码文件,进一步掌握UDP套接字在Windows下的C语言实现。

    TCP和UDP套接字_简单的客户/服务器

    总的来说,理解TCP和UDP的工作原理,掌握多线程编程技术,是构建高效网络应用程序的基础。在设计客户/服务器系统时,根据业务需求灵活选择TCP或UDP,结合多线程编程,可以构建出能够满足大规模并发访问的网络服务。

    Windows下UDP套接字聊天小软件

    【标题】:“Windows下UDP套接字聊天小软件”是一个基于UDP协议的简易通信程序,专为初学者设计,用于理解套接字编程的基本概念和操作。它在Windows操作系统环境下运行,通过设置客户端代码中的IP地址,使得不同...

    C#中的套接字编程实例

    在C#中,套接字编程是实现网络通信的基础,特别是在TCP/IP协议栈上构建应用。C#提供了丰富的API来支持套接字编程,使得开发者能够轻松地创建客户端和服务器端应用程序。以下是对C#中套接字编程的详细解释。 首先,...

    简单的套接字编程,讲的很易懂

    ### 套接字编程基础知识点详解 #### 1.1 什么是套接字 套接字(Socket),作为同一台主机内部应用层与传输层之间的接口,扮演着应用程序和网络之间的桥梁角色,即应用程序接口(API)。它允许应用程序通过网络进行...

    套接字编程的基本概念

    套接字编程是网络编程的基础,它允许程序通过网络与其他程序进行通信。本文将深入探讨套接字编程的基本概念,包括套接字的定义、地址结构、套接字类型以及基本的套接字系统调用。 首先,我们要理解套接字编程中的几...

    网络编程(二) —— 套接字编程原理

    网络编程中的套接字编程是实现TCP/IP网络中两个进程间通信的基础,主要涉及客户机/服务器模式。在这个模式下,通信双方并非对等,而是由服务器提供服务,客户机发起请求。通信过程是异步的,即服务器始终保持监听...

    套接字编程

    本篇文章将深入探讨TCP/UDP套接字编程的基础知识及其在Linux C环境下的应用。 TCP套接字: TCP是一种面向连接的、可靠的传输协议。它通过三次握手建立连接,并在数据传输过程中实施流量控制和拥塞避免,确保数据的...

    MFC套接字编程简介.txt

    在C++中,尤其是MFC框架下,套接字编程是实现客户端与服务器间通信的基础。 - **C/S模型**(Client/Server Model):这是套接字编程中最常见的模型之一,其中客户端向服务器发送请求,服务器处理这些请求并返回结果...

Global site tag (gtag.js) - Google Analytics