網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
Rust應(yīng)用調(diào)用C語(yǔ)言動(dòng)態(tài)庫(kù)的操作方法_Rust語(yǔ)言
作者:塵觴葉 ? 更新時(shí)間: 2023-03-23 編程語(yǔ)言外部功能接口FFI
雖然高級(jí)(腳本)編程語(yǔ)言的功能豐富,表達(dá)能力強(qiáng),但對(duì)底層的一些特殊操作的支持并不完善,就需要以其他編程語(yǔ)言來(lái)實(shí)現(xiàn)。調(diào)用其他編程語(yǔ)言的接口,被稱為Foreign Function Interface
,直譯為外部功能接口。該接口通常是調(diào)用C語(yǔ)言實(shí)現(xiàn)的外部功能模塊,因?yàn)镃語(yǔ)言接近于全能,幾乎任何功能都能夠?qū)崿F(xiàn);正如同使用匯編語(yǔ)言也可以實(shí)現(xiàn)很多功能一樣,但開(kāi)發(fā)效率低下。很多腳本語(yǔ)言提供了FFI
功能,例如Python、PHP和JIT版本的Lua解析器等。同樣的,Rust也提供了FFI接口,作為標(biāo)準(zhǔn)庫(kù)中一個(gè)功能模塊;但本文不會(huì)討論該模塊的使用方法。本文記錄了筆者編寫(xiě)一個(gè)簡(jiǎn)單的C語(yǔ)言動(dòng)態(tài)庫(kù),并通過(guò)Rust調(diào)用動(dòng)態(tài)庫(kù)導(dǎo)出的函數(shù);另外,筆者直接使用Rust官方提供的libc庫(kù),直接替代筆者編寫(xiě)的C語(yǔ)言動(dòng)態(tài)庫(kù),以避免重復(fù)造輪子。
UDP套接字的讀超時(shí)
Rust標(biāo)準(zhǔn)庫(kù)中的UDP網(wǎng)絡(luò)功能,提供了設(shè)置套接字讀超時(shí)的函數(shù),set_read_timeout
,了解C語(yǔ)言網(wǎng)絡(luò)編譯的開(kāi)發(fā)人員都知道,相應(yīng)的底層調(diào)用為setsockopt(SO_RCVTIMEO)
。假設(shè)Rust標(biāo)準(zhǔn)庫(kù)中UDP模塊未提供該函數(shù),就需要編寫(xiě)C語(yǔ)言代碼,將其編譯成一個(gè)動(dòng)態(tài)庫(kù),嘗試將Rust鏈接到該庫(kù),并調(diào)用其中定義的函數(shù)了。筆者編寫(xiě)的代碼如下:
#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; }
通過(guò)以下命令生成動(dòng)態(tài)庫(kù)libsetsock.so
:
gcc -Wall -O2 -fPIC -D_GNU_SOURCE -shared -o libsetsock.so -Wl,-soname=libsetsock.so mysetsock.c
筆者使用Rust編寫(xiě)的簡(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)編程語(yǔ)言的Rust開(kāi)發(fā)效率可見(jiàn)一斑。不過(guò)該UDP服務(wù)器的讀操作是阻塞的,它會(huì)一直等待網(wǎng)絡(luò)數(shù)據(jù)的到來(lái):
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:58811
Rust調(diào)用C語(yǔ)言動(dòng)態(tài)庫(kù)中的函數(shù)
與C語(yǔ)言類似,Rust使用extern
關(guān)鍵字可實(shí)現(xiàn)對(duì)外部函數(shù)的聲明,不過(guò)在調(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)鏈接命令失敗:
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
這說(shuō)明雖然編譯是正常的,但在鏈接時(shí)找不到libsetsock.so
動(dòng)態(tài)庫(kù)。解決方法是在工程根目錄下增加一個(gè)編譯控制的Rust代碼,文件名為build.rs
,給出動(dòng)態(tài)庫(kù)所在的目錄:
fn main() { println!(r"cargo:rustc-link-search=native=/home/yejq/program/rust-lang/socket_udp"); }
再次執(zhí)行cargo build
編譯工程,鏈接就能成功了;使用patchelf
和nm
等命令行工具察看,生成的可執(zhí)行文件依賴了C語(yǔ)言編寫(xiě)的動(dòng)態(tài)庫(kù)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語(yǔ)言庫(kù)
以上我們用C語(yǔ)言編寫(xiě)了簡(jiǎn)單的動(dòng)態(tài)庫(kù),導(dǎo)出了一個(gè)可設(shè)置套接字讀超時(shí)的函數(shù)。這個(gè)功能過(guò)于簡(jiǎn)單,費(fèi)大周折編寫(xiě)一個(gè)動(dòng)態(tài)庫(kù)顯得得不償失。另一個(gè)解決方案是直接使用Rust官方提供的C語(yǔ)言庫(kù),該庫(kù)提供了很多變量和函數(shù)(與glibc提供的宏定義和庫(kù)函數(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語(yǔ)言庫(kù)的依賴,這里筆者使用的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語(yǔ)言庫(kù)提供的函數(shù)也需要用到unsafe
代碼塊;而工程根目錄下的編譯相關(guān)的控制代碼build.rs
就不再需要了;編譯生成的UDP服務(wù)器也會(huì)在5秒無(wú)數(shù)據(jù)時(shí)退出。最后,能夠調(diào)用C語(yǔ)言編寫(xiě)的動(dòng)態(tài)庫(kù),意味著使用Rust語(yǔ)言來(lái)進(jìn)行嵌入式系統(tǒng)軟件的開(kāi)發(fā),是一種具備可行性的技術(shù)方案。
原文鏈接:https://blog.csdn.net/yeholmes/article/details/118660405
相關(guān)推薦
- 2023-02-26 nvidia-smi命令詳解和一些高階技巧講解_linux shell
- 2023-04-07 關(guān)于vector的常見(jiàn)用法詳解_C 語(yǔ)言
- 2022-10-26 Python如何用NumPy讀取和保存點(diǎn)云數(shù)據(jù)_python
- 2022-03-16 詳解C語(yǔ)言在STM32中的內(nèi)存分配問(wèn)題_C 語(yǔ)言
- 2023-06-17 關(guān)于生產(chǎn)消費(fèi)者模型中task_done()的具體作用_python
- 2022-12-05 Windows的sc命令詳解(sc命令用法)_DOS/BAT
- 2022-05-22 Docker容器之間數(shù)據(jù)傳輸?shù)膶?shí)現(xiàn)_docker
- 2022-04-28 python導(dǎo)入導(dǎo)出redis數(shù)據(jù)的實(shí)現(xiàn)_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支