UICIUDP实现及与TCP的比较
立即解锁
发布时间: 2025-08-22 01:29:20 阅读量: 4 订阅数: 12 


UNIX系统编程:通信、并发与线程精解
### UICI UDP 实现及与 TCP 的比较
#### 1. UICI UDP 基础及请求 - 回复 - 确认协议
在使用 UICI UDP 时,为了确保请求的唯一性,一种可行的方法是使用包含进程 ID 和序列号的结构。客户端将序列号初始化为 1,并为每个新请求递增该序列号。只要序列号和进程 ID 不溢出,这种方法就能正常工作。由于进程 ID 是以字符串形式发送,而非原始二进制形式,所以序列号也可以采用相同的方式发送。发送的字符串由序列号、空格和进程 ID 组成,服务器会解析该字符串以分离这两个值。若数据以原始形式而非字符串形式发送,当客户端和服务器的字节序(大端序与小端序)不同时,需要特别处理,除非这些值仅用于保证唯一性。
请求 - 回复 - 确认协议的服务器端更为复杂。服务器必须保留每个回复的副本,直到收到相应的确认信息。若客户端因崩溃等原因未能发送确认信息,服务器可能会永远保留该信息。因此,面向连接的通信更适合此类通信。TCP 通过使用请求 - 回复 - 确认协议(包括否定确认和流量控制)实现可靠通信,该协议经过优化以实现良好的性能。
#### 2. UICI UDP 函数实现
UICI UDP 函数使用与 UICI TCP 函数相同的名称解析函数 `addr2name` 和 `name2addr`。在使用 UICI UDP 时,需将源代码与 `uiciname.c` 一起编译。
##### 2.1 `u_openudp` 函数实现
`u_openudp` 函数以端口号为参数,创建一个用于 UDP 通信的无连接套接字。若成功创建通信端点,该函数将返回一个文件描述符。服务器调用 `u_openudp` 时,会将其知名端口作为参数;客户端通常将参数设为 0,表示允许系统在必要时选择一个临时端口。若发生错误,`u_openudp` 函数将返回 -1 并设置 `errno`。
以下是 `u_openudp` 函数的实现代码:
```c
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include "restart.h"
#include "uiciudp.h"
int u_openudp(u_port_t port) {
int error;
int one = 1;
struct sockaddr_in server;
int sock;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
return -1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
error = errno;
r_close(sock);
errno = error;
return -1;
}
if (port > 0) {
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons((short)port);
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
error = errno;
r_close(sock);
errno = error;
return -1;
}
}
return sock;
}
```
与 `u_open` 相比,`u_openudp` 仅在端口号大于 0 时调用 `bind` 函数,因为只有服务器需要将套接字绑定到特定端口。此外,使用 UDP 时无需担心 `SIGPIPE` 信号。向管道(或 TCP 套接字)写入数据时,若没有活动的读取器,会生成 `SIGPIPE` 信号;而 UDP 不提供有关活动接收器的信息,当 UDP 数据报成功复制到网络子系统的缓冲区时,即认为已正确发送。即使应用程序将数据报发送到未等待接收的目的地,UDP 也不会检测到错误,因此发送操作不会生成 `SIGPIPE` 信号。
##### 2.2 `sendto` 函数
POSIX `sendto` 函数将数据作为单个数据报进行传输,若成功,将返回传输的字节数。但 `sendto` 仅检查本地错误,成功并不意味着接收方实际收到了数据。
`sendto` 函数的前三个参数与 `read` 和 `write` 函数的参数含义相同。`socket` 参数保存先前通过调用 `socket` 函数打开的文件描述符;`message` 参数包含要发送的数据;`length` 是要发送的字节数。`flags` 参数允许使用特殊选项,但此处未使用,因此该值始终为 0。`dest_addr` 参数指向一个填充了目的地信息的结构,包括远程主机地址和远程端口号。由于使用的是 Internet 域,`*dest_addr` 是一个 `struct sockaddr_in` 结构,`dest_len` 是该结构的大小。
`sendto` 函数的原型如下:
```c
#include <sys/socket.h>
ssize_t sendto(int socket, const void *message, size_t length,
int flags, const struct sockaddr *dest_addr,
socklen_t dest_len);
```
若成功,`sendto` 返回发送的字节数;若失败,返回 -1 并设置 `errno`。以下是未连接套接字使用 `sendto` 时的强制错误列表:
| errno | 原因 |
| ---- | ---- |
| EAFNOSUPPORT | 地址族不能与此套接字一起使用 |
| EAGAIN 或 EWOULDBLOCK | 设置了 O_NONBLOCK 且操作会阻塞 |
| EBADF | socket 参数不是有效的文件描述符 |
| EINTR | 在传输任何数据之前,sendto 被中断 |
| EMSGSIZE | 消息太大,无法按照套接字的要求一次性发送 |
| ENOTSOCK | socket 不引用套接字 |
| EOPNOTSUPP | 指定的标志不支持此类型的套接字 |
`sendto` 函数可用于连接到特定目的地主机和端口的套接字,但它仍根据 `*dest_addr` 结构中的信息确定目的地主机和端口号,与连接状态无关。若在尚未绑定源端口的套接字上使用 `sendto`,网络子系统会分配一个未使用的临时端口并将其与套接字绑定。从该套接字发出的数据报会包含端口号和源主机地址以及数据,以便远程主机进行回复。
##### 2.3 `u_sendto` 和 `u_sendtohost` 函数实现
UICI UDP 库提供了两个用于发送消息的函数:`u_sendto` 和 `u_sendtohost`。
`u_sendtohost` 函数以目标主机名和端口号为参数,用于发起与远程主机的通信。`u_sendto` 函数使用先前调用 `u_recvfrom` 填充的 `u_buf_t` 结构,该结构用于回复消息。
以下是这两个函数的实现代码:
```c
#include <errno.h>
#include <sys/socket.h>
#include "uiciname.h"
#include "uiciudp.h"
ssize_t u_sendto(int fd, void *buf, size_t nbytes, u_buf_t *ubufp) {
int len;
struct sockaddr *remotep;
int retval;
len = sizeof(struct sockaddr_in);
remotep = (struct sockaddr *)ubufp;
while (((retval = sendto(fd, buf, nbytes, 0, remotep, len)) == -1) &&
(errno == EINTR)) ;
return retval;
}
ssize_t u_sendtohost(int fd, void *buf, size_t nbytes, char *hostn,
u_port_t port) {
struct sockaddr_in remote;
if (name2addr(hostn, &(remote.sin_addr.s_addr)) == -1) {
errno = EINVAL;
return -1;
}
remote.sin_port = htons((short)port);
remote.sin_family = AF_INET;
return u_sendto(fd, buf, nbytes, &remote);
}
```
`u_sendto` 函数与 `sendto` 函数几乎相同,只是若被信号中断,`u
0
0
复制全文
相关推荐







