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
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的引數是對方的地址。