Linux C环境开发编码实例(一)

1.Linux C开发中,使用void指针的场景有哪些 动态内存分配和处理:malloc()、calloc() 和 realloc() 等函数返回的是 void 类型的指针,因为它们可以在运行时确定存储需要什么类型的内存。
函数参数传递:当你不确定接收者需要什么样的数据类型,或者想让函数接受多种类型的参数时,可以用 void* 作为参数。通过强制类型转换,可以在函数内部访问实际的数据。
标准库函数:如标准输入输出操作中的 fread() 和 fwrite(),它们接受缓冲区的指针,通常就是 void。 字符串处理:字符串常量在C语言里实际上是 char,但在某些情况,比如从外部输入或系统调用获取字符串,可能会用到 void* 来间接处理字符串。
结构体和数组的动态大小:当你有一个不确定大小的结构体或数组时,可以使用 void* 指针与其关联,然后在需要的时候进行解析。

2.关闭文件描述符的操作中,close()函数和shutdown()函数有何区别
shutdown()函数主要用于针对网络套接字的操作,它用于关闭套接字的发送部分/接收部分,而不仅仅是普通文件的I/O操作。
close()函数则是单纯用来关闭文件描述符,无论是标准输入、输出还是其他类型的文件。
当需要终止一个网络连接,并希望双方都能感知到这个操作时,比如服务器主动断开和客户端的连接,此时应该用shutdown()。
shutdown()可以分别指定SHUT_RD(只关闭接收)或SHUT_WR(只关闭发送),让另一方能够处理错误或者清理资源,可以避免不必要的数据传输,提高通信效率。
如果仅需关闭文件描述符本身,如普通文件读写完成后,应该使用close(),因为close()不会影响正在进行的数据传输,而只是标记该文件描述符不再有效。

3.为何推荐使用uint8_t类型来表示缓冲区(buffer)的数据,有哪些优点
明确字节数量:uint8_t是一个固定的8位无符号整型,这意味着它可以明确地表示单个字节的数据,这对于处理网络数据包、文件I/O、内存映射等场景尤其重要,因为很多这样的操作都是基于字节级别的。对于明确表示单个字节的数据,使用uint8_t能直观地反映出数据的含义。
无符号特性:缓冲区往往涉及到二进制数据,如颜色值、像素值或网络字节序,uint8_t作为无符号类型,避免了正负数带来的潜在问题,简化了数据解析过程。
跨平台兼容:uint8_t是标准C语言定义的一部分,保证了在各种Linux发行版和其他系统之间的可移植性和一致性。
性能考虑:对于较小的数据类型,像uint8_t,CPU缓存友好,访问速度快,特别是在频繁读写的高性能环境下。
清晰易懂:通过明确的数据类型,开发人员可以一眼看出该变量用于存储什么样的数据,提高了代码可读性。
字节无溢出:uint8_t是一个8位无符号整型,它可以存储0到255的整数,正好对应计算机中一个字节的范围。当处理字节级别的数据时,如网络包、图像像素等,避免了数据溢出的风险。
平台兼容性:许多标准库函数和数据结构如struct sockaddr_in等都使用uint8_t来定义部分字段,许多标准库函数如memcpy()、memset()等直接接受uint8_t类型的参数,保证跨平台的代码兼容性。
内存对齐:uint8_t通常是字节对齐的,这在处理特定硬件特性(例如CPU缓存优化)时是有益的。

4.Linux C开发中uint8_t类型与char类型有何异同
相同点:
它们都是基本数据类型,用于存储单个字符或字节的数据。在内存占用上,两者通常大小相同,即一个字节。数值范围上,它们都可以表示从0到255的整数值。
不同点:
根据不同的编译器,char可以为signed char(-128~127)或unsigned char(0~255),而uint8_t被明确指定为无符号类型,只能存储非负值。
在默认值方面,未初始化的char可能会得到一个不确定的值,这个取决于编译器,而uint8_t初始化为0。

在应用场景上,char类型更多用于文本操作,如ASCII字符、字符串等;uint8_t类型更多用于无符号数据的读写,比如数据包、网络协议的字节流等。
数据宽度上,uint8_t类型是一种固定宽度的类型,通常是8位,而char类型在不同的系统上可能有不同的宽度,比如,在嵌入式系统上是8位(ASCII字符集),在Windows系统上是16位(Unicode字符集)。

代码样例:

include

include

define BUFFER_SIZE 8

typedefuint8_t uint8_buffer[BUFFER_SIZE];
typedefchar char_buffer[BUFFER_SIZE];

void sendData(void* buffer, const char* message) {
if (buffer && message) {
strncpy((char)buffer, message, BUFFER_SIZE); ((char)buffer)[BUFFER_SIZE – 1] = ‘\0’;
}
}

void sendData_uint8(uint8_buffer data, const char* message) {
sendData(data, message);
//printf(“send uint8 data: %s\n”, (char*)data); 更推荐的写法
printf(“send uint8 data: %s\n”, data);
}

void sendData_char(char_buffer data, const char* message) {
sendData(data, message);
printf(“send char data: %s\n”, (char*)data);
}

int main() {
char_buffer buffer1;
uint8_buffer buffer2;

sendData_char(buffer1, "hello");
sendData_uint8(buffer2, "hello");

printf("Print origin uint8 data: \n");
for (size_t i = 0; i < sizeof(buffer2); ++i) {
    printf("%x ", (unsignedchar)buffer2[i]);
}
printf("\n");

printf("Print origin char data: \n");
for (size_t i = 0; i < sizeof(buffer1); ++i) {
    printf("%x ", buffer1[i]);
}
printf("\n");

return0;

}
运行结果:
send char data: hello
send uint8 data: hello
Print origin uint8 data:
68 65 6c 6c 6f 0 0 0
Print origin char data:
68 65 6c 6c 6f 0 0 0

5.如何创建一对已连接的套接字?
socketpair用于创建一对已连接的套接字,这两个套接字通常被称为”孤儿套接字对”。它们是在内核空间直接生成的,不需要中间服务器,非常适用于两个进程之间建立私有、安全的通信链路的情况。
这对套接字通常用于需要双方即时通信的场景,例如守护进程间的通信或者轻量级的进程间通信(Pipe)。每个套接字都有一个读端和一个写端,它们之间可以直接进行双向数据交换。
代码样例:

include

include

include

include

include

define SOCK_DOMAIN AF_UNIX

define SOCK_TYPE SOCK_STREAM

define PATH “/tmp/my_socket_pair”

int main() {
int sockpair[2];
char message[] = “Hello from child process!”;

// 创建套接字对
if (socketpair(SOCK_DOMAIN, SOCK_TYPE, 0, &sockpair[0]) < 0) {
    perror("Failed to create socket pair");
    return-1;
}

// 父进程向子进程写入消息
ssize_t bytes_sent = write(sockpair[1], message, strlen(message));
if (bytes_sent <= 0) {
    perror("Failed to write to socket");
    close(sockpair[0]);
    close(sockpair[1]);
    return-1;
}

// 子进程读取并打印消息
char buffer[1024];
bytes_sent = read(sockpair[0], buffer, sizeof(buffer));
printf("Received: %s\n", buffer);

// 关闭套接字
close(sockpair[0]);
close(sockpair[1]);

return0;

}
运行结果:
Received: Hello from child process!

6.Linux C开发场景如何进行字符串分割
安全版本的字符串分割函数推荐使用strtok_r(), 它在处理和分割字符串时,会返回之前分割过程中的下一个token,而不会影响原始字符串。

代码样例:

include

include

define BUFFER_SIZE 100

int main() {
char str[] = “This is a test string to be tokenized.”;
char *token;
char **save_token = NULL;

// 第一次调用,保存分隔状态
token = strtok_r(str, " ", &save_token);  
// save_token是之前分割的状态,如果为NULL则从头开始

while (token != NULL) {
    printf("Token: %s\n", token);

    // 再次调用strtok_r,传入保存的token和&save_token
    token = strtok_r(NULL, " ", &save_token);
}

return0;

}
运行结果:
Token: This
Token: is
Token: a
Token: test
Token: string
Token: to
Token: be
Token: tokenized.

7.常用的文件同步函数
sync/fsync/fdatasync都是一组用于同步文件系统状态到内存的系统调用,它们主要用于确保数据的持久性和一致性。
sync:这是最基础的同步操作,它会强制将内核的所有缓存的数据刷入磁盘。这个操作对整个文件系统都有效,无论是否是打开的文件。然而,因为它是全局的,所以可能会有性能开销,特别是在大量并发写操作的场景下。
fsync:相较于sync,fsync 更加有针对性,它专门针对单个打开的文件系统描述符(file descriptor)。当调用fsync时,系统会确保该文件的内容已经完全写入磁盘,并更新相关的元数据。这对于保证特定文件的一致性非常重要。
fdatasync:这是另一个更精细的同步选项,它只同步文件数据而不会同步元数据。也就是说,它只关注实际文件内容的写入,适合于不需要修改文件属性(如权限、时间戳等)的情况下,提高性能。
这三个函数通常用于需要持久化存储的应用程序,比如日志记录、数据库写操作,以及避免数据丢失的情况。
代码样例:

include

include

include

int main(void) {
int file_desc = open(“test.txt”, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (file_desc == -1) {
perror(“Error opening file”);
return1;
}

// 写入数据
char data[] = "Hello, World!";
write(file_desc, data, sizeof(data));

// 调用fsync()函数同步数据
fsync(file_desc);

close(file_desc);
printf("Data synced to disk.\n");
return0;

}
运行结果:
Data synced to disk.

8.Linux C开发场景中,如何监控一个指定的文件描述符是否可读/可写
可以利用FD_SET函数实现,在C标准库中的头文件中,FD_SET是一个宏,常用于Linux系统的I/O多路复用机制中,它用于设置和检查文件描述符集合(file descriptor sets, FDS)。

define FD_SET(int fd,fd_set *set);

用法含义:
fd:这是一个整数,代表一个文件描述符。在调用select()等函数时,fd会被添加到所指定的FDS集中,通常小于FD_SETSIZE(通常是1024)的值。
set:是一个fd_set结构体,它是一个无符号8位数组,每个元素代表一个文件描述符对应的位,1表示该描述符已经被加入集合,0表示未加入。

FD_SET函数的功能:
添加文件描述符:当一个事件发生(比如数据可用读取或写入),FD_SET(fd, &readfds)会将fd添加到readfds集合中,表明该文件描述符准备好接收数据。
查询文件描述符:在select()等函数调用后,通过比较返回的描述符集合和初始设置的FD_SET,可以判断哪些文件描述符发生了感兴趣的事件。

代码样例:
fd_set readfds, writefds;

int max_fd = -1;

// 初始化文件描述符集合
FD_ZERO(&readfds);
FD_ZERO(&writefds);

// 添加需要监控的文件描述符
if (socket_desc > max_fd) {
max_fd = socket_desc;
}
FD_SET(max_fd, &readfds);

// 调用select函数并获取结果
if (select(max_fd + 1, &readfds, &writefds, NULL, NULL)) {
// 处理发生事件的描述符…
}
完整代码实现:

include

include

include

include

int main() {
int file_descriptor = open(“/tmp/test.txt”, O_RDWR);
fd_set read_fds;
struct timeval timeout;

// 初始化文件描述符集合
FD_ZERO(&read_fds);

// 将文件描述符添加到可读集合
FD_SET(file_descriptor, &read_fds);  

// 设置超时时间,如果超过这个时间还没有可读事件,程序将继续运行
timeout.tv_sec = 5;  // 单位秒
timeout.tv_usec = 0;  // 如果希望精确到微秒,可以填写

// 使用select()判断文件描述符是否可读
if (select(file_descriptor + 1, &read_fds, NULL, NULL, &timeout)) {
    if (FD_ISSET(file_descriptor, &read_fds)) {
        printf("File descriptor is ready for reading.\n");
        // 文件描述符已准备读取,这里处理读取操作
    } else {
        printf("No readable data available after the timeout.\n");
    }
} else {
    perror("Error in select()");
}

return0;

}

运行”touch /tmp/test.txt”命令后,运行结果:
File descriptor is ready for reading.

9.使用TCP/IP传输文件,有哪些优点
可靠性:TCP(Transmission Control Protocol)是一种面向连接、基于字节流的传输层协议,它提供可靠的、按序的数据传输服务,保证了文件数据的一致性和完整性。
错误检测和恢复:TCP包含错误检测机制,如果数据包丢失或损坏,会自动请求重传,这对于大文件传输来说非常重要,可以避免文件残缺或丢失部分数据。
流量控制:TCP能通过滑动窗口机制来调整发送速率,防止接收方来不及处理而导致数据积压,这有利于在网络条件不稳定时保持高效传输。
拥塞控制:当网络拥塞时,TCP会自动减小发送速率,避免对其他连接造成冲击,有助于稳定地传输文件。
跨平台兼容:由于TCP是标准协议,几乎所有的操作系统都支持,所以无论是服务器还是客户端,只要它们支持网络通信,就可以使用TCP进行文件传输。
运行结果:
Demo1:Server端

include

include

include

include

include

include

define PORT 50000

define BUFFER_SIZE 4096

void send_file(char* filename) {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t bytes_sent;

// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
    perror("Creating socket failed");
    exit(EXIT_FAILURE);
}

// 配置服务地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

// 绑定并监听
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
    perror("Binding to address failed");
    exit(EXIT_FAILURE);
}
listen(server_fd, 5);

// 接受连接
printf("Server listening on port %d...\n", PORT);
//accept函数,传递了client_addr参数时,一定要传递client_addr的长度,不然会发生"Bad address"报错
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd == -1) {
    perror("Accepting connection failed");
    exit(EXIT_FAILURE);
}

// 发送文件内容
FILE* file = fopen(filename, "rb");
if (!file) {
    perror("Opening file failed");
    close(client_fd);
    return;
}

while ((bytes_sent = fread(buffer, 1, BUFFER_SIZE, file)) > 0) {
    if (send(client_fd, buffer, bytes_sent, 0) == -1) {
        perror("Sending data failed");
        break;
    }
}

fclose(file);
close(client_fd);

}

int main() {
send_file(“/tmp/example.txt”);
return0;
}

Demo2:Client端

include

include

include

include

include

include

include

define PORT 50000

define BUFFER_SIZE 4096

void receive_file(int client_fd) {
char buffer[BUFFER_SIZE];
ssize_t bytes_received;
FILE* file = fopen(“/tmp/received_file.txt”, “wb”);

if (!file) {
    perror("Opening output file failed");
    exit(EXIT_FAILURE);
}

while ((bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0)) > 0) {
    fwrite(buffer, 1, bytes_received, file);
}

fclose(file);
printf("File received successfully.\n");
close(client_fd);

}

int main() {
int client_fd;
struct sockaddr_in server_addr;
socklen_t addr_len = sizeof(server_addr);

// 连接到服务器
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
    perror("Creating socket failed");
    exit(EXIT_FAILURE);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

if (connect(client_fd, (struct sockaddr*)&server_addr, addr_len) == -1) {
    perror("Connecting to server failed");
    exit(EXIT_FAILURE);
}

receive_file(client_fd);

close(client_fd);
return0;

}
客户端打印:
File received successfully.

10.校验文件完整性有哪些方式
MD5哈希 (Message-Digest Algorithm 5): 使用md5sum命令生成文件的MD5散列值,然后与预存的散列值比较。如果一致则表明文件未被修改。C语言中也可以通过md5sum库来计算。
SHA-1/SHA-256: 类似于MD5,使用更安全的哈希算法如SHA-1或SHA-256,如openssl sha1 -file filename 或 sha256sum。
校验和(Checksum): 计算文件的字节流的校验和,比如CRC32、 Adler32 等。C语言中可以使用特定库函数如crc32()来实现。
文件大小检查:若文件内容未变,其大小也应该保持不变。你可以读取并对比文件的新旧大小是否一致。
整数递增计数器:对于二进制文件,可以在文件开头或结尾添加一个递增的数值(如版本号),每次更新文件时增加该数字,验证时再次读取比较。
Demo1:MD5校验

include

include

include

void compute_md5(const char* file_path, unsigned char digest[MD5_DIGEST_LENGTH]) {
FILE *fp = fopen(file_path, “rb”);
if (fp == NULL) {
perror(“Failed to open file”);
if (fp) {
fclose(fp);
}
return;
}

MD5_CTX md5context;
MD5_Init(&md5context);

size_t bytes_read = 0;
unsignedchar buffer[4096];
while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
    MD5_Update(&md5context, buffer, bytes_read);
}

if (ferror(fp)) {
    perror("Error reading file");
    fclose(fp);
    return;
}

fclose(fp);

MD5_Final(digest, &md5context);
printf("MD5 Hash: ");
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
    printf("%02x", digest[i]);
}

}

int main() {
constchar* filePath = “/tmp/test.txt”;
unsignedchar hash[MD5_DIGEST_LENGTH];

compute_md5(filePath, hash);
return0;

}
运行结果:
MD5 Hash: d41d8cd98f00b204e9800998ecf8427e

Demo2:CRC32校验
使用异或(XOR)运算符和位移(shift)来逐步计算CRC32值

include

define POLYynom 0xEDB88320

define TABLE_SIZE 256

unsignedlonglong crc32_table[TABLE_SIZE] = {0};
void build_crc32_table() {
for (int i = 0; i < TABLE_SIZE; ++i) { unsignedlonglong crc = i; for (int j = 8; j != 0; –j) { if (crc & 1) crc = (crc >> 1) ^ POLYynom;
else
crc >>= 1;
}
crc32_table[i] = crc;
}
}

unsigned long long crc32_manual(const char *data, size_t len) {
build_crc32_table();
unsignedlonglong crc = ~0ULL; // 初始化为全1

constunsignedchar *ptr = (constunsignedchar *) data;
while (len--) {
    crc = crc32_table[(crc ^ *ptr++) & 0xFF] ^ (crc << 8);
}

return crc ^ (~0ULL); // 取反得到正确的CRC32

}

int main() {
char test_data[] = “Hello, World!”;
unsignedlonglong manual_crc = crc32_manual(test_data, strlen(test_data));
printf(“Manual CRC32: 0x%llx\n”, manual_crc);
return0;
}
运行结果:
Manual CRC32: 0xedae0f9b9dc88ce9

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/424459.html

联系我们
联系我们
分享本页
返回顶部