日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Rust應用調用C語言動態庫的操作方法_Rust語言

作者:塵觴葉 ? 更新時間: 2023-03-23 編程語言

外部功能接口FFI

雖然高級(腳本)編程語言的功能豐富,表達能力強,但對底層的一些特殊操作的支持并不完善,就需要以其他編程語言來實現。調用其他編程語言的接口,被稱為Foreign Function Interface,直譯為外部功能接口。該接口通常是調用C語言實現的外部功能模塊,因為C語言接近于全能,幾乎任何功能都能夠實現;正如同使用匯編語言也可以實現很多功能一樣,但開發效率低下。很多腳本語言提供了FFI功能,例如Python、PHP和JIT版本的Lua解析器等。同樣的,Rust也提供了FFI接口,作為標準庫中一個功能模塊;但本文不會討論該模塊的使用方法。本文記錄了筆者編寫一個簡單的C語言動態庫,并通過Rust調用動態庫導出的函數;另外,筆者直接使用Rust官方提供的libc庫,直接替代筆者編寫的C語言動態庫,以避免重復造輪子。

UDP套接字的讀超時

Rust標準庫中的UDP網絡功能,提供了設置套接字讀超時的函數,set_read_timeout,了解C語言網絡編譯的開發人員都知道,相應的底層調用為setsockopt(SO_RCVTIMEO)。假設Rust標準庫中UDP模塊未提供該函數,就需要編寫C語言代碼,將其編譯成一個動態庫,嘗試將Rust鏈接到該庫,并調用其中定義的函數了。筆者編寫的代碼如下:

#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;
}

通過以下命令生成動態庫libsetsock.so

gcc -Wall -O2 -fPIC -D_GNU_SOURCE -shared -o libsetsock.so -Wl,-soname=libsetsock.so mysetsock.c

筆者使用Rust編寫的簡單UDP服務端代碼如下:

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多行代碼實現了一個簡單的UDP服務端,作為系統編程語言的Rust開發效率可見一斑。不過該UDP服務器的讀操作是阻塞的,它會一直等待網絡數據的到來:

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調用C語言動態庫中的函數

與C語言類似,Rust使用extern關鍵字可實現對外部函數的聲明,不過在調用的代碼需要以unsafe關鍵字包成代碼塊。以下是筆者對上面的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編譯器在鏈接時加入-lsetsock鏈接選項。再次編譯,會發現鏈接命令失敗:

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

這說明雖然編譯是正常的,但在鏈接時找不到libsetsock.so動態庫。解決方法是在工程根目錄下增加一個編譯控制的Rust代碼,文件名為build.rs,給出動態庫所在的目錄:

fn main() {
    println!(r"cargo:rustc-link-search=native=/home/yejq/program/rust-lang/socket_udp");
}

再次執行cargo build編譯工程,鏈接就能成功了;使用patchelfnm等命令行工具察看,生成的可執行文件依賴了C語言編寫的動態庫libsetsock.so,并引用了其導出的函數符號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

此時運行簡單UDP服務端程序,可以確定我們增加的套接字讀超時功能能夠正常工作:

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" }

避免重復造輪子,使用Rust官方C語言庫

以上我們用C語言編寫了簡單的動態庫,導出了一個可設置套接字讀超時的函數。這個功能過于簡單,費大周折編寫一個動態庫顯得得不償失。另一個解決方案是直接使用Rust官方提供的C語言庫,該庫提供了很多變量和函數(與glibc提供的宏定義和庫函數、系統調用有很多重疊),可以直接添加setsockopt等系統調用的代碼。修改UDP服務器代碼:

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"

以上修改的代碼,與之前相同的是,調用C語言庫提供的函數也需要用到unsafe代碼塊;而工程根目錄下的編譯相關的控制代碼build.rs就不再需要了;編譯生成的UDP服務器也會在5秒無數據時退出。最后,能夠調用C語言編寫的動態庫,意味著使用Rust語言來進行嵌入式系統軟件的開發,是一種具備可行性的技術方案。

原文鏈接:https://blog.csdn.net/yeholmes/article/details/118660405

欄目分類
最近更新