套接字接口(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」。dom
sa_data 包含了一些遠程電腦的地址、端口和套接字的數目,它裏面的數據是雜溶在一塊兒的。socket
爲了處理struct sockaddr, 程序員創建了另一個類似的結構 struct sockaddr_in:函數
struct sockaddr_in (「in」 表明 「Internet」)spa
1 struct sockaddr_in 2 { 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);
本文章參考了《深刻理解計算機系統》和博主流雲攬月的文章。