網絡socket編程相關函數

網絡socket編程相關函數

socket函數

socket函數爲系統創建一個套接字,應用程序通過訪問該函數創建的套接字實現對數據的發送和接收。
函數原型如下:如果函數調用成功,會返回一個標識這個套接字的文件描述符,失敗的時候返回-1

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
  • 參數domain:函數socket()的參數domain用於設置網絡通信的域,函數socket()根據這個參數選擇通信協議的族。通信協議族在文件sys/socket.h中定義。
domain 含義
AF_INET IPV4 Internet協議
AF_INET6 IPV4 Internet協議
AF_UNIX 同一主機進程間通信
  • 參數type:用於設置套接字通信的類型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(數據包套接字)等。
type類型 含義
SOCK_STREAM Tcp連接,提供序列化的、可靠的、雙向連接的字節流。支持帶外數據傳輸
SOCK_DGRAM 支持UDP連接(無連接狀態的消息)
SOCK_SEQPACKET 序列化包,提供一個序列化的、可靠的、雙向的基本連接的數據傳輸通道,數據長度定常。每次調用讀系統調用時數據需要將全部數據讀出
SOCK_RAW RAW類型,提供原始網絡協議訪問
SOCK_RDM 提供可靠的數據報文,不過可能數據會有亂序
SOCK_PACKET 這是一個專用類型,不能呢過在通用程序中使用

並不是所有的協議族都實現了這些協議類型,例如,AF_INET協議族就沒有實現SOCK_SEQPACKET協議類型。

  • 參數protocol:用於制定某個協議的特定類型,通常某協議中只有一種特定類型,這樣protocol參數僅能設置爲0;但是有些協議有多種特定的類型,就需要設置這個參數來選擇特定的類型。

  • 返回值 errno:函數socket()並不總是執行成功,有可能會出現錯誤,錯誤的產生有多種原因,可以通過errno獲得:

含義
EACCES 沒有權限建立制定的domain的type的socket
EAFNOSUPPORT 不支持所給的地址類型
EINVAL 不支持此協議或者協議不可用
EMFILE 進程文件表溢出
ENFILE 已經達到系統允許打開的文件數量,打開文件過多
ENOBUFS/ENOMEM 內存不足。socket只有到資源足夠或者有進程釋放內存
EPROTONOSUPPORT 制定的協議type在domain中不存在

實例:創建一個流式套接字

int sock = socket(AF_INET, SOCK_STREAM, 0);

bind函數

bind函數用於將套接字與網絡地址綁定,一般適用於未連接的數據報或流類套接口,在connect()或listen()調用前使用。
其原型如下:如果成功返回0;如果出錯返回-1

#include <sys/types.h>
#include <sys/socket.h>
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
  • 參數sockfd爲socket函數創建的套接字
  • 參數my_addr爲sockaddr * 類型的網絡地址結構體
    sockaddr定義如下:
struct sockaddr{
u_short sa_family;
char sa_data[14];
};
  • 參數addrlen爲網絡地址的長度

listen函數

listen函數僅用在服務端程序,用來監聽套接字的狀態。
其原型如下:

#include <sys/socket.h>
int listen(int sock_fd, int backlog);
  • 參數sock_fd爲將要監聽的套接字
  • 參數 backlog 指定同時能處理的最大連接要求, 如果連接數目達此上限則client 端將收到ECONNREFUSED 的錯誤.
  • 返回值:成功則返回0, 失敗返回-1, 錯誤原因存於errno
錯誤信息 含義
EBADF 參數sockfd 非合法socket 處理代碼
EACCESS EACCESS
EOPNOTSUPP 指定的socket 並未支援listen 模式.

select函數

select函數是sock網絡編程中比較重要的一個函數之一,其主要作用是多路複用。舉個例子:

read(sock_fd1,buff,sizeof(buff));
read(sock_fd2,buff,sizeof(buff));

我們知道read ,recv等函數都是阻塞函數,在sock程序中,如果有兩個套接字sock_fd1和sock_fd2,使用read或者recv函數讀取套接字的內容,由於他們都是阻塞函數,使用read函數或recv函數讀取sock_fd1的內容,此時程序會一直阻塞,如果這個時候sock_fd2有內容來了是沒有辦法立刻讀取的,必須等read函數返回才能讀取。這個時候便可以使用select函數來解決這個問題。
其原型爲:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 
  • maxfdp爲最大套接字加1,
  • fd_set實際上是一個long類型的數組類型,表示文件描述符集,即多個套接字的集合。對於fd_set我們需要了解用來操作fd_set結構的幾個宏
    fd_set set;

    • FD_ZERO(&set); /將set清零使集合中不含任何fd/
    • FD_SET(sock_fd, &set); /將sock_fdfd加入set集合/
    • FD_CLR(sock_fd, &set); /將sock_fd從set集合中清除/
    • FD_ISSET(sock_fd, &set); /在調用select()函數後,用FD_ISSET來檢測sock_fd是否在set集合中,當檢測到fsock_fdd在set中則返回真,否則,返回假(0)/
  • fd_set *readfds是指向fd_set結構的指針,select函數監視該文件描述符集和中的套接字 是否發生讀變化的,即我們關心是否可以從這些文件中讀取數據了,如果這個集合中有一個文件可讀,select就會返回一個大於0的值,表示有文件可讀;如果沒有可讀的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的讀變化。

  • fd_set *writefds是指向fd_set結構的指針,elect函數監視該文件描述符集和中的套接字 是否發生可寫變化的,即我們關心是否可以向這些文件中寫入數據了,如果這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,如果沒有可寫的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。一般設該值爲NULL,表示不關心任何文件的寫變化。
  • fd_set *errorfds同上面兩個參數的意圖,用來監視文件錯誤異常文件。
  • timeval 是一個常用的結構體,有兩個成員秒和毫秒。用來表示阻塞時間。
struct timeval  
{  
    long tv_sec;    //second  
    long tv_usec;   //microsecond  
}; 

現在我們用select函數多路複用來解決上面阻塞的問題,假如已經有了sock_fd1和sock_fd2

fd_set rfds;
struct timeval tv;
/*將套接字sock_fd1和sock_fd2添加到文件描述符集rfds*/
FD_SET(sock_fd1,&rfds);
FD_SET(sock_fd2,&rfds);

/*設置阻塞時間爲1s*/
tv.tv_sec = 1;
tv.tv_usev = 0;

/*去sock_fd1和sock_fd2中的最大值*/
maxfd = sock_fd1 > sock_fd2? sock_fd1:sock_fd2;
/*調用select函數,如果sock_fd1或sock_fd2任何一個套接字發生可讀變化select函數就會返回,否則達到阻塞時間1S後返回*/
select(maxfd+1,&rfds,NULL,NULL,&tv);

/*判斷具體是哪一個套接字發生變化,如果sock_fd1發生可讀變化*/
if(FD_ISSET(sock_fd1,&rfds))
{
  /*讀出sock_fd1的值,注意因爲現在是在套接字發生可讀變化的情況下才會調用read函數,所以不會再有不必要的阻塞*/
  read(sock_fd1,buff,sizeof(buff));
}
/*如果sock_fd2發生可讀變化*/
if(FD_ISSET(sock_fd2,&rfds))
{
  read(sock_fd2,buff,sizeof(buff));
}

send函數

send函數用來向TCP連接的另一端發送數據。客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。注意:send函數僅僅是把buf中的數據copy到sock_fd的發送緩衝區的剩餘空間裏,數據的傳送是靠網絡協議傳輸。
其原型爲:

int send( SOCKET sock_fd,char *buf,int len,int flags );
  • 參數sock_fd:指定發送端套接字
  • 參數buf:包含待發送數據的緩衝區。
  • 參數len:緩衝區中數據的長度。
  • 參數flags:調用執行方式。一般設該值爲0。
  • 返回值:若無錯誤發生,send()返回所發送數據的總數(請注意這個數字可能小於len中所規定的大小)。否則的話,返回SOCKET_ERROR錯誤,應用程序可通過WSAGetLastError()獲取相應錯誤代碼

調用該函數時,send先比較待發送數據的長度len和套接字sock_fd的發送緩衝的長度(因爲待發送數據是要copy到套接字sock_fd的發送緩衝區的):
1. 如果len大於sock_fd的發送緩衝區的長度,該函數返回SOCKET_ERROR;
2. 如果len小於或者等於sock_fd的發送緩衝區的長度,那麼send先檢查協議是否正在發送sock_fd的發送緩衝中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送sock_fd的發送緩衝中的數據或者sock_fd的發送緩衝中沒有數據,那麼 send就比較sock_fd的發送緩衝區的剩餘空間和len:
1. 如果len大於剩餘空間大小send就一直等待協議把s的發送緩衝中的數據發送完;
2. 如果len小於剩餘空間大小send就僅僅把buf中的數據copy到剩餘空間裏。
3. 如果send函數copy數據成功,就返回實際copy的字節數,如果send在copy數據時出現錯誤,那麼send就返回SOCKET_ERROR;如果send在等待協議傳送數據時網絡斷開的話,那麼send函數也返回SOCKET_ERROR。

注意:send函數把buf中的數據成功copy到s的發送緩衝的剩餘空間裏後它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。如果協議在後續的傳送過程中出現網絡錯誤的話,那麼下一個Socket函數就會返回SOCKET_ERROR。(每一個除send外的Socket函數在執行的最開始總要先等待套接字的發送緩衝中的數據被協議傳送完畢才能繼續,如果在等待時出現網絡錯誤,那麼該Socket函數就返回 SOCKET_ERROR)

recv函數

recv函數用於從TCP連接的另一端接收數據。
其原型爲:

int recv( SOCKET sock_fd, char *buf, int  len, int flags)
  • 參數 sock_fd:指定接收端套接字;
  • 參數buf:指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;
  • 參數 len:指明buf的長度;
  • 參數flag:調用執行方式,一般設該值爲0

應用程序調用recv函數時,recv先等待sock_fd的發送緩衝中的數據被協議傳送完畢,如果協議在傳送sock_fd的發送緩衝中的數據時出現網絡錯誤,那麼recv函數返回SOCKET_ERROR;如果sock_fd的發送緩衝中沒有數據或者數據被協議成功發送完畢後,recv先檢查套接字s的接收緩衝區,如果sock_fd接收緩衝區中沒有數據或者協議正在接收數據,那麼recv就一直等待,直到協議把數據接收完畢;當協議把數據接收完畢,recv函數就把sock_fd的接收緩衝中的數據copy到buf中(注意協議接收到的數據可能大於buf的長度,所以在這種情況下要調用幾次recv函數才能把s的接收緩衝中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),recv函數返回其實際copy的字節數;如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0。

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