1. 程式人生 > >socket程式設計 -- socket、bind、accept、connect函式

socket程式設計 -- socket、bind、accept、connect函式

socket

socket函式原型

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

引數說明

  • domain:
    AF_INET 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用IPv4的地址
    AF_INET6 與上面類似,不過是來用IPv6的地址
    AF_UNIX 本地協議,使用在Unix和Linux系統上,一般都是當客戶端和伺服器在同一臺及其上的時候使用

  • type:
    SOCK_STREAM

    這個協議是按照順序的、可靠的、資料完整的基於位元組流的連線。這是一個使用最多的socket型別,這個socket是使用TCP來進行傳輸。
    SOCK_DGRAM 這個協議是無連線的、固定長度的傳輸呼叫。該協議是不可靠的,使用UDP來進行它的連線。
    SOCK_SEQPACKET 這個協議是雙線路的、可靠的連線,傳送固定長度的資料包進行傳輸。必須把這個包完整的接受才能進行讀取。
    SOCK_RAW 這個socket型別提供單一的網路訪問,這個socket型別使用ICMP公共協議。(ping、traceroute使用該協議)
    SOCK_RDM 這個型別是很少使用的,在大部分的作業系統上沒有實現,它是提供給資料鏈路層使用,不保證資料包的順序

  • protocol:
    0 預設協議

函式返回值
成功返回一個新的檔案描述符,失敗返回-1,設定errno

socket()開啟一個網路通訊埠,如果成功的話,就像open()一樣返回一個檔案描
述符,應用程式可以像讀寫檔案一樣用read/write在網路上收發資料,如果socket()調用出錯則返回-1。
對於IPv4,domain引數指定為AF_INET。
對於TCP協議,type引數指定為SOCK_STREAM,表示面向流的傳輸協議。
如果是UDP協議,則type引數指定為SOCK_DGRAM,表示面向資料報的傳輸協議。
protocol引數的介紹略,指定為0即可。

bind

函式原型

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

引數說明

  • sockfd:
    socket檔案描述符
  • addr:
    構造出IP地址加埠號
  • addrlen:
    sizeof(addr)長度

函式返回值
成功返回0,失敗返回-1, 設定errno

伺服器程式所監聽的網路地址和埠號通常是固定不變的,客戶端程式得知伺服器程式
的地址和埠號後就可以向伺服器發起連線,因此伺服器需要呼叫bind繫結一個固定的網路地址和埠號。

bind()的作用是將引數sockfd和addr繫結在一起,使sockfd這個用於網路通訊的檔案描述符監聽addr所描述的地址和埠號。前面講過,struct sockaddr *是一個通用指標型別,addr引數實際上可以接受多種協議的sockaddr結構體,而它們的長度各不相同,所以需要第三個引數addrlen指定結構體的長度。
如:

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8000);

首先將整個結構體清零,然後設定地址型別為AF_INET,網路地址為INADDR_ANY,這個巨集表示本地的任意IP地址,因為伺服器可能有多個網絡卡,每個網絡卡也可能繫結多個IP地址,這樣設定可以在所有的IP地址上監聽,直到與某個客戶端建立了連線時才確定下來到底用哪個IP地址,埠號為8000

listen

listen函式原型

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

引數說明

  • sockfd:
    socket檔案描述符
  • backlog:
    排隊建立3次握手佇列和剛剛建立3次握手佇列的連結數和

檢視系統預設backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

典型的伺服器程式可以同時服務於多個客戶端,當有客戶端發起連線時,伺服器呼叫的accept()返回並接受這個連線,如果有大量的客戶端發起連線而伺服器來不及處理,尚未accept的客戶端就處於連線等待狀態,listen()宣告sockfd處於監聽狀態,並且最多允許有backlog個客戶端處於連線待狀態,如果接收到更多的連線請求就忽略。

返回值
listen()成功返回0,失敗返回-1。

accept

函式原型

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

引數說明

  • sockdf:
    socket檔案描述符
  • addr:
    傳出引數,返回連結客戶端地址資訊,含IP地址和埠號
  • addrlen:
    傳入傳出引數(值-結果),傳入sizeof(addr)大小,函式返回時返回真正接收到地址結構體的大小

函式返回值:
成功返回一個新的socket檔案描述符,用於和客戶端通訊,失敗返回-1,設定errno

三方握手完成後,伺服器呼叫accept()接受連線,如果伺服器呼叫accept()時還沒有客戶端的連線請求,就阻塞等待直到有客戶端連線上來。
addr是一個傳出引數,accept()返回時傳出客戶端的地址和埠號。
addrlen引數是一個傳入傳出引數(value-result argument),傳入的是呼叫者提供的緩衝區addr的長度以避免緩衝區溢位問題,傳出的是客戶端地址結構體的實際長度(有可能沒有佔滿呼叫者提供的緩衝區)。
如果給addr引數傳NULL,表示不關心客戶端的地址。

我們的伺服器程式結構是這樣的:

while (1)
{
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    n = read(connfd, buf, MAXLINE);
    ......
    close(connfd);
}

整個是一個while死迴圈,每次迴圈處理一個客戶端連線。
由於cliaddr_len是傳入傳出
引數,每次呼叫accept()之前應該重新賦初值。
accept()的引數listenfd是先前的監聽檔案描述符,而accept()的返回值是另外一個檔案描述符connfd,之後與客戶端之間就通過這個connfd通訊,最後關閉connfd斷開連線,而不關閉listenfd,再次回到迴圈開頭listenfd仍然用作accept的引數。
accept()成功返回一個檔案描述符,出錯返回-1。

connect

函式原型

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

引數說明

  • sockdf:
    socket檔案描述符
  • addr:
    傳入引數,指定伺服器端地址資訊,含IP地址和埠號
  • addrlen:
    傳入引數,傳入sizeof(addr)大小

函式返回值
成功返回0,失敗返回-1,設定errno

客戶端需要呼叫connect()連線伺服器,connect和bind的引數形式一致,區別在於
bind的引數是自己的地址,而connect的引數是對方的地址。