標籤:
通訊端介面(socket interface)是一組函數,它們和Unix I/O函數結合起來,用以建立網路應用。大多數現代系統上都實現通訊端介面,包括所有的Unix變種、Windows和Macintosh。
1.通訊端的基本結構
struct sockaddr
這個結構用來儲存通訊端地址。
資料定義:
1 struct sockaddr 2 {3 unsigned short sa_family; /* address族, AF_xxx */4 char sa_data[14]; /* 14 bytes的協議地址 */5 6 };
sa_family 一般來說,都是“AFINET”。
sa_data 包含了一些遠程電腦的地址、連接埠和通訊端的數目,它裡面的資料是雜溶在一起的。
為了處理struct sockaddr, 程式員建立了另外一個相似的結構 struct sockaddr_in:
struct sockaddr_in (“in” 代表 “Internet”)
1 struct sockaddr_in2 {3 short int sin_family; /* Internet地址族 */4 unsigned short int sin_port; /* 連接埠號碼 */5 struct in_addr sin_addr; /* Internet地址 */6 unsigned char sin_zero[8]; /* 添0(和struct sockaddr一樣大小)*/7 };
這個結構提供了方便的手段來訪問socket address(struct sockaddr)結構中的每一個元素。
2.基本通訊端調用
socket() 函數
用戶端和伺服器使用socket函數來建立一個通訊端描述符。
socket 函數的定義是下面這樣子的:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain , int type , int protocol);
若成功則返回非負描述符,出錯返回-1。
在我們的代碼中,一般總是帶這樣的參數來調用socket函數:
client = socket(AF_INET, SOCK_STREAM, 0);
其中,AF_INET表明我們是在使用網際網路,而SOCK_STREAM表示開啟的是一個TCP通訊端,0表示使用預設方式。
bind() 函數
bind()函數可以協助你指定一個通訊端使用的連接埠。
當你使用socket() 函數得到一個通訊端描述符,你也許需要將socket 綁定上一個你的機器上的連接埠。
當你需要進行連接埠監聽 listen()操作,等待接受一個連入請求的時候,一般都需要經過這一步。
如果你只是想進行串連一台伺服器,也就是進行 connect() 操作的時候,這一步並不是必須的。
bind()的系統調用聲明如下:
#include <sys/types.h>
#include <sys/socket.h>
int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;
參數說明:
sockfd 是由socket()函數返回的通訊端描述符。
my_addr 是一個指向struct sockaddr 的指標,包含有關你的地址的資訊:名稱、連接埠和IP 位址。
addrlen 可以設定為sizeof(struct sockaddr)。
connect()函數
讓我們花一點時間來假設你是一個Telnet 應用程式。你的使用者命令你建立一個通訊端描述符。你遵從命令,調用了socket()。
然後,使用者告訴你串連到“166.111.69.52”的23 連接埠(標準的Telnet 連接埠)??你應該怎麼做呢?
你很幸運:Telnet 應用程式,你現在正在閱讀的就是通訊端的進行網路連接部分:connect()。
connect() 函數的定義是這樣的:
#include <sys/types.h>
#include <sys/socket.h>
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
connect()的三個參數意義如下:
sockfd :通訊端檔案描述符,由socket()函數返回的。
serv_addr 是一個儲存遠端電腦的IP 位址和連接埠資訊的結構。
addrlen 應該是sizeof(struct sockaddr)。
listen() 函數
listen()函數是等待別人串連,進行系統偵聽請求的函數。當有人串連你的時候,你有兩步需要做:
通過listen()函數等待串連請求,然後使用accept()函數來處理。(accept()函數在下面介紹)。
listen()函數調用是非常簡單的。函式宣告如下:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
listen()函數的參數意義如下:
sockfd 是一個通訊端描述符,由socket()系統調用獲得。
backlog 是未經過處理的串連請求隊列可以容納的最大數目。
backlog 具體一些是什麼意思呢?每一個連入請求都要進入一個連入請求隊列,等待
listen 的程式調用accept()(accept()函數下面有介紹)函數來接受這個串連。當系統還沒有
調用accept()函數的時候,如果有很多串連,那麼本地能夠等待的最大數目就是backlog 的數值。
《深入理解電腦系統》這本書推薦一般設定為一個比較大的數,如1024.
accept()函數
函數accept()有一些難懂。當調用它的時候,大致過程是下面這樣的:
l 有人從很遠很遠的地方嘗試調用 connect()來串連你的機器上的某個連接埠(當然是
你已經在listen()的)。
l 他的串連將被 listen 加入等待隊列等待accept()函數的調用(加入等待隊列的最多
數目由調用listen()函數的第二個參數backlog 來決定)。
l 你調用 accept()函數,告訴他你準備串連。
l accept()函數將回返回一個新的通訊端描述符,這個描述符就代表了這個串連!
好,這時候你有了兩個通訊端描述符,返回給你的那個就是和遠端電腦的串連,而
第一個通訊端描述符仍然在你的機器上原來的那個連接埠上listen()。
這時候你所得到的那個新的通訊端描述符就可以進行send()操作和recv()操作了。
下面是accept()函數的聲明:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
accept()函數的參數意義如下:
sockfd 是正在listen() 的一個通訊端描述符。
addr 一般是一個指向struct sockaddr_in 結構的指標;裡面儲存著遠端連線過來的電腦的資訊(比如遠端電腦的IP 位址和連接埠)
send()、recv()函數
這兩個函數是最基本的,通過有串連的通訊端流進行通訊的函數。
send() 函數的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void *msg, int len, int flags);
send 的參數含義如下:
l sockfd 是代表你與遠程程式串連的通訊端描述符。
l msg 是一個指標,指向你想發送的資訊的地址。
l len 是你想發送資訊的長度。
l flags 發送標記。一般都設為0
函數recv()調用在許多方面都和send()很相似,下面是recv()函數的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int recv(int sockfd, void *buf, int len, unsigned int flags);
recv()的參數含義如下:
l sockfd 是你要讀取資料的通訊端描述符。
l buf 是一個指標,指向你能儲存資料的記憶體快取區域。
l len 是緩衝區的最大尺寸。
l flags 是recv() 函數的一個標誌,一般都為0 (具體的其他數值和含義請參考recv()
的man pages)。
recv() 返回它所真正收到的資料的長度。
sendto() 和recvfrom() 函數
這兩個函數是進行不需連線的UDP 通訊時使用的。使用這兩個函數,則資料會在沒有
建立過任何串連的網路上傳輸。因為資料通訊端無法對遠程主機進行串連,想想我們在
發送資料前需要知道些什麼呢?
對了!是遠程主機的IP 位址和連接埠!
下面是sendto()函數和recvfrom()函數的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
和你所看到的一樣,這個函數和send()函數基本一致。
l sockfd 是代表你與遠程程式串連的通訊端描述符。
l msg 是一個指標,指向你想發送的資訊的地址。
l len 是你想發送資訊的長度。
l flags 發送標記。一般都設為0。(你可以查看send 的man pages 來獲得其他的參
數值並且明白各個參數所代表的含義)
l to 是一個指向struct sockaddr 結構的指標,裡麵包含了遠程主機的IP 位址和連接埠
資料。
l tolen 只是指出了struct sockaddr 在記憶體中的大小sizeof(struct sockaddr)。
和send()一樣,sendto()返回它所真正發送的位元組數(當然也和send()一樣,它所真正
發送的位元組數可能小於你所給它的資料的位元組數)。當它發生錯誤的時候,也是返回 –1 ,
同時全域變數errno 儲存了錯誤碼。
同樣的,recv()函數和recvfrom()函數也基本一致。
recvfrom()的聲明為:
#include <sys/types.h>
- 156 - Linux網路編程
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);
其參數含義如下:
l sockfd 是你要讀取資料的通訊端描述符。
l buf 是一個指標,指向你能儲存資料的記憶體快取區域。
l len 是緩衝區的最大尺寸。
l flags 是recv() 函數的一個標誌,一般都為0 (具體的其他數值和含義請參考recv()
的man pages)。
l from 是一個本地指標,指向一個struct sockaddr 的結構(裡面存有源IP 位址和端
口數)。
l fromlen 是一個指向一個int 型資料的指標,它的大小應該是sizeof (struct sockaddr)。
當函數返回的時候,formlen 指向的資料是form 指向的struct sockaddr 的實際大小。
recvfrom() 返回它接收到的位元組數,如果發生了錯誤,它就返回-1。
close()和shutdown()函數
程式進行網路傳輸完畢後,你需要關閉這個通訊端描述符所表示的串連。實現這個非
常簡單,只需要使用標準的關閉檔案的函數:close()。
使用方法:
close(sockfd);
執行close()之後,通訊端將不會在允許進行讀操作和寫操作。任何有關對通訊端描述
符進行讀和寫的操作都會接收到一個錯誤。
如果你想對網路通訊端的關閉進行進一步的操作的話,你可以使用函數shutdown()。
它允許你進行單向的關閉操作,或是全部禁止掉。
shutdown()的聲明為:
#include <sys/socket.h>
int shutdown(int sockfd, int how);
它的參數含義如下:
l sockfd 是一個你所想關閉的通訊端描述符.
l how 可以取下面的值。0 表示不允許以後資料的接收操作;1 表示不允許以後資料的發送操作;2 表示和close()一樣,不允許以後的任何操作(包括接收,發送資料)
shutdown() 如果執行成功將返回0,如果在調用過程中發生了錯誤,它將返回–1,全
局變數errno 中儲存了錯誤碼。
setsockopt() 和getsockopt() 函數
Linux 所提供的socket 庫含有一個錯誤(bug)。此錯誤表現為你不能為一個通訊端重新啟用同一個連接埠號碼,即使在你正常關閉該通訊端以後。
例如,比方說,你編寫一個伺服器在一個通訊端上等待的程式.伺服器開啟通訊端並在其上偵聽是沒有問題的。無論如何,總有一些原因(不管是正常還是非正常的結束程式)
使你的程式需要重新啟動。然而重啟動後你就不能把它綁定在原來那個連接埠上了。從bind()系統調用返回的錯誤碼總是報告說你試圖串連的連接埠已經被別的進程所綁定。問題
就是Linux 核心在一個綁定通訊端的進程結束後從不把連接埠標記為未用。在大多數Linux/UNIX 系統中,連接埠可以被一個進程重複使用,甚至可以被其它進程使用。在Linux 中繞開
這個問題的辦法是,當通訊端已經開啟但尚未有串連的時候用setsockopt()系統調用在其上設定選項(options)。setsockopt() 調用設定選項而getsockopt()從給定的通訊端取得選項。
這裡是這些調用的文法:
#include<sys/types.h>
#include<sys/socket.h>
int getsockopt(int sockfd, int level, int name, char *value, int *optlen);
int setsockopt(int sockfd, int level, int name, char *value, int *optlen);
下面是兩個調用的參數說明:
l sockfd 必須是一個已開啟的通訊端。
l level 是函數所使用的協議標準(protocol level)(TCP/IP 協議使用IPPROTO_TCP,
通訊端標準的選項實用SOL_SOCKET)。
l name 選項在通訊端說明書中(man page)有詳細說明。
l value 指向為getsockopt()函數所擷取的值,setsockopt()函數所設定的值的地址。
l optlen 指標指向一個整數,該整數包含參數以位元組計算的長度。
現在我們再回到Linux 的錯誤上來.當你開啟一個通訊端時必須同時用下面的程式碼片段
來調用setsockopt()函數:
/* 設定參數數值 */
opt = 1; len = sizeof(opt);
/* 設定通訊端屬性 */
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
本文章參考了《深入理解電腦系統》和博主流雲攬月的文章。
Unix網路編程隨手記——通訊端介面函數