Rust應(yīng)用調(diào)用C語言動(dòng)態(tài)庫的操作方法
外部功能接口FFI
雖然高級(jí)(腳本)編程語言的功能豐富,表達(dá)能力強(qiáng),但對(duì)底層的一些特殊操作的支持并不完善,就需要以其他編程語言來實(shí)現(xiàn)。調(diào)用其他編程語言的接口,被稱為Foreign Function Interface,直譯為外部功能接口。該接口通常是調(diào)用C語言實(shí)現(xiàn)的外部功能模塊,因?yàn)镃語言接近于全能,幾乎任何功能都能夠?qū)崿F(xiàn);正如同使用匯編語言也可以實(shí)現(xiàn)很多功能一樣,但開發(fā)效率低下。很多腳本語言提供了FFI功能,例如Python、PHP和JIT版本的Lua解析器等。同樣的,Rust也提供了FFI接口,作為標(biāo)準(zhǔn)庫中一個(gè)功能模塊;但本文不會(huì)討論該模塊的使用方法。本文記錄了筆者編寫一個(gè)簡(jiǎn)單的C語言動(dòng)態(tài)庫,并通過Rust調(diào)用動(dòng)態(tài)庫導(dǎo)出的函數(shù);另外,筆者直接使用Rust官方提供的libc庫,直接替代筆者編寫的C語言動(dòng)態(tài)庫,以避免重復(fù)造輪子。
UDP套接字的讀超時(shí)
Rust標(biāo)準(zhǔn)庫中的UDP網(wǎng)絡(luò)功能,提供了設(shè)置套接字讀超時(shí)的函數(shù),set_read_timeout,了解C語言網(wǎng)絡(luò)編譯的開發(fā)人員都知道,相應(yīng)的底層調(diào)用為setsockopt(SO_RCVTIMEO)。假設(shè)Rust標(biāo)準(zhǔn)庫中UDP模塊未提供該函數(shù),就需要編寫C語言代碼,將其編譯成一個(gè)動(dòng)態(tài)庫,嘗試將Rust鏈接到該庫,并調(diào)用其中定義的函數(shù)了。筆者編寫的代碼如下:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
/* export function to set socket receive timeout */
extern int normal_setsock_timeout(int sockfd,
unsigned long timeout);
int normal_setsock_timeout(int sockfd,
unsigned long timeout)
{
int ret, err_n;
struct timeval tv;
tv.tv_sec = (time_t) (timeout / 1000);
tv.tv_usec = (timeout % 1000) * 1000;
ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,
&tv, sizeof(tv));
if (ret == -1) {
err_n = errno;
fprintf(stderr, "Error, setsockopt(%d, RECVTIMEO, %lu) has failed: %s\n",
sockfd, timeout, strerror(err_n));
fflush(stderr);
errno = err_n;
return -1;
}
return 0;
}
通過以下命令生成動(dòng)態(tài)庫libsetsock.so:
gcc -Wall -O2 -fPIC -D_GNU_SOURCE -shared -o libsetsock.so -Wl,-soname=libsetsock.so mysetsock.c
筆者使用Rust編寫的簡(jiǎn)單UDP服務(wù)端代碼如下:
use std::net::UdpSocket;
use chrono::{DateTime, Local};
fn get_local_time() -> String {
let nowt: DateTime<Local> = Local::now();
nowt.to_string()
}
fn main() -> std::io::Result<()> {
let usock = UdpSocket::bind("127.0.0.1:2021");
if usock.is_err() {
let errval = usock.unwrap_err();
println!("Error, failed to create UDP socket: {:?}", errval);
return Err(errval);
}
// get the UdpSocket structure
let usock = usock.unwrap();
// create 2048 bytes of buffer
let mut buffer = vec![0u8; 2048];
println!("{} -> Waiting for UDP data...", get_local_time());
// main loop
loop {
let res = usock.recv_from(&mut buffer);
if res.is_err() {
println!("{} -> Error, failed to receive from UDP socket: {:?}",
get_local_time(), res.unwrap_err());
break;
}
let (rlen, peer_addr) = res.unwrap();
println!("{} -> received {} bytes from {:?}:{}",
get_local_time(), rlen, peer_addr.ip(), peer_addr.port());
}
// just return ok
Ok(())
}
短短50多行代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的UDP服務(wù)端,作為系統(tǒng)編程語言的Rust開發(fā)效率可見一斑。不過該UDP服務(wù)器的讀操作是阻塞的,它會(huì)一直等待網(wǎng)絡(luò)數(shù)據(jù)的到來:
udp_socket$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/udp_socket`
2021-07-11 19:38:29.791363796 +08:00 -> Waiting for UDP data...
2021-07-11 19:38:39.721713256 +08:00 -> received 16 bytes from 127.0.0.1:39180
2021-07-11 19:38:48.553386975 +08:00 -> received 16 bytes from 127.0.0.1:58811Rust調(diào)用C語言動(dòng)態(tài)庫中的函數(shù)
與C語言類似,Rust使用extern關(guān)鍵字可實(shí)現(xiàn)對(duì)外部函數(shù)的聲明,不過在調(diào)用的代碼需要以u(píng)nsafe關(guān)鍵字包成代碼塊。以下是筆者對(duì)上面的Rust代碼的修改:
diff --git a/src/main.rs b/src/main.rs
index 304c7dc..5921106 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,12 @@
use std::net::UdpSocket;
use chrono::{DateTime, Local};
+use std::os::raw::c_int;
+use std::os::unix::io::AsRawFd;
+
+#[link(name = "setsock")]
+extern {
+ pub fn normal_setsock_timeout(sock_fd: c_int, timo: usize) -> c_int;
+}
fn get_local_time() -> String {
let nowt: DateTime<Local> = Local::now();
@@ -20,6 +27,11 @@ fn main() -> std::io::Result<()> {
let mut buffer = vec![0u8; 2048];
println!("{} -> Waiting for UDP data...", get_local_time());
+ // set UDP socket receive timeout
+ unsafe {
+ normal_setsock_timeout(usock.as_raw_fd() as c_int, 5000);
+ }
+
// main loop
loop {
let res = usock.recv_from(&mut buffer);修改后的主代碼增加了#[link]屬性,指示Rust編譯器在鏈接時(shí)加入-lsetsock鏈接選項(xiàng)。再次編譯,會(huì)發(fā)現(xiàn)鏈接命令失?。?/p>
udp_socket$ cargo build Compiling udp_socket v0.1.0 (/home/yejq/program/rust-lang/udp_socket) error: linking with `cc` failed: exit status: 1
這說明雖然編譯是正常的,但在鏈接時(shí)找不到libsetsock.so動(dòng)態(tài)庫。解決方法是在工程根目錄下增加一個(gè)編譯控制的Rust代碼,文件名為build.rs,給出動(dòng)態(tài)庫所在的目錄:
fn main() {
println!(r"cargo:rustc-link-search=native=/home/yejq/program/rust-lang/socket_udp");
}再次執(zhí)行cargo build編譯工程,鏈接就能成功了;使用patchelf和nm等命令行工具察看,生成的可執(zhí)行文件依賴了C語言編寫的動(dòng)態(tài)庫libsetsock.so,并引用了其導(dǎo)出的函數(shù)符號(hào)normal_setsock_timeout:
udp_socket$ cargo build
Compiling udp_socket v0.1.0 (/home/yejq/program/rust-lang/udp_socket)
Finished dev [unoptimized + debuginfo] target(s) in 1.72s
udp_socket$ patchelf --print-needed ./target/debug/udp_socket
libsetsock.so
libgcc_s.so.1
libpthread.so.0
libdl.so.2
libc.so.6
ld-linux-x86-64.so.2
udp_socket$ nm --undefined-only ./target/debug/udp_socket | grep -e normal_setsock
U normal_setsock_timeout此時(shí)運(yùn)行簡(jiǎn)單UDP服務(wù)端程序,可以確定我們?cè)黾拥奶捉幼肿x超時(shí)功能能夠正常工作:
udp_socket$ LD_LIBRARY_PATH=/home/yejq/program/rust-lang/socket_udp ./target/debug/udp_socket
2021-07-11 19:55:26.279653039 +08:00 -> Waiting for UDP data...
2021-07-11 19:55:29.788948366 +08:00 -> received 16 bytes from 127.0.0.1:43303
2021-07-11 19:55:31.977738660 +08:00 -> received 16 bytes from 127.0.0.1:46854
2021-07-11 19:55:37.179290653 +08:00 -> Error, failed to receive from UDP socket: Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }避免重復(fù)造輪子,使用Rust官方C語言庫
以上我們用C語言編寫了簡(jiǎn)單的動(dòng)態(tài)庫,導(dǎo)出了一個(gè)可設(shè)置套接字讀超時(shí)的函數(shù)。這個(gè)功能過于簡(jiǎn)單,費(fèi)大周折編寫一個(gè)動(dòng)態(tài)庫顯得得不償失。另一個(gè)解決方案是直接使用Rust官方提供的C語言庫,該庫提供了很多變量和函數(shù)(與glibc提供的宏定義和庫函數(shù)、系統(tǒng)調(diào)用有很多重疊),可以直接添加setsockopt等系統(tǒng)調(diào)用的代碼。修改UDP服務(wù)器代碼:
diff --git a/src/main.rs b/src/main.rs
index 5921106..3f4bc84 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,11 +2,7 @@ use std::net::UdpSocket;
use chrono::{DateTime, Local};
use std::os::raw::c_int;
use std::os::unix::io::AsRawFd;
-
-#[link(name = "setsock")]
-extern {
- pub fn normal_setsock_timeout(sock_fd: c_int, timo: usize) -> c_int;
-}
+use libc;
fn get_local_time() -> String {
let nowt: DateTime<Local> = Local::now();
@@ -27,9 +23,17 @@ fn main() -> std::io::Result<()> {
let mut buffer = vec![0u8; 2048];
println!("{} -> Waiting for UDP data...", get_local_time());
- // set UDP socket receive timeout
unsafe {
- normal_setsock_timeout(usock.as_raw_fd() as c_int, 5000);
+ let time_val = libc::timeval {
+ tv_sec: 5,
+ tv_usec: 0,
+ };
+
+ // set socket receive timeout via extern create, libc
+ libc::setsockopt(usock.as_raw_fd() as c_int,
+ libc::SOL_SOCKET, libc::SO_RCVTIMEO,
+ &time_val as *const libc::timeval as *const libc::c_void,
+ std::mem::size_of_val(&time_val) as libc::socklen_t);
}除了以上的修改,還需要在Cargo.toml文件中加入C語言庫的依賴,這里筆者使用的libc版本為0.2.98:
diff --git a/Cargo.toml b/Cargo.toml index f802b0d..eb0b78e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +libc = "0.2.98" chrono = "0.4.19"
以上修改的代碼,與之前相同的是,調(diào)用C語言庫提供的函數(shù)也需要用到unsafe代碼塊;而工程根目錄下的編譯相關(guān)的控制代碼build.rs就不再需要了;編譯生成的UDP服務(wù)器也會(huì)在5秒無數(shù)據(jù)時(shí)退出。最后,能夠調(diào)用C語言編寫的動(dòng)態(tài)庫,意味著使用Rust語言來進(jìn)行嵌入式系統(tǒng)軟件的開發(fā),是一種具備可行性的技術(shù)方案。
到此這篇關(guān)于Rust應(yīng)用調(diào)用C語言動(dòng)態(tài)庫的操作方法的文章就介紹到這了,更多相關(guān)Rust調(diào)用C語言動(dòng)態(tài)庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust錯(cuò)誤處理之`foo(...)?`的用法與錯(cuò)誤類型轉(zhuǎn)換小結(jié)
foo(...)?語法糖為Rust的錯(cuò)誤處理提供了極大的便利,通過結(jié)合map_err方法和From?trait的實(shí)現(xiàn),你可以輕松地處理不同類型的錯(cuò)誤,并保持代碼的簡(jiǎn)潔性和可讀性,這篇文章主要介紹了Rust錯(cuò)誤處理:`foo(...)?`的用法與錯(cuò)誤類型轉(zhuǎn)換,需要的朋友可以參考下2024-05-05
Rust中多線程?Web?服務(wù)器的項(xiàng)目實(shí)戰(zhàn)
本文主要介紹了Rust中多線程?Web?服務(wù)器的項(xiàng)目實(shí)戰(zhàn),利用通道和互斥鎖管理任務(wù)隊(duì)列,解決單線程處理請(qǐng)求的性能瓶頸,確保并發(fā)處理能力并實(shí)現(xiàn)優(yōu)雅關(guān)閉機(jī)制2025-06-06

