Rust 實戰(zhàn) - 使用套接字聯(lián)網(wǎng)API (一)

雖然標準庫已經(jīng)封裝好了 TcpListenerTcpStream 等基礎api,但作為Rust 的愛好者竭鞍,我們可以去一探究竟板惑。本文假設你已經(jīng)對 Rust 和 Linux 操作系統(tǒng)有了一定了解。

在 Linux 上 Rust 默認會鏈接的系統(tǒng)的 libc 以及一些其他的庫偎快,這就意味著冯乘,你可以直接使用libc中的函數(shù)。比如晒夹,你可以使用 gethostname 獲取你電腦的 "hostname":

use std::os::raw::c_char;
use std::ffi::CStr;

extern {
    pub fn gethostname(name: *mut c_char, len: usize) -> i32;
}

fn main() {
    let len = 255;
    let mut buf = Vec::<u8>::with_capacity(len);
    let ptr = buf.as_mut_ptr() as *mut c_char;

    unsafe {
        gethostname(ptr, len);
        println!("{:?}", CStr::from_ptr(ptr));
    }
}

解釋一下上面的代碼裆馒。

extren 表示“外部塊(External blocks)”饵骨,用來申明外部非 Rust 庫中的符號沪伙。我們需要使用 Rust 以外的函數(shù),比如 libc 唬血,就需要在 extren 中將需要用到的函數(shù)定義出來响逢,然后就可以像使用本地函數(shù)一樣使用外部函數(shù),編譯器會負責幫我們轉(zhuǎn)換棕孙,是不是很方便呢舔亭。但是,調(diào)用一個外部函數(shù)是unsafe的蟀俊,編譯器不能提供足夠的保證钦铺,所以要放到unsafe塊中。

如果外部函數(shù)有可變參數(shù)肢预,可以這么申明:

extern {
    fn foo(x: i32, ...);
}

不過 Rust 中的函數(shù)目前還不支持可變參數(shù)矛洞。

實際上,這里應該是 extern "C" { .. },因為默認值就是"C"沼本,我們就可以將其省略噩峦。還有一些其他的可選值,因為這里不會用到抽兆,暫且不討論识补,你可以去這兒這兒查看。

再來說說類型辫红∑就浚“gethostname” 函數(shù)在 C 頭文件中的原型是:

int gethostname(char *name, size_t len);

在 Linux 64位平臺上,C中的int對應于Rust中的int贴妻,size_t對應Rust中的usize切油,但C中的char與Rust中的char是完全不同的,C中的char始終是i8或者u8名惩,而 Rust 中的char是一個unicode標量值澎胡。你也可以去標準庫查看。對于指針绢片,Rust 中的裸指針 與C中的指針幾乎是一樣的滤馍,Rust的*mut對應C的普通指針,*const 對應C的const指針底循。因此我們將類型一一對應巢株,函數(shù)的參數(shù)名稱不要求一致。

pub fn gethostname(name: *mut i8, len: usize) -> i32;

但是熙涤,我們后面會使用CStr::from_ptr()將C中的字符串轉(zhuǎn)換為 Rust 本地字符串阁苞,這個函數(shù)的定義是:

pub unsafe fn from_pt<'a>(ptr: *const c_char) -> &'a CStr

為了“好看”一點,我就寫成了c_char祠挫,但是那槽,c_char只是i8的別名,你寫成i8也沒有問題的等舔。

type c_char = i8;

你可以看這里骚灸。

不過,如果你要是考慮跨平臺的話慌植,可能需要吧 i32 換成 std::os::raw::c_int甚牲,并不是所有平臺上C中的int都對應Rust中的i32。不過蝶柿,如果你沒有一一對應類型丈钙,一定程度上是可行的,如果沒有發(fā)生越界的話交汤。比如像這樣:

use std::os::raw::c_char;
use std::ffi::CStr;

extern {
    pub fn gethostname(name: *mut c_char, len: u16) -> u16;
}

fn main() {
    let len = 255;
    let mut buf = Vec::<u8>::with_capacity(len);
    let ptr = buf.as_mut_ptr() as *mut c_char;

    unsafe {
        gethostname(ptr, len as u16);
        println!("{:?}", CStr::from_ptr(ptr));
    }
}

我把 size_tint 都對應成了 u16雏赦,這段代碼是可以編譯通過,并正確輸出你的hostname的,但我建議星岗,你最好是將類型一一對應上填大,以減少一些不必要的麻煩。當然伍茄,你把那個 *mut c_char 換成 *mut i32栋盹,也沒問題,反正都是個指針敷矫,你可以試試:

use std::os::raw::c_char;
use std::ffi::CStr;

extern {
    pub fn gethostname(name: *mut i32, len: u16) -> u16;
}

fn main() {
    let len = 255;
    let mut buf = Vec::<u8>::with_capacity(len);
    let ptr = buf.as_mut_ptr() as *mut i32;

    unsafe {
        gethostname(ptr, len as u16);
        println!("{:?}", CStr::from_ptr(ptr as *const i8));
    }
}

你還可以把 Vec::<u8>換成Vec::<i32> 看看結(jié)果例获。

int gethostname(char *name, size_t len) 這個函數(shù),是接收一個char數(shù)組和數(shù)組長度曹仗,也可以說成接收緩沖區(qū)和接收緩沖區(qū)的最大長度榨汤。我是創(chuàng)建了一個容量為255的Vec<u8>,將其可變指針轉(zhuǎn)換為裸指針怎茫。你也可以創(chuàng)建可以長度為255的u8數(shù)組收壕,也沒有問題:

    let len = 255;
    let mut buf = [0u8; 255];
    let ptr = buf.as_mut_ptr() as *mut i32;

    unsafe {
        gethostname(ptr, len as u16);
        println!("{:?}", CStr::from_ptr(ptr as *const i8));
    }

為什么這樣可以,因為Rust的Slice和Vec的底層內(nèi)存布局轨蛤,跟C是一樣的蜜宪。(注意,Rust中Slice與Array的關(guān)系祥山,就像&str與str的關(guān)系)圃验。我們可以看看Vec和Slice在源碼中的定義:

pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}

pub struct RawVec<T, A: Alloc = Global> {
    ptr: Unique<T>,
    cap: usize,
    a: A,
}

pub struct Unique<T: ?Sized> {
    pointer: *const T,
    _marker: PhantomData<T>,
}

struct FatPtr<T> {
    data: *const T,
    len: usize,
}

Vec是一個結(jié)構(gòu)體,里面包含buflen兩個字段缝呕,len用來表示Vec的長度澳窑,buf又指向另一個結(jié)構(gòu)體RawVec,其中有三個字段供常,第三個字段a是一個Tarit摊聋,不占內(nèi)存。cap用來表示Vec的容量栈暇,ptr指向另一個結(jié)構(gòu)體Unique麻裁,其中的pointer字段就是一個裸指針了,_marker是給編譯器看的一個標記源祈,也不占內(nèi)存煎源,暫時不討論這個,你可以去看文檔新博。Slice的結(jié)構(gòu)更簡單薪夕,就一個裸指針和長度脚草。

雖然RawVecUnique在標準庫外部是不可見的赫悄,但我們還是能用一定的“手段”取出里面值,那就是定義一個內(nèi)存布局跟Vec一樣的結(jié)構(gòu)體,“強行”轉(zhuǎn)換埂淮。

#[derive(Debug)]
struct MyVec<T> {
    ptr: *mut T,
    cap: usize,
    len: usize
}

我定義了一個叫做MyVec的結(jié)構(gòu)體姑隅,忽略了Vec中兩個不占用內(nèi)存的字段,他們的內(nèi)存布局是相同的倔撞,在64位平臺上都是24(ptr占8個讲仰,另外兩個usize個8個)個字節(jié)。你可以試試:

#[derive(Debug)]
struct MyVec<T> {
    ptr: *mut T,
    cap: usize,
    len: usize
}

println!("{:?}", std::mem::size_of::<Vec<u8>>());
println!("{:?}", std::mem::size_of::<MyVec<u8>>());

我先創(chuàng)建一個Vec<u8>痪蝇,拿到Vec<u8>的裸指針*const Vec<u8>鄙陡,再將*const Vec<u8>轉(zhuǎn)換為*const MyVec<u8>,之后躏啰,解引用趁矾,就能得到MyVec<u8>了。不過给僵,解引裸指針是unsafe的毫捣,要謹慎!!! 你還可以看看標準庫中講述pointer的文檔。

fn main() {
    let vec = Vec::<u8>::with_capacity(255);

    println!("vec ptr: {:?}", vec.as_ptr());

    #[derive(Debug)]
    struct MyVec<T> {
        ptr: *mut T,
        cap: usize,
        len: usize
    }

    let ptr: *const Vec<u8> = &vec;

    let my_vec_ptr: *const MyVec<u8> = ptr as _;

    unsafe {
        println!("{:?}", *my_vec_ptr);
    }
}

然后編譯運行帝际,是否可以看到類似下面的輸出呢:

vec ptr: 0x557933de6b40
MyVec { ptr: 0x557933de6b40, cap: 255, len: 0 }

你可以看到蔓同,我們調(diào)用vec.as_ptr()得到的就是Vec內(nèi)部的那個裸指針。

對于std::mem::size_of 相等的兩個類型蹲诀,你也可以使用std::mem::transmute 這個函數(shù)轉(zhuǎn)換斑粱,跟上面的通過裸指針間接轉(zhuǎn)換,幾乎是等效的侧甫,只是會多加一個驗證珊佣,如果兩個類型size_of不相等的話,是無法通過編譯的披粟。這個函數(shù)是unsafe的咒锻。

你還可以繼續(xù)嘗試,比如把Vec<u8>轉(zhuǎn)換為長度為3(或者更小更大)的usize數(shù)組守屉,像是這樣:

fn main() {
    let vec = Vec::<u8>::with_capacity(255);

    println!("vec ptr: {:?}", vec.as_ptr());

    let ptr: *const Vec<u8> = &vec;

    unsafe {
        let aaa_ptr: *const [usize; 2] = ptr as _;
        println!("{:?}", (*aaa_ptr)[0] as *const u8);
    }
}

不過惑艇,由于Rust中Vec的擴容機制,這段代碼是存在一定問題的:

fn main() {
    let len = 255;
    let mut buf = Vec::<u8>::with_capacity(len);
    let ptr = buf.as_mut_ptr() as *mut c_char;

    unsafe {
        gethostname(ptr, len);
        println!("{:?}", CStr::from_ptr(ptr));
    }

    println!("{:?}", buf);
}

雖然獲取到了正確的主機名拇泛,但是之后你打印buf會發(fā)現(xiàn)滨巴,buf是空的,這個問題留給你去探究俺叭。

你已經(jīng)看到恭取,Rust已經(jīng)變得“不安全”,這又不小心又引入了另一個話題--《 Meet Safe and Unsafe》熄守。不過蜈垮,還是盡快回歸正題耗跛,等之后有機會再說這個話題。

說起套接字API攒发,主要包括TCP调塌、UDP、SCTP相關(guān)的函數(shù)惠猿,I/O復用函數(shù)和高級I/O函數(shù)羔砾。其中大部分函數(shù)Rust標準里是沒有的,如果標準庫不能滿足你的需求偶妖,你可以直接調(diào)用libc中的函數(shù)姜凄。實際上,標準庫中趾访,網(wǎng)絡這一塊檀葛,也基本是對libc中相關(guān)函數(shù)的封裝。

先從TCP開始腹缩。TCP套接字編程主要會涉及到socket屿聋、connectbind藏鹊、listen润讥、acceptclose盘寡、getsockname楚殿、getpeername等函數(shù)。先來看看這些函數(shù)的定義:

// socket 函數(shù)用來指定期望的通信協(xié)議類型竿痰,并返回套接字描述符
int socket(int family, int type, int protocol); // 成功返回監(jiān)聽描述符脆粥。用來設置監(jiān)聽,出錯為-1
// family是表示socket使用的協(xié)議類型影涉,對于TCP变隔,通常設置為 `AF_INET` 或`AF_INET6`,表示`IPv4`和`IPv6`
// type是創(chuàng)建的套接字類型蟹倾,TCP是字節(jié)流套接字匣缘,所以這里設置為`SOCK_STREAM`,可選的值還有
// `SOCK_DGRAM`用于UDP鲜棠,`SOCK_SEQPACKET`用于SCTP
// protocol協(xié)議的標識肌厨,可以設置為0,讓系統(tǒng)選擇默認值豁陆「贪郑可選的值有`IPPROTO_TCP`、`IPPROTO_UDP`盒音、`IPPROTO_SCTP`

// connect 函數(shù)被客戶端用來聯(lián)立與TCP服務器的連接
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); // 成功返回0 表鳍,出錯為-1
// sockfd 是由 socket 函數(shù)返回的套接字描述符何址,第二和第三個參數(shù)分別指向一個指向套接字地址結(jié)構(gòu)的指針和該指針的長度

// bind 函數(shù)把一個本地協(xié)議地址賦予一個套接字。
int bind(int sockfd, const struct sockaddr *myaddr,  socklen_t addrlen); // 成功返回0 进胯,出錯為-1
// 第二個和第三個參數(shù)分別是指向特點于協(xié)議的地址結(jié)構(gòu)的指針和指針的長度

// listen 函數(shù)把一個未連接的套接字轉(zhuǎn)換成一個被動套接字,指示內(nèi)核應接受指向該套接字的連接請求原押。
int listen(int sockfd, int backlog); // 成功返回0 胁镐,出錯為-1
// 第二個參數(shù)指定內(nèi)核該為相應套接字排隊的最大連接個數(shù)。

// accept 函數(shù)由TCP服務器調(diào)用诸衔,用于從已完成連接的隊列頭返回下一個已完成的連接盯漂。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); // 成功返回非負描述符,錯誤返回-1
// 第二個和第三個參數(shù)用來返回客戶端的協(xié)議地址和該地址的大小

// close 用來關(guān)閉套接字笨农,并終止TCP連接
int close(int sockfd); // 成功返回0 就缆,出錯為-1

// getsockname 和 getpeername 函數(shù)返回與某個套接字關(guān)聯(lián)的本地協(xié)議地址和外地協(xié)議地址
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen); // 成功返回0 ,出錯為-1
int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addelen); // 成功返回0 谒亦,出錯為-1

還有一對常見的函數(shù)竭宰,readwrite 用于讀寫數(shù)據(jù)。另外還有三對高級I/O函數(shù)份招,recv/send切揭、readv/writevrecvmsg/sendmsg等需要的時候再加。

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

除了函數(shù)外锁摔,還有幾個常量和sockaddr這個結(jié)構(gòu)體廓旬。常量我們需要在Rust這邊定義出來,只定義出需要的:

const AF_INET: i32 = 2;
const AF_INET6: i32 = 10;
const SOCK_STREAM: i32 = 1;
const IPPROTO_TCP: i32 = 6;

除了sockaddr外谐腰,還有幾個與之相關(guān)的結(jié)構(gòu)體孕豹,他們在C中的定是:

struct sockaddr
{
    unsigned short    int sa_family; // 地址族
    unsigned char     sa_data[14];  // 包含套接字中的目標地址和端口信息
};

struct sockaddr_in
{
    sa_family_t       sin_family;
    uint16_t          sin_port;
    struct in_addr    sin addr;
    char              sin_zero[8];
};

struct in_addr
{
    In_addr_t  s_addr;
};

struct sockaddr_in6
{
    sa_family_t       sin_family;
    in_port_t         sin6_port;
    uint32_t          sin6_flowinfo;
    struct in6_addr   sin6_addr; 
    uint32_t          sin6_scope_id;
};

struct in6_addr
{
    uint8_t           s6_addr[16]
};

struct sockaddr_storage {
    sa_family_t       ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char              __ss_pad1[_SS_PAD1SIZE];
    int64_t           __ss_align;
    char              __ss_pad2[_SS_PAD2SIZE];
};

然后,需要在Rust中定義出布局相同的結(jié)構(gòu)體:

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr {
    pub sa_family: u16,
    pub sa_data: [c_char; 14],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr_in {
    pub sin_family: u16,
    pub sin_port: u16,
    pub sin_addr: in_addr,
    pub sin_zero: [u8; 8],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct in_addr {
    pub s_addr: u32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr_in6 {
    pub sin6_family: u16,
    pub sin6_port: u16,
    pub sin6_flowinfo: u32,
    pub sin6_addr: in6_addr,
    pub sin6_scope_id: u32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct in6_addr {
    pub s6_addr: [u8; 16],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr_storage {
    pub ss_family: u16,
    _unused: [u8; 126]
}

你需要在結(jié)構(gòu)體前面加一個#[repr(C)]標簽十气,以確保結(jié)構(gòu)體的內(nèi)存布局跟C一致励背,因為,Rust結(jié)構(gòu)體的內(nèi)存對齊規(guī)則砸西,可能跟C是不一樣的椅野。#[derive(Debug, Clone, Copy)] 不是必須的。對于最后一個結(jié)構(gòu)體sockaddr_storage籍胯,我也很迷竟闪,我不知道在Rust中如何定義出來,但是我知道它占128個字節(jié)杖狼,然后我就定義一個長度為126的u8數(shù)組炼蛤,湊夠128位。

接下來蝶涩,繼續(xù)把那幾個函數(shù)定義出來:

extern {
    pub fn socket(fanily: i32, ty: i32, protocol: i32) -> i32;
    pub fn connect(sockfd: i32, servaddr: *const sockaddr, addrlen: u32) -> i32;
    pub fn bind(sockfd: i32, myaddr: *const sockaddr, addrlen: u32) -> i32;
    pub fn listen(sockfd: i32, backlog: i32);
    pub fn accept(sockfd: i32, cliaddr: *mut sockaddr, addrlen: u32) -> i32;
    pub fn close(sockfd: i32) -> i32;
    pub fn getsockname(sockfd: i32, localaddr: *mut sockaddr, addrlen: *mut u32) -> i32;
    pub fn getpeername(sockfd: i32, peeraddr: *mut sockaddr, addrlen: *mut u32) -> i32;
    pub fn read(fd: i32, buf: *mut std::ffi::c_void, count: usize) -> isize;
    pub fn write(fd: i32, buf: *const std::ffi::c_void, count: usize) -> isize;
}

對于readwrite 里的參數(shù)buf類型void理朋, 可以使用標準庫提供的 std::ffi::c_void絮识,也可以是*mut u8/*const u8,像是下面這樣:

pub fn read(fd: i32, buf: *mut u8, count: usize) -> isize;
pub fn write(fd: i32, buf: *const u8, count: usize) -> isize;

或者嗽上,既然void本身是個“動態(tài)類型”次舌,也可以傳個其他類型的指針進去的,之后你可以試試兽愤,不過可能會有點危險彼念。

看看目前的代碼:

use std::os::raw::c_char;
use std::ffi::c_void;

pub const AF_INET: i32 = 2;
pub const AF_INET6: i32 = 10;
pub const SOCK_STREAM: i32 = 1;
pub const IPPRPTO_TCP: i32 = 6;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr {
    pub sa_family: u16,
    pub sa_data: [c_char; 14],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr_in {
    pub sin_family: u16,
    pub sin_port: u16,
    pub sin_addr: in_addr,
    pub sin_zero: [u8; 8],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct in_addr {
    pub s_addr: u32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sockaddr_in6 {
    pub sin6_family: u16,
    pub sin6_port: u16,
    pub sin6_flowinfo: u32,
    pub sin6_addr: in6_addr,
    pub sin6_scope_id: u32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct in6_addr {
    pub s6_addr: [u8; 16],
}

#[repr(C)]
#[derive(Clone, Copy)]
pub struct sockaddr_storage {
    pub ss_family: u16,
    _unused: [u8; 126]
}

extern {
    pub fn socket(fanily: i32, ty: i32, protocol: i32) -> i32;
    pub fn connect(sockfd: i32, servaddr: *const sockaddr, addrlen: u32) -> i32;
    pub fn bind(sockfd: i32, myaddr: *const sockaddr, addrlen: u32) -> i32;
    pub fn listen(sockfd: i32, backlog: i32);
    pub fn accept(sockfd: i32, cliaddr: *mut sockaddr, addrlen: *mut u32) -> i32;
    pub fn close(sockfd: i32) -> i32;
    pub fn getsockname(sockfd: i32, localaddr: *mut sockaddr, addrlen: *mut u32) -> i32;
    pub fn getpeername(sockfd: i32, peeraddr: *mut sockaddr, addrlen: *mut u32) -> i32;
    pub fn read(fd: i32, buf: *mut std::ffi::c_void, count: usize) -> isize;
    pub fn write(fd: i32, buf: *const std::ffi::c_void, count: usize) -> isize;
}

然后,我們可以寫一個簡單的服務器和客戶端程序:服務器監(jiān)聽一個地址浅萧,客戶端連接服務器逐沙,然后向服務器發(fā)送“Hello, server!”,服務器回應“Hi洼畅,client!”吩案,客戶端收到后斷開連接。

fn main() {
    use std::io::Error;
    use std::mem;
    use std::thread;
    use std::time::Duration;

    thread::spawn(|| {

        // server
        unsafe {
            let socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if socket < 0 {
                panic!("last OS error: {:?}", Error::last_os_error());
            }

            let servaddr = sockaddr_in {
                sin_family: AF_INET as u16,
                sin_port: 8080u16.to_be(),
                sin_addr: in_addr {
                    s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be()
                },
                sin_zero: mem::zeroed()
            };

            let result = bind(socket, &servaddr as *const sockaddr_in as *const sockaddr, mem::size_of_val(&servaddr) as u32);
            if result < 0 {
                println!("last OS error: {:?}", Error::last_os_error());
                close(socket);
            }

            listen(socket, 128);

            loop {
                let mut cliaddr: sockaddr_storage = mem::zeroed();
                let mut len = mem::size_of_val(&cliaddr) as u32;

                let client_socket = accept(socket, &mut cliaddr as *mut sockaddr_storage as *mut sockaddr, &mut len);
                if client_socket < 0 {
                    println!("last OS error: {:?}", Error::last_os_error());
                    break;
                }

                thread::spawn(move || {
                    loop {
                        let mut buf = [0u8; 64];
                        let n = read(client_socket, &mut buf as *mut _ as *mut c_void, buf.len());
                        if n <= 0 {
                            break;
                        }

                        println!("{:?}", String::from_utf8_lossy(&buf[0..n as usize]));

                        let msg = b"Hi, client!";
                        let n = write(client_socket, msg as *const _ as *const c_void, msg.len());
                        if n <= 0 {
                            break;
                        }
                    }

                    close(client_socket);
                });
            }

            close(socket);
        }

    });

    thread::sleep(Duration::from_secs(1));

    // client
    unsafe {
        let socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if socket < 0 {
            panic!("last OS error: {:?}", Error::last_os_error());
        }

        let servaddr = sockaddr_in {
            sin_family: AF_INET as u16,
            sin_port: 8080u16.to_be(),
            sin_addr: in_addr {
                s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be()
            },
            sin_zero: mem::zeroed()
        };

        let result = connect(socket, &servaddr as *const sockaddr_in as *const sockaddr, mem::size_of_val(&servaddr) as u32);
        if result < 0 {
            println!("last OS error: {:?}", Error::last_os_error());
            close(socket);
        }

        let msg = b"Hello, server!";
        let n = write(socket, msg as *const _ as *const c_void, msg.len());
        if n <= 0 {
            println!("last OS error: {:?}", Error::last_os_error());
            close(socket);
        }

        let mut buf = [0u8; 64];
        let n = read(socket, &mut buf as *mut _ as *mut c_void, buf.len());
        if n <= 0 {
            println!("last OS error: {:?}", Error::last_os_error());
        }

        println!("{:?}", String::from_utf8_lossy(&buf[0..n as usize]));

        close(socket);
    }
}

調(diào)用外部函數(shù)是unsafe的帝簇,我為了簡單省事徘郭,暫時把代碼放到了一個大的unsafe {} 中,之后我們再把他們封裝成safe的API丧肴。為了方便測試崎岂,我把服務器程序放到了一個線程里,然后等待1秒后闪湾,再讓客戶端建立連接冲甘。

std::io::Error::last_os_error 這個函數(shù),是用來捕獲函數(shù)操作失敗后途样,內(nèi)核反饋給我們的錯誤江醇。

在調(diào)用bindconnect 函數(shù)時,先要創(chuàng)建sockaddr_in結(jié)構(gòu)體何暇,端口(sin_port)和IP地址(s_addr) 是網(wǎng)絡字節(jié)序(big endian)陶夜,于是我調(diào)用了u16u32to_be()方法將其轉(zhuǎn)換為網(wǎng)絡字節(jié)序。u32::from_be_bytes 函數(shù)是將[127u8, 0u8, 0u8, 1u8] 轉(zhuǎn)換為u32整數(shù)裆站,由于我們看到的已經(jīng)是大端了条辟,轉(zhuǎn)換回去會變成小端,于是后面又調(diào)用了to_be()宏胯,你也可以直接u32::from_le_bytes([127, 0, 0, 1])羽嫡。然后使用了std::mem::zeroed 函數(shù)創(chuàng)建一個[0u8; 8] 數(shù)組,你也可以直接[0u8; 8]肩袍,在這里他們是等效的杭棵。接著,我們進行強制類型轉(zhuǎn)換氛赐,將&sockaddr_in 轉(zhuǎn)換為*const sockaddr_in類型魂爪,又繼續(xù)轉(zhuǎn)換為*const sockaddr先舷,如果你理解了一開始“gethostname”那個例子話,這里應該很好理解滓侍。這里還可以簡寫成&servaddr as *const _ as *const _蒋川,編譯器會自動推導類型。

在調(diào)用accept函數(shù)時撩笆,先創(chuàng)建了一個mut sockaddr_storage捺球,同樣進行類型轉(zhuǎn)換。之所以用sockaddr_storage 而不是sockaddr_insockaddr_in6是因為sockaddr_storage這個通用結(jié)構(gòu)足夠大浇衬,能承載sockaddr_insockaddr_in6等任何套接字的地址結(jié)構(gòu),因此餐济,我們?nèi)绻烟捉?code>bind到一個IPv6地址上的話耘擂,這里的代碼是不需要修改的。我還是用std::mem::zeroed 函數(shù)初始化sockaddr_storage絮姆,它的結(jié)構(gòu)我也很迷惑醉冤,所以就借助了這個函數(shù),這個函數(shù)是unsafe的篙悯,使用的時候要小心蚁阳。你也可以繼續(xù)嘗試這個函數(shù):

let mut a: Vec<u8> = unsafe { std::mem::zeroed() };
a.push(123);
println!("{:?}", a);

readwrite 時,同樣要類型轉(zhuǎn)換鸽照。

很多時候螺捐,類型根本“強不起來”。OK矮燎,這一節(jié)的內(nèi)容就先到這里定血。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诞外,隨后出現(xiàn)的幾起案子澜沟,更是在濱河造成了極大的恐慌,老刑警劉巖峡谊,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茫虽,死亡現(xiàn)場離奇詭異,居然都是意外死亡既们,警方通過查閱死者的電腦和手機濒析,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啥纸,“玉大人悼枢,你說我怎么就攤上這事∑⒉穑” “怎么了馒索?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵莹妒,是天一觀的道長。 經(jīng)常有香客問我绰上,道長旨怠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任蜈块,我火速辦了婚禮鉴腻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘百揭。我一直安慰自己爽哎,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布器一。 她就那樣靜靜地躺著课锌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祈秕。 梳的紋絲不亂的頭發(fā)上渺贤,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音请毛,去河邊找鬼志鞍。 笑死,一個胖子當著我的面吹牛方仿,可吹牛的內(nèi)容都是我干的固棚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼仙蚜,長吁一口氣:“原來是場噩夢啊……” “哼玻孟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳍征,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤黍翎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后艳丛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匣掸,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年氮双,在試婚紗的時候發(fā)現(xiàn)自己被綠了碰酝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡戴差,死狀恐怖送爸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤袭厂,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布墨吓,位于F島的核電站,受9級特大地震影響纹磺,放射性物質(zhì)發(fā)生泄漏帖烘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一橄杨、第九天 我趴在偏房一處隱蔽的房頂上張望秘症。 院中可真熱鬧,春花似錦式矫、人聲如沸乡摹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聪廉。三九已至,卻和暖如春氏义,著一層夾襖步出監(jiān)牢的瞬間锄列,已是汗流浹背图云。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工惯悠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竣况。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓克婶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丹泉。 傳聞我的和親對象是個殘疾皇子情萤,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 距離開學還有幾天,前兩天摹恨,剛從老家回來筋岛,奶奶扔給我一個大包裹,孩子寒假作業(yè)晒哄,我懵圈了……開啟了輔導之旅…… 回到老...
    曦zona閱讀 422評論 0 0
  • 日暖花開麗景天睁宰,千紅萬紫正暄妍。青苗潤雨拔節(jié)起寝凌,綠柳乘風動韻添柒傻。爭樹早鶯方細語,繞梁新燕又呢喃较木。勸君莫負良辰意红符,好...
    醉清風_于叢洋閱讀 776評論 0 19
  • 從小有很多夢想,要做科學家,要做警察预侯,想做醫(yī)生致开,想做英雄......正如老男孩那首打動無數(shù)人的歌:‘歲月是把無情...
    乜石頭閱讀 460評論 2 3
  • 《產(chǎn)品日思錄》是我個人公眾號上每天更新的系列文章,記錄了我在做產(chǎn)品過程中的思考雌桑、總結(jié)喇喉、經(jīng)驗積累,也希望在這里和簡書...
    s2dongman申悅閱讀 672評論 0 1
  • 賣技能是首選P?印<鸺肌! 現(xiàn)如今是是付費的時代耍目,擁有一技之長膏斤,就擁有一筆潛力巨大的財富。 1.寫作/自媒體 自媒體養(yǎng)活了...
    按住了北鼻閱讀 1,650評論 0 25