linux 中的奇妙錯誤
1/無名管道
當(dāng)打開一個管道之后巩踏,將管道的讀端關(guān)閉音榜,然后在向其中寫入內(nèi)容稚叹,進程會被信號SIGPIPE殺死动雹,基本無調(diào)試信息輸出债朵。
int main(int argc, char const *argv[])
{
int fd[2];
int ret=pipe(fd);
//關(guān)閉讀端
close(fd[0]);
// 向管道中寫入東西
write(fd[1],"string",strlen("string"));
// 這里進程會收到 13:SIGPIPE 進程會被殺死
printf("這里不會輸出\n");
return 0;
}
2/有名管道
有名管道凫海,如果使用open函數(shù)以只讀的方式打開的時候其會阻塞馆类,等待另一個進程以寫的方式打開這個管道的時候亩码,程序才會繼續(xù)執(zhí)行。
下面兩個程序 雖然代碼結(jié)構(gòu)上看起來很整齊茸塞,可以打開兩個半雙工的管道躲庄,構(gòu)成一個全雙工的通信,但由于 其都是先進行只讀打開導(dǎo)致兩個程序都阻塞在open函數(shù)這里無法繼續(xù)钾虐。
int fd_read=open(fifo1,O_RDONLY);
// 程序會阻塞在這里噪窘,不會繼續(xù)執(zhí)行下去
int fd_write=open(fifo2,O_WRONLY);
int fd_read=open(fifo2,O_RDONLY);
// 程序會阻塞在這里,不會繼續(xù)執(zhí)行下去
int fd_write=open(fifo1,O_WRONLY);
3/向一個關(guān)閉的描述符寫東西
如果向一個關(guān)閉的描述符寫東西效扫,進程會被信號殺死
例如管道 網(wǎng)絡(luò)描述符倔监,所以一般都是寫端主動關(guān)閉,讀端口被動關(guān)閉菌仁。
4/網(wǎng)絡(luò)編程中結(jié)構(gòu)體sockaddr_in結(jié)構(gòu)體的填充
mac 手冊中inet(4) 結(jié)構(gòu)體是這么填充的
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
但POSIX定義確實
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
i_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
這意味著如果在定義 sockaddr_in 變量的時候使用 下面這種初始化方式
struct sockaddr_in s{sin_family, sin_port, sin_addr};
則運行可能會出現(xiàn)錯誤浩习!
由于結(jié)構(gòu)體中成都是 _t類型(即 int,long,int,short,long long) 在一定情況下 就算填充位置錯誤,但由于類型匹配济丘,編譯器可能不會發(fā)出警告谱秽,而且由于這樣使用大多數(shù)情況下都是正確的(即便是POSIX標準,但好像linux并沒有遵守)摹迷,但如果發(fā)生錯誤弯院,一般很難發(fā)現(xiàn).
所以應(yīng)該這么定義
struct sockaddr_in s;
s->sin_family = sin_family;
s->sin_port = sin_port;
s->sin_addr = sin_addr;
5/linux 網(wǎng)絡(luò)編程 write read recv send
建立好了TCP連接之后,我們就可以把得到的套接字當(dāng)做文件描述符來使用泪掀,由此,想到了網(wǎng)絡(luò)程序里面的基本的讀寫函數(shù)read和write函數(shù)颂碘。
Write函數(shù)
Ssize_t write(int fd,const void *buf,size_t nbytes);
Write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫入到文件描述符中异赫,成功返回寫的字節(jié)數(shù)椅挣,失敗返回-1.并設(shè)置errno變量。在網(wǎng)絡(luò)程序中塔拳,當(dāng)我們向套接字文件描述舒服寫數(shù)據(jù)時有兩種可能:
1鼠证、write的返回值大于0,表示寫了部分數(shù)據(jù)或者是全部的數(shù)據(jù)靠抑,這樣用一個while循環(huán)不斷的寫入數(shù)據(jù)量九,但是循環(huán)過程中的buf參數(shù)和nbytes參數(shù)是我們自己來更新的,也就是說颂碧,網(wǎng)絡(luò)編程中寫函數(shù)是不負責(zé)將全部數(shù)據(jù)寫完之后再返回的荠列,說不定中途就返回了!
2载城、返回值小于0肌似,此時出錯了,需要根據(jù)錯誤類型進行相應(yīng)的處理诉瓦。
如果錯誤是EINTR表示在寫的時候出現(xiàn)了中斷錯誤川队,如果是EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題。
Read函數(shù)
Ssize_t read(int fd,void *buf,size_t nbyte)
Read函數(shù)是負責(zé)從fd中讀取內(nèi)容睬澡,當(dāng)讀取成功時固额,read返回實際讀取到的字節(jié)數(shù),如果返回值是0煞聪,表示已經(jīng)讀取到文件的結(jié)束了斗躏,小于0表示是讀取錯誤。
如果錯誤是EINTR表示在寫的時候出現(xiàn)了中斷錯誤米绕,如果是EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題瑟捣。
有了上面的兩個函數(shù),我們就可以向客戶端或者是服務(wù)器端進行數(shù)據(jù)傳輸了栅干!比如我要傳送一個結(jié)構(gòu)體迈套,可以使用下面的方法
客戶端向服務(wù)器:
Struct student stu;
Write(sock,(void *)&stu,sizeof(struct student));
服務(wù)器讀:
Char buffer[sizeof(struct student)];
Struct *my_student;
Read(sock,(void *)buffer,sizeof(struct student));
My_student=(struct student)buffer;
在網(wǎng)絡(luò)上傳遞數(shù)據(jù)時,我們一般把數(shù)據(jù)轉(zhuǎn)換為char類型碱鳞,接收的時候也是一樣的的桑李。沒必要在網(wǎng)絡(luò)上傳遞指針。
Recv函數(shù)和send函數(shù)
Recv函數(shù)和read函數(shù)提供了read和write函數(shù)一樣的功能窿给,不同的是他們提供了四個參數(shù)贵白。
Int recv(int fd,void *buf,int len,int flags)
Int send(int fd,void *buf,int len,int flags)
前面的三個參數(shù)和read、write函數(shù)是一樣的崩泡。第四個參數(shù)可以是0或者是一下組合:
MSG_DONTROUTE
:不查找表
是send函數(shù)使用的標志禁荒,這個標志告訴IP,目的主機在本地網(wǎng)絡(luò)上角撞,沒有必要查找表呛伴,這個標志一般用在網(wǎng)絡(luò)診斷和路由程序里面勃痴。
MSG_OOB
:接受或者發(fā)生帶外數(shù)據(jù)
表示可以接收和發(fā)送帶外數(shù)據(jù)。
MSG_PEEK
:查看數(shù)據(jù)热康,并不從系統(tǒng)緩沖區(qū)移走數(shù)據(jù)
是recv函數(shù)使用的標志沛申,表示只是從系統(tǒng)緩沖區(qū)中讀取內(nèi)容,而不清楚系統(tǒng)緩沖區(qū)的內(nèi)容姐军。這樣在下次讀取的時候铁材,依然是一樣的內(nèi)容,一般在有過個進程讀寫數(shù)據(jù)的時候使用這個標志奕锌。
MSG_WAITALL
:等待所有數(shù)據(jù)
是recv函數(shù)的使用標志著觉,表示等到所有的信息到達時才返回,使用這個標志的時候歇攻,recv返回一直阻塞固惯,直到指定的條件滿足時,或者是發(fā)生了錯誤缴守。
6/信號缺陷
sinal 信號有一的機制是 通過某個信號的標志位來判斷葬毫,信號是否發(fā)生所以當(dāng)多個相同信號同時發(fā)生的時候,信號處理函數(shù)可能調(diào)用的次數(shù)會比預(yù)想的次數(shù)要少很多
同時信號中盡量不要使用公共資源屡穗,和線程一樣贴捡,信號的處理函數(shù)同樣會對其他程序的運行造成影響,例如如果在
signal_handle
中使用printf
村砂,可能在主程序中printf運行一半的時候烂斋,被中斷,那么結(jié)果可能會輸出意料之外的數(shù)據(jù)础废。