雖然標準庫已經(jīng)封裝好了 TcpListener 和TcpStream 等基礎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_t
和 int
都對應成了 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)體,里面包含buf
和len
兩個字段缝呕,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)更簡單薪夕,就一個裸指針和長度脚草。
雖然RawVec
和Unique
在標準庫外部是不可見的赫悄,但我們還是能用一定的“手段”取出里面值,那就是定義一個內(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
屿聋、connect
、bind
藏鹊、listen
润讥、accept
、close
盘寡、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ù)竭宰,read
和 write
用于讀寫數(shù)據(jù)。另外還有三對高級I/O函數(shù)份招,recv/send
切揭、readv/writev
和recvmsg/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;
}
對于read
和 write
里的參數(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)用bind
和 connect
函數(shù)時,先要創(chuàng)建sockaddr_in
結(jié)構(gòu)體何暇,端口(sin_port
)和IP地址(s_addr
) 是網(wǎng)絡字節(jié)序(big endian)陶夜,于是我調(diào)用了u16
和u32
的to_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_in
和sockaddr_in6
是因為sockaddr_storage
這個通用結(jié)構(gòu)足夠大浇衬,能承載sockaddr_in
和sockaddr_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);
在read
和 write
時,同樣要類型轉(zhuǎn)換鸽照。
很多時候螺捐,類型根本“強不起來”。OK矮燎,這一節(jié)的內(nèi)容就先到這里定血。