2021年4月28日 星期三

SOCKET函式

send/sendto和recv/recvfrom各自的區別

一般情況下:

   send(),recv()用於TCP,sendto()及recvfrom()用於UDP

   但是send(),recv()也可以用於UDP,sendto()及recvfrom()也可以用於TCP

   sendto可以在引數中指定傳送的目標地址 , send需要socket已建立連線, sendto 可用於無連線的 socket 對於send的有連線socket,兩者一樣,sendto最後兩個引數沒用.


send函式 

int send( SOCKET s , const char FAR *buf , int len , int flags );  

不論是客戶還是伺服器應用程式都用send函式來向TCP連線的另一端傳送資料。

客戶程式一般用send函式向伺服器傳送請求,而伺服器則通常用send函式來向客戶程式傳送應答。

  • 第一個引數指定傳送端套接字描述符;
  • 第二個引數指明一個存放應用程式要傳送資料的緩衝區;
  • 第三個引數指明實際要傳送的資料的位元組數;
  • 第四個引數一般設定為0。


這裡只描述同步socket的send函式的執行流程。

當呼叫該函式時,send先比較待發送資料的長度len和套接字s的傳送緩衝的 長度, 如果len大於s的傳送緩衝區的長度,該函式返回SOCKET_ERROR;如果len小於或者等於s的傳送緩衝區的長度,那麼send先檢查協議 是否正在傳送s的傳送緩衝中的資料,如果是就等待協議把資料傳送完,如果協議還沒有開始傳送s的傳送緩衝中的資料或者s的傳送緩衝中沒有資料,那麼 send就比較s的傳送緩衝區的剩餘空間和len,如果len大於剩餘空間大小send就一直等待協議把s的傳送緩衝中的資料傳送完,如果len小於剩餘空間大小send就僅僅把buf中的資料copy到剩餘空間裡(注意並不是send把s的傳送緩衝中的資料傳到連線的另一端的,而是協議傳的,send僅僅是把buf中的資料copy到s的傳送緩衝區的剩餘空間裡)。如果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函式

int recv( SOCKET s , char FAR * buf , int len , int flags );   

不論是客戶還是伺服器應用程式都用recv函式從TCP連線的另一端接收資料。

  • 第一個引數指定接收端套接字描述符;
  • 第二個引數指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;
  • 第三個引數指明buf的長度;
  • 第四個引數一般置0。


這裡只描述同步socket的recv函式的執行流程。

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


sendto函式 和 recvfrom 函式一般用於UDP協議中,但是如果在 TCP 中 connect 函式呼叫後也可以用.

sendto() 和recvfrom() --------> 利用資料報文方式進行資料傳輸  

  在無連線的資料報socket方式下,由於本地socket並沒有與遠端機器建立連線,所以在傳送資料時應指明目的地址,sendto() 函式原型為:  

   int sendto(int sockfd, const void *msg,int len , unsigned int flags, const struct sockaddr *to, int tolen); 


     該函式比 send() 函式多了兩個引數,to表示目地機的IP地址和埠號資訊,而tolen常常被賦值為sizeof (struct sockaddr)。sendto 函式也返回實際傳送的資料位元組長度或在出現傳送錯誤時返回-1。 

  recvfrom() 函式原型為:  

  int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen); 


    from是一個struct sockaddr型別的變數,該變數儲存源機的IP地址及埠號。fromlen常置為sizeof (struct sockaddr)。當recvfrom() 返回時,fromlen 包含實際存入from中的資料位元組數。recvfrom() 函式返回接收到的位元組數或當出現錯誤時返回-1,並置相應的errno。 

    應注意的一點是,當你對於資料報socket呼叫了connect() 函式時,你也可以利用send() 和recv() 進行資料傳輸,但該socket仍然是資料報socket,並且利用傳輸層的UDP服務。但在傳送或接收資料報時,核心會自動為之加上目地和源地址資訊。





Synchronous:表示有一個thread處於blocked狀態專門負責等待網絡事件的到來 (including blocking,select, poll, WSAAsyncSelect, WSAEventSelect, Overlapped with Event Notification, Completion Port); WSAAsyncSelect 其實比較特殊,用户本身不用專門創建一個thread來等待,但是Windows窗口本身有一個消息循環,在等待網絡事件的時候,這個消息循環實際上幫助實現了 blocked thread的工作,所以還是把它放在這個類別下。

Asynchronous:表示只是提供一個callback function,issue 網絡動作後,不需要專門的thread進行等待,網絡事件發生後,kernel會自動調用callback function (including signal-driven I/O, Asynchronous I/O,Overlapped with Completion Routine)。

accept的第三个参数,在调用之前需要设置为第二个参数的长度,否則會。
在manpage中有明确的说明:
The addrlen argument is a value-result argument: the caller  must  initialize  it  to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address.

connect函式

int connect(int sd, struct sockaddr *serverAddr, int addr_len)

Parameters
  • sd:sd是socket的描述符,即是前個Example的sockfd
  • serverAddr:負責提供關於這個socket的所有信息
  • addr_len:它的意義簡單明瞭,就是*serverAddr的大小

Return Values

成功了回傳0,否則回傳-1。可以從變數errno,取得下面的錯誤訊息。

ERRORS         top
       The following are general socket errors only.  There may be other
       domain-specific error codes.

       EACCES For UNIX domain sockets, which are identified by pathname:
              Write permission is denied on the socket file, or search
              permission is denied for one of the directories in the
              path prefix.  (See also path_resolution(7).)

       EACCES, EPERM
              The user tried to connect to a broadcast address without
              having the socket broadcast flag enabled or the connection
              request failed because of a local firewall rule.

              EACCES can also be returned if an SELinux policy denied a
              connection (for example, if there is a policy saying that
              an HTTP proxy can only connect to ports associated with
              HTTP servers, and the proxy tries to connect to a
              different port).  dd

       EADDRINUSE
              Local address is already in use.

       EADDRNOTAVAIL
              (Internet domain sockets) The socket referred to by sockfd
              had not previously been bound to an address and, upon
              attempting to bind it to an ephemeral port, it was
              determined that all port numbers in the ephemeral port
              range are currently in use.  See the discussion of
              /proc/sys/net/ipv4/ip_local_port_range in ip(7).

       EAFNOSUPPORT
              The passed address didn't have the correct address family
              in its sa_family field.

       EAGAIN For nonblocking UNIX domain sockets, the socket is
              nonblocking, and the connection cannot be completed
              immediately.  For other socket families, there are
              insufficient entries in the routing cache.

       EALREADY
              The socket is nonblocking and a previous connection
              attempt has not yet been completed.

       EBADF  sockfd is not a valid open file descriptor.

       ECONNREFUSED
              A connect() on a stream socket found no one listening on
              the remote address.

       EFAULT The socket structure address is outside the user's address
              space.

       EINPROGRESS
              The socket is nonblocking and the connection cannot be
              completed immediately.  (UNIX domain sockets failed with
              EAGAIN instead.)  It is possible to select(2) or poll(2)
              for completion by selecting the socket for writing.  After
              select(2) indicates writability, use getsockopt(2) to read
              the SO_ERROR option at level SOL_SOCKET to determine
              whether connect() completed successfully (SO_ERROR is
              zero) or unsuccessfully (SO_ERROR is one of the usual
              error codes listed here, explaining the reason for the
              failure).

       EINTR  The system call was interrupted by a signal that was
              caught; see signal(7).

       EISCONN
              The socket is already connected.

       ENETUNREACH
              Network is unreachable.

       ENOTSOCK
              The file descriptor sockfd does not refer to a socket.

       EPROTOTYPE
              The socket type does not support the requested
              communications protocol.  This error can occur, for
              example, on an attempt to connect a UNIX domain datagram
              socket to a stream socket.

       ETIMEDOUT
              Timeout while attempting connection.  The server may be
              too busy to accept new connections.  Note that for IP
              sockets the timeout may be very long when syncookies are
              enabled on the server.

SO_REUSEADDR有什麼用處和怎麼使用?

這個問題在Richard Stevens的《Unix網絡編程指南》卷一裡有很詳細的解答(中文版P166-168頁)。這裡我只是寫幾個基本的例子來驗證這個問題。首先聲明一個問題:當兩個socket的address和port相衝突,而你又想重用地
址和端口,則舊的socket和新的socket都要已經被設置了SO_REUSEADDR特性,只有兩者之一有這個特性還是有問題的。
    SO_REUSEADDR可以用在以下四種情況下。
    (摘自《Unix網絡編程》卷一,即UNPv1)
  •  當有一個有相同本地地址和端口的socket1處於TIME_WAIT狀態時,而你啟動的程序的socket2要佔用該地址和端口,你的程序就要用到該選項。
  •  SO_REUSEADDR允許同一port上啟動同一服務器的多個實例(多個進程)。但每個實例綁定的IP地址是不能相同的。在有多塊網卡或用IP Alias技術的機器可以測試這種情況。
  •  SO_REUSEADDR允許單個進程綁定相同的端口到多個socket上,但每個socket綁定的ip地址不同。這和2很相似,區別請看UNPv1。
  • SO_REUSEADDR允許完全相同的地址和端口的重複綁定。但這只用於UDP的多播,不用於TCP。

REF

沒有留言:

張貼留言