- 浏览: 141157 次
文章分类
最新评论
下图显示了使用 UDP 套接字编写客户/服务器程序时的大致流程。
UDP 中的客户不需要与服务器建立连接,而是采用 sendto 和 recvfrom 函数来发送和接收数据。
这两个函数的前三个参数等同于 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 套接字编写的简单回射服务器示例(省略了对函数返回值的检测)。
由于无需连接,所以大多数 UDP 服务器提供的都是一个迭代服务器,而不是像 TCP 服务器那样需要提供一个并发服务器。
每个 UDP 套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个缓冲区。当进程调用 recvfrom 时,缓冲区中的下一个数据报就以 FIFO 的顺序递送给进程。
接下来也给出了客户端的代码示例。
要注意的是,这里的 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 的内核会暂时连接该套接字,然后发送数据报,之后又断开该连接,即使下一个数据报是发送到同一地址也是如此。
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 的内核会暂时连接该套接字,然后发送数据报,之后又断开该连接,即使下一个数据报是发送到同一地址也是如此。
发表评论
-
Unix 域套接字与描述符的传递
2019-03-27 23:59 711在Unix 域套接字概述一节中介绍了什么是 U ... -
Unix 域套接字概述
2019-03-12 22:48 979Unix 域协议并不是一个实际的协议族,而是在 ... -
kqueue 接口
2019-03-06 00:47 691kqueue 接口是 ... -
辅助数据
2019-02-28 00:40 687辅助数据(a ... -
recv/send 和 recvmsg/sendmsg 函数
2019-01-22 00:40 1553recv 和 send ... -
inetd 守护进程介绍
2019-01-09 21:51 1054在 4.3 BSD 系统之前,很多网络服务都是 ... -
主机名与 IP 地址的转换(续)
2018-12-25 00:37 957在主机名与 IP 地址的转换一节中提到的 ge ... -
主机名与 IP 地址的转换
2018-11-14 00:20 2308在网络编程中,尽管大部分情况下操作的都是 IP ... -
SCTP 事件通知
2018-02-08 03:49 985SCTP 提供了多种可用的通知,用户可经由这些通知追踪 ... -
SCTP 套接字选项
2018-02-04 09:35 1692在获取和设置套接 ... -
SCTP 套接字编程基础函数
2018-02-04 10:08 1056SCTP 服务器可以使 ... -
SCTP 套接字编程基础概念
2018-01-18 00:10 561SCTP 套接字分为一到一套接字和一到多套接字。提供一 ... -
通用套接字选项
2018-01-02 00:46 551在获取和设置套接 ... -
获取和设置套接字选项
2017-12-29 08:21 441下面几种方法可用 ... -
I/O 复用之 poll 函数
2017-12-27 00:20 423poll 函数提供的功能与 select 类似,不过在 ... -
I/O 复用之select 函数
2017-12-12 00:32 516select 函数允许进 ... -
Unix 5 种 IO 模型概述
2017-11-19 01:44 306Unix 下有 5 种可用 ... -
套接字创建、连接和关闭函数
2017-08-13 17:16 664下图是一对 TCP 客户与服务器进程之间发生的一些典型 ... -
字节转换和填充函数
2017-08-06 01:09 428网络编程中,为保证发送协议栈和接收协议栈就如 32 位 ... -
IPv4 和 IPv6 的套接字地址结构
2017-08-01 21:03 739大多数套接字函数 ...
相关推荐
- 首先,导入socket模块并创建一个UDP套接字对象,使用socket.SOCK_DGRAM指定UDP协议。 - 绑定本地IP地址和端口号,这使得服务端可以监听特定的网络接口和端口,等待来自客户端的数据。 - 使用socket的recvfrom...
熟悉基于Python进行UDP套接字编程的基础知识,掌握使用UDP套接字发送和接收数据包,以及设置正确的套接字超时,了解Ping应用程序的基本概念,并理解其在简单判断网络状态,例如计算数据包丢失率等统计数据方面的意义...
**二、UDP套接字编程** 1. **QUdpSocket**:QUdpSocket类用于UDP通信,它不保证数据的顺序和完整性,但具有较低的延迟。与TCP套接字不同,UDP通信不需要建立连接,而是直接使用writeDatagram()发送数据,...
### Java套接字编程详解 #### 一、引言 随着互联网技术的不断发展与普及,网络编程成为现代软件开发中不可或缺的一部分。Java作为一种广泛应用的编程语言,提供了丰富的API支持网络编程,尤其是对于套接字(Socket)...
总之,网络套接字编程是构建网络应用的基础,通过学习和掌握相关函数和概念,开发者能够实现自定义的客户端和服务端应用程序。而在MFC环境下,CAsyncSocket类提供了一种简洁的方式来处理套接字通信,使开发工作更为...
UDP(User Datagram Protocol)是一种无...总的来说,"UDP-chat.rar"项目提供了一个学习和实践UDP套接字编程的平台,通过实际操作,可以加深对UDP协议和套接字编程的理解,为将来处理更复杂的网络应用打下坚实的基础。
2. **UDP套接字编程**: - UDP是一种无连接的协议,通信双方不需要先建立连接就可以直接发送数据。 - **服务器端**: - 创建套接字,类型为SOCK_DGRAM(UDP)。 - `bind()`函数同样用于绑定IP地址和端口,但因为...
UDP套接字编程相对简单,无需建立连接。服务器和客户端都可以直接使用sendto()和recvfrom()发送和接收数据。由于UDP是无连接的,因此需要处理数据包乱序、丢失等问题。 五、跨平台兼容性 套接字编程通常遵循POSIX...
在MFC中,我们可以利用其提供的类来实现套接字编程,以便构建网络应用,如客户端(Client)和服务器端(Server)。 套接字编程主要涉及以下知识点: 1. 套接字概念:套接字是网络上的两个进程之间进行通信的一种...
本文将详细讲解"简单的套接字编程",包括基础概念、服务端实现、客户端交互以及如何使用Python进行实际操作。 首先,我们要理解套接字的基本概念。套接字是操作系统提供的一种接口,用于在网络中实现进程间通信...
在IT领域,多线程和套接字编程是两个至关重要的概念,特别是在网络通信和并发处理方面。这里,我们深入探讨这两个主题,并结合你提供的文件列表进行解析。 首先,让我们来了解一下多线程编程。多线程是操作系统提供...
在Windows环境下,UDP(User Datagram Protocol)套接字是网络编程中的一个重要概念,它主要用于实现无...通过理解这些基础概念和函数,你可以分析并学习这两个源码文件,进一步掌握UDP套接字在Windows下的C语言实现。
总的来说,理解TCP和UDP的工作原理,掌握多线程编程技术,是构建高效网络应用程序的基础。在设计客户/服务器系统时,根据业务需求灵活选择TCP或UDP,结合多线程编程,可以构建出能够满足大规模并发访问的网络服务。
【标题】:“Windows下UDP套接字聊天小软件”是一个基于UDP协议的简易通信程序,专为初学者设计,用于理解套接字编程的基本概念和操作。它在Windows操作系统环境下运行,通过设置客户端代码中的IP地址,使得不同...
在C#中,套接字编程是实现网络通信的基础,特别是在TCP/IP协议栈上构建应用。C#提供了丰富的API来支持套接字编程,使得开发者能够轻松地创建客户端和服务器端应用程序。以下是对C#中套接字编程的详细解释。 首先,...
### 套接字编程基础知识点详解 #### 1.1 什么是套接字 套接字(Socket),作为同一台主机内部应用层与传输层之间的接口,扮演着应用程序和网络之间的桥梁角色,即应用程序接口(API)。它允许应用程序通过网络进行...
套接字编程是网络编程的基础,它允许程序通过网络与其他程序进行通信。本文将深入探讨套接字编程的基本概念,包括套接字的定义、地址结构、套接字类型以及基本的套接字系统调用。 首先,我们要理解套接字编程中的几...
网络编程中的套接字编程是实现TCP/IP网络中两个进程间通信的基础,主要涉及客户机/服务器模式。在这个模式下,通信双方并非对等,而是由服务器提供服务,客户机发起请求。通信过程是异步的,即服务器始终保持监听...
本篇文章将深入探讨TCP/UDP套接字编程的基础知识及其在Linux C环境下的应用。 TCP套接字: TCP是一种面向连接的、可靠的传输协议。它通过三次握手建立连接,并在数据传输过程中实施流量控制和拥塞避免,确保数据的...
在C++中,尤其是MFC框架下,套接字编程是实现客户端与服务器间通信的基础。 - **C/S模型**(Client/Server Model):这是套接字编程中最常见的模型之一,其中客户端向服务器发送请求,服务器处理这些请求并返回结果...