網絡編程學習筆記二:Socket 讀寫函數

 

概要


一旦,我們建立好了tcp連接之後,我們就可以把得到的fd當作文件描述符來使用。

一般情況下,

(1) send、recv 函數在TCP協議下使用,當你對於數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動爲之加上目地和源地址信息。

(a) 在Unix系統下,如果send在等待協議傳送數據時網絡斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
(b) 在Unix系統下,如果recv函數在等待協議接收數據時網絡斷開了,那麼調用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

(2) sendto、recvfrom 函數在UDP協議下使用,但是如果在 TCP 中 connect 函數調用後也可以用,不過用的很少。

 

 

1 read 和 write 函數

網絡程序裏最基本的函數就是 read 和 write 函數了。

 

1.1 寫函數 write

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

write 函數將 buf 中的 nbytes 字節內容寫入文件描述符 fd 。成功時返回寫的字節數。失敗時返回-1, 並設置 errno 變量。在網絡程序中,當我們向套接字文件描述符寫時有兩可能。

(1) write的返回值大於0, 表示寫了部分或者是全部的數據。這樣我們用一個while循環來不停的寫入,但是循環過程中的buf參數和nbyte參數得由我們來更新。也就是說,網絡寫函數是不負責將全部數據寫完之後在返回的。

(2) 返回的值小於0,此時出現了錯誤。我們要根據錯誤類型來處理。如果錯誤爲EINTR表示在寫的時候出現了中斷錯誤。如果爲EPIPE表示網絡連接出現了問題(對方已經關閉了連接)。

爲了處理以上的情況,我們自己編寫一個寫函數來處理這幾種情況.

int my_write(int fd, void *buffer, int length)
{
    int bytes_left = 0;
    int written_bytes = 0;
    char *ptr = NULL;

    ptr = buffer;
    bytes_left = length;
    
    while (bytes_lef > 0 )
    {
            
        written_bytes = write(fd, ptr, bytes_left);
        if(written_bytes <= 0)
        {       
            if(errno == EINTR)
            {
                written_bytes = 0;
            }
            else
            {
                return -1;
            }
        }
        
        bytes_left -= written_bytes;
        ptr += written_bytes;
    }
    
    return 0;
}

 


1.2 讀函數 read

ssize_t read(int fd, void *buf, size_t nbyte);

read 函數是負責從 fd 中讀取內容。

(1) 當讀成功時, read返回實際所讀的字節數, 如果返回的值是0 表示已經讀到文件的結束了。

(2) 小於0表示出現了錯誤。如果錯誤爲 EINTR 說明讀是由中斷引起 的, 如果是 ECONNREST 表示網絡連接出了問題。和上面一樣, 我們也寫一個自己的讀函數。

int my_read(int fd, void *buffer, int length)
{

    int bytes_left = 0;
    int bytes_read = 0;
    char *ptr = NULL;
      
    bytes_left = length;
    while(bytes_left > 0)
    {
        bytes_read = read(fd, ptr, bytes_read);
        if(bytes_read < 0)
        {
            if(errno == EINTR)
            {
                bytes_read=0;
            }
            else
            {
                return -1;
            }
        }
        else if(bytes_read == 0)
        {
            break;
        }
        
        bytes_left -= bytes_read;
        ptr += bytes_read;
    }
    return (length - bytes_left);
}

 


1.3 數據的傳遞

有了上面的兩個函數, 我們就可以向客戶端或者是服務端傳遞數據了。比如我們要傳遞一個結構, 可以使用如下方式

struct my_struct my_struct_client;
write(fd, (void *)&my_struct_client, sizeof(struct my_struct);

char buffer[sizeof(struct my_struct)];
struct *my_struct_server = NULL;
read(fd, (void *)buffer ,sizeof(struct my_struct));
my_struct_server = (struct my_struct *)buffer;  

 

在網絡上傳遞數據時我們一般都是把數據轉化爲char類型的數據傳遞.接收的時候也是一樣的注意的是我們沒有必要在網絡上傳遞指針(因爲傳遞指針是沒有任何意義的,我們必須傳遞指針所指向的內容)。

 


2 recv 和 send 函數

 

2.1 概念

recv 和 send 函數提供了和 read 和 write 差不多的功能。不過它們提供了第四個參數來控制讀寫操作。一般第四個參數設置爲0即可。

 

int recv(int sockfd, void *buf, int len, int flags);
int send(int sockfd, void *buf, int len, int flags);

 

前面的三個參數和read,write一樣,第四個參數可以是0或者是以下的組合
_______________________________________________________________
| MSG_DONTROUTE  | 不查找表
| MSG_OOB        | 接受或者發送帶外數據
| MSG_PEEK       | 查看數據,並不從系統緩衝區移走數據
| MSG_WAITALL    | 等待所有數據 
|--------------------------------------------------------------


(1) recv 對應的flags有3個選項

(a) MSG_PEEK :查看數據,並不從系統緩衝區移走數據。
(b) MSG_WAITALL :等待所有數據,等到所有的信息到達時才返回,使用它時,recv 返回一直阻塞,直到指定的條件滿足時,或者發生錯誤。
(c) MSG_OOB :接受或者發送帶外數據。
 


(2) send 對應的flags有2個選項
(a) MSG_DONTROUTE :不查找表,它告訴ip,目的主機在本地網絡上,沒必要查找表。(一般用在網絡診斷和路由程序裏面)。

(b) MSG_OOB :接受或者發送帶外數據。

 

 


2.2 分析

//! recv 在 read 上增加了第四個參數, MSG_PEEK 是不清空緩衝區
//! 這個函數的作用相當是先去瞟一眼緩衝區,看見有多少字符,返回這個字符數

ssize_t recv_peek(int sockfd ,void *buf, size_t len)
{
    while(1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK);
        if(ret == -1 && errno == EINTR)
        {
            continue;
        }
        return ret;
    }
}

 

 

 

2.3 注意事項

 

(1) 在Unix系統下,如果send在等待協議傳送數據時網絡斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

(2) 在Unix系統下,如果recv函數在等待協議接收數據時網絡斷開了,那麼調用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

(3) send、recv 函數在TCP協議下使用,當你對於數據報 socket 調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動爲之加上目地和源地址信息。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文轉自:

https://blog.csdn.net/petershina/article/details/7946615

https://blog.csdn.net/zhubosa/article/details/38295571

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章