什么是Socket
Socket是進程通訊的一種方式,即調(diào)用這個網(wǎng)絡(luò)庫的一些API函數(shù)實現(xiàn)分布在不同主機的相關(guān)進程之間的數(shù)據(jù)交換。
Socket是一門技術(shù),由于現(xiàn)在是面向?qū)ο蟮木幊?,一些計算機行業(yè)的大神通過抽象的理念,在現(xiàn)實中通過反復(fù)的理論或者實際的推導(dǎo),提出了抽象的一些通信協(xié)議,基于tcp/ip協(xié)議,提出大致的構(gòu)想,一些泛型的程序大牛在這個協(xié)議的基礎(chǔ)上,將這些抽象化的理念接口化,針對協(xié)議提出的每個理念,專門的編寫制定的接口,與其協(xié)議一一對應(yīng),形成了現(xiàn)在的socket標(biāo)準(zhǔn)規(guī)范,然后將其接口封裝成可以調(diào)用的接口,供開發(fā)者使用。
目前,開發(fā)者開發(fā)出了很多封裝的類來完善socket編程,都是更加方便的實現(xiàn)剛開始socket通信的各個環(huán)節(jié),所以我們首先必須了解socket的通信原理,只有從本質(zhì)上理解socket的通信,才可能快速方便的理解socket的各個環(huán)節(jié),才能從底層上真正的把握。
相關(guān)術(shù)語解釋
(1)IP地址:即依照TCP/IP協(xié)議分配給本地主機的網(wǎng)絡(luò)地址,兩個進程要通訊,任一進程首先要知道通訊對方的位置,即對方的IP。
(2)端口號:用來辨別本地通訊進程,一個本地的進程在通訊時均會占用一個端口號,不同的進程端口號不同,因此在通訊前必須要分配一個沒有被訪問的端口號。
(3)連接:指兩個進程間的通訊鏈路。
(4)半相關(guān):網(wǎng)絡(luò)中用一個三元組可以在全局唯一標(biāo)志一個進程:(協(xié)議,本地地址,本地端口號)這樣一個三元組,叫做一個半相關(guān),它指定連接的每半部分。
(4)全相關(guān):一個完整的網(wǎng)間進程通信需要由兩個進程組成,并且只能使用同一種高層協(xié)議。也就是說,不可能通信的一端用TCP協(xié)議,而另一端用UDP協(xié)議。因此一個完整的網(wǎng)間通信需要一個五元組來標(biāo)識:
(協(xié)議,本地地址,本地端口號,遠(yuǎn)地地址,遠(yuǎn)地端口號)
這樣一個五元組,叫做一個相關(guān)(association),即兩個協(xié)議相同的半相關(guān)才能組合成一個合適的相關(guān),或完全指定組成一連接。
客戶/服務(wù)器模式
在TCP/IP網(wǎng)絡(luò)應(yīng)用中,通信的兩個進程間相互作用的主要模式是客戶/服務(wù)器(Client/Server, C/S)模式,即客戶向服務(wù)器發(fā)出服務(wù)請求,服務(wù)器接收到請求后,提供相應(yīng)的服務(wù)。客戶/服務(wù)器模式的建立基于以下兩點:
(1)首先,建立網(wǎng)絡(luò)的起因是網(wǎng)絡(luò)中軟硬件資源、運算能力和信息不均等,需要共享,從而造就擁有眾多資源的主機提供服務(wù),資源較少的客戶請求服務(wù)這一非對等作用。
(2)其次,網(wǎng)間進程通信完全是異步的,相互通信的進程間既不存在父子關(guān)系,又不共享內(nèi)存緩沖區(qū),因此需要一種機制為希望通信的進程間建立聯(lián)系,為二者的數(shù)據(jù)交換提供同步,這就是基于客戶/服務(wù)器模式的TCP/IP。
服務(wù)器端:
其過程是首先服務(wù)器方要先啟動,并根據(jù)請求提供相應(yīng)服務(wù):
(1)打開一通信通道并告知本地主機,它愿意在某一公認(rèn)地址上的某端口(如FTP的端口可能為21)接收客戶請求;
(2)等待客戶請求到達該端口;
(3)接收到客戶端的服務(wù)請求時,處理該請求并發(fā)送應(yīng)答信號。接收到并發(fā)服務(wù)請求,要激活一新進程來處理這個客戶請求(如UNIX系統(tǒng)中用fork、exec)。新進程處理此客戶請求,并不需要對其它請求作出應(yīng)答。服務(wù)完成后,關(guān)閉此新進程與客戶的通信鏈路,并終止。
(4)返回第(2)步,等待另一客戶請求。
(5)關(guān)閉服務(wù)器
客戶端:
(1)打開一通信通道,并連接到服務(wù)器所在主機的特定端口;
(2)向服務(wù)器發(fā)服務(wù)請求報文,等待并接收應(yīng)答;繼續(xù)提出請求......
(3)請求結(jié)束后關(guān)閉通信通道并終止。
從上面所描述過程可知:
(1)客戶與服務(wù)器進程的作用是非對稱的,因此代碼不同。
(2)服務(wù)器進程一般是先啟動的。只要系統(tǒng)運行,該服務(wù)進程一直存在,直到正?;驈娖冉K止。
網(wǎng)絡(luò)體系結(jié)構(gòu)
協(xié)議:控制網(wǎng)絡(luò)中信息的發(fā)送和接收。定義了通信實體之間交換報文的格式和次序,以及在報文傳輸或接收或其他事件所采取的動作。
一般把網(wǎng)絡(luò)的層次結(jié)構(gòu)和每層所使用協(xié)議的集合稱為網(wǎng)絡(luò)體系結(jié)構(gòu)(NetworkArchitecture)。
由國際標(biāo)準(zhǔn)化組織ISO 在1981年提出的網(wǎng)絡(luò)分層結(jié)構(gòu),簡稱為OSI參考模型。(Open Systems Interconnection Reference Model)。
各層協(xié)議如下,可以看出TCP和UDP協(xié)議在傳輸層。
各層功能
1)鏈路層
鏈路層的功能:是把接收到的網(wǎng)絡(luò)層數(shù)據(jù)報(也 稱IP數(shù)據(jù)報)通過該層的物理接口發(fā)送到傳輸介質(zhì)上,或從物理網(wǎng)絡(luò)上接收數(shù)據(jù)幀,抽出IP數(shù)據(jù)報并交給IP層。
鏈路層通常包括操作系統(tǒng)中的設(shè)備驅(qū)動程序和計算機中對應(yīng)的網(wǎng)絡(luò)接口卡。
2)網(wǎng)絡(luò)層
主要功能:是可以把源主機上的分組發(fā)送到互聯(lián)網(wǎng)中的任何一臺目標(biāo)主機上。
3)傳輸層
為運行在不同主機上的應(yīng)用進程提供邏輯通信功能(主機好像是直接相連的)。
進程之間使用邏輯通信功能彼此發(fā)送報文,無需考慮具體物理鏈路。
傳輸層主要包括種協(xié)議:傳輸控制協(xié)議(TCP),用戶數(shù)據(jù)報協(xié)議(UDP)。
4)應(yīng)用層
應(yīng)用層向使用網(wǎng)絡(luò)的用戶提供特定的、常用的應(yīng)用程序。
表示層:通信用戶之間數(shù)據(jù)格式的轉(zhuǎn)換、數(shù)據(jù)壓縮及加解密等。
會話層:對數(shù)據(jù)傳輸進行管理,包括數(shù)據(jù)交換的定界、同步,建立檢查點等。
套接字
套接字是從網(wǎng)絡(luò)向進程傳遞數(shù)據(jù),或從進程向網(wǎng)絡(luò)傳遞數(shù)據(jù)的門戶。傳輸層和應(yīng)用層的進程通過套接字來傳遞數(shù)據(jù)。
主機上的套接字可以有多個,每個套接字都有惟一的標(biāo)識符(格式取決于UDP或TCP)。
當(dāng)報文段到達主機時,運輸層檢查報文段中的目的端口號,將其定向到相應(yīng)的套接字。
用戶數(shù)據(jù)報協(xié)議UDP
用戶數(shù)據(jù)報協(xié)議UDP(User Datagram Protocol):提供用戶之間的不可靠、無連接的報文傳輸服。使用UDP協(xié)議的原因
1)無連接創(chuàng)建(減少時延)
2)簡單:無連接(在UDP發(fā)送方和接收方之間無握手)
3)段首部小
4)無擁塞控制: UDP能夠盡可能快地傳輸
經(jīng)UDP的可靠傳輸 : 在應(yīng)用層增加可靠性,實現(xiàn)特定的差錯恢復(fù)!
傳輸控制協(xié)議TCP
傳輸控制協(xié)議TCP(Transmission Control Protocol):提供用戶之間可靠的、面向連接的報文傳輸服務(wù)。
面向連接、可靠的服務(wù)是指在進行數(shù)據(jù)交換前,初始化發(fā)送方與接收方狀態(tài),進行握手(交換控制信息)。
建立一個TCP 連接的作用:讓發(fā)送方和接收方都做好準(zhǔn)備,準(zhǔn)備好之后就要開始進行數(shù)據(jù)傳輸了。三次握手建立TCP連接
三次握手的目的主要在于同步連接雙方發(fā)送數(shù)據(jù)的初始序列號。
TCP 連接是一種全雙工的連接,即一個TCP 連接的兩個端點之間可以同時發(fā)送和接收數(shù)據(jù),而不是每一個時刻只能有一個端點發(fā)送數(shù)據(jù)。
TCP與UDP的比較
TCP 是一種面向連接的協(xié)議,而UDP 是無連接的協(xié)議。
TCP 提供的是可靠的傳輸服務(wù),而UDP 協(xié)議提供的是不可靠的服務(wù)。
TCP 提供的是面向字節(jié)流的服務(wù)。
UDP 協(xié)議的傳輸單位是數(shù)據(jù)塊,一個數(shù)據(jù)塊只能封裝在一個UDP 數(shù)據(jù)包中。
解釋:
1)可靠、連接
UDP不可靠、無連接:在UDP發(fā)送方和接收方之間無握手(交換控制信息)。
面向連接舉例:兩個人之間通過電話進行通信;
面向無連接舉例:郵政服務(wù),用戶把信函放在郵件中期待郵政處理流程來傳遞郵政包裹。顯然,不可達代表不可靠。
2)TCP面向字節(jié)流和UDP面向數(shù)據(jù)塊(面向報文)
面向報文的傳輸方式是應(yīng)用層交給UDP多長的報文,UDP就照樣發(fā)送,即一次發(fā)送一個報文。因此,應(yīng)用程序必須選擇合適大小的報文。若報文太長,則IP層需要分片,降低效率。若太短,會使IP太小。UDP對應(yīng)用層交下來的報文,既不合并,也不拆分,而是保留這些報文的邊界。這也就是說,應(yīng)用層交給UDP多長的報文,UDP就照樣發(fā)送,即一次發(fā)送一個報文。
面向字節(jié)流的話,雖然應(yīng)用程序和TCP的交互是一次一個數(shù)據(jù)塊(大小不等),但TCP把應(yīng)用程序看成是一連串的無結(jié)構(gòu)的字節(jié)流。TCP有一個緩沖,當(dāng)應(yīng)用程序傳送的數(shù)據(jù)塊太長,TCP就可以把它劃分短一些再傳送。如果應(yīng)用程序一次只發(fā)送一個字節(jié),TCP也可以等待積累有足夠多的字節(jié)后再構(gòu)成報文段發(fā)送出去。
TCP 協(xié)議與UDP協(xié)議應(yīng)用的比較
TCP 協(xié)議對于有大量數(shù)據(jù)需要進行可靠傳輸?shù)膽?yīng)用是很適合的。比如可用于文件傳輸協(xié)議(FTP )。
對于雖然數(shù)據(jù)量少但需要時間較長且可靠性要求高的應(yīng)用TCP 也是比較適合的。 Telnet 就是這種應(yīng)用的一個例子。
實時應(yīng)用適合使用UDP 協(xié)議。
對于多個實體間的多播式應(yīng)用無法使用TCP 進行通信 。
從程序?qū)崿F(xiàn)的角度,TCP與UDP的過程如下
從上圖也能清晰的看出,TCP通信需要服務(wù)器端偵聽listen、接收客戶端連接請求accept,等待客戶端connect建立連接后才能進行數(shù)據(jù)包的收發(fā)(recv/send)工作。而UDP則服務(wù)器和客戶端的概念不明顯,服務(wù)器端即接收端需要綁定端口,等待客戶端的數(shù)據(jù)的到來。后續(xù)便可以進行數(shù)據(jù)的收發(fā)(recvfrom/sendto)工作。
JAVA實現(xiàn)Socket網(wǎng)絡(luò)編程
在JAVA中TCP套接字由Socket類實現(xiàn),UDP套接字由DatagramSocket類實現(xiàn)。
TCP的JAVA代碼大致如下
TCP網(wǎng)絡(luò)編程步驟
1)創(chuàng)建Socket
2)打開連接到socket的輸入、輸出流
3)按照一定的協(xié)議對socket進行讀、寫操作(接收消息-處理數(shù)據(jù)-發(fā)送消息)
4)關(guān)閉socket
服務(wù)端代碼
public static start(){
ServerSocket serverSocket=null;
try {
int i=1;
//1、創(chuàng)建TCP套接字
int maxConnection="10"; //最大連接數(shù)
serverSocket = new java.net.ServerSocket(8189,maxConnection);
//等待連接
Socket sct=serverSocket.accept();
//輸入輸出流
InputStream inStream = clientSocket.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
OutputStream outStream = clientSocket.getOutputStream();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
//接收消息-處理數(shù)據(jù)-發(fā)送消息
//接收客戶端消息
String response="",lineStr = in.readLine();
while(lineStr!=null){
response += lineStr;
lineStr = in.readLine();
}//end while
//TODO 處理消息等及其動作
//向客戶端發(fā)送信息(output-write)
out.wirte("TODO");
}
catch (Exception e)
{
e.printStackTrace();
}
finally{
if(serverSocket!=null) //關(guān)閉套接字
serverSocket.close();
}
}//end start()
客戶端代碼與服務(wù)端代碼類似,只不過服務(wù)端對客戶端的請求是accept,而客戶端需要連接服務(wù)端,所以使用connect方法,如下:
public static start(){
ServerSocket serverSocket=null;
try {
int i=1;
////打開套接字
Socket clientSocket = new Socket(String serverMachineIp, int port);
int timeout=1000;
clientSocket.connect(sa, timeout);
//輸入輸出流
InputStream inStream = clientSocket.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
OutputStream outStream = clientSocket.getOutputStream();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
//接收消息-處理數(shù)據(jù)-發(fā)送消息
/向客戶端發(fā)送信息(output-write)
out.wirte("TODO");
//TODO 處理消息等及其動作
//返回服務(wù)端的消息
String response="",lineStr = in.readLine();
while(lineStr!=null){
response += lineStr;
lineStr = in.readLine();
}//end while
}
catch (Exception e)
{
e.printStackTrace();
}
finally{
if(serverSocket!=null) //關(guān)閉套接字
serverSocket.close();
}
}//end start()
UDP的JAVA代碼
服務(wù)端代碼:
public class ServerDatagramSocket
{
public static void main(String[] args)
{
try
{
//1創(chuàng)建套接字,監(jiān)聽某個端口
DatagramSocket ds = new DatagramSocket(10010);
//2接收數(shù)據(jù)
//構(gòu)造緩沖數(shù)組,用于存放接收到的數(shù)據(jù)(要求該長度必須大于或等于接收到的數(shù)據(jù)長度)
byte[] data=new byte[1024];
//構(gòu)造數(shù)據(jù)包對象
DatagramPacket receiveDp=new DatagramPacket(data, data.length);
//接收數(shù)據(jù)
ds.receive(receiveDp);
//輸出接收到的數(shù)據(jù)內(nèi)容
byte[] receiveByte=receiveDp.getData();
String receiveContent=new String(receiveByte,0,receiveByte.length);
//3發(fā)送數(shù)據(jù)
//準(zhǔn)備數(shù)據(jù)
//獲得客戶端的IP
InetAddress clientIp=receiveDp.getAddress();
//獲得客戶端的端口號
int port=receiveDp.getPort();
String sendContent="hello back";
byte[] sendByte=sendContent.getBytes();
DatagramPacket sendDp=new DatagramPacket(sendByte, sendByte.length, clientIp, port);
ds.send(sendDp);
}
catch (Exception e) {
// TODO: handle exception
}
finally{
//4關(guān)閉連接
ds.close();
}
}
}
客戶端代碼
public static void main(String[] args) {
try
{
//1創(chuàng)建連接
DatagramSocket ds=new DatagramSocket();
//2發(fā)送數(shù)據(jù)
//數(shù)據(jù)準(zhǔn)備(數(shù)據(jù)內(nèi)容+地址+端口號)
String sendContent="hello";
String host="127.0.0.1";
int port=10001;
//將發(fā)送的內(nèi)容轉(zhuǎn)換為字節(jié)數(shù)組
byte[] sendByte=sendContent.getBytes();
//將服務(wù)器IP轉(zhuǎn)換為InetAddress對象
InetAddress server=InetAddress.getByName(host);
//構(gòu)造發(fā)送的數(shù)據(jù)包對象
DatagramPacket sendDp=new DatagramPacket(sendByte, sendByte.length, server, port);
//發(fā)送數(shù)據(jù)
ds.send(sendDp);
//3接收數(shù)據(jù)
//構(gòu)造緩沖數(shù)組,用于存放接收到的數(shù)據(jù)(要求該長度必須大于或等于接收到的數(shù)據(jù)長度)
byte[] data=new byte[1024];
//構(gòu)造數(shù)據(jù)包對象
DatagramPacket receiveDp=new DatagramPacket(data, data.length);
//接收數(shù)據(jù)
ds.receive(receiveDp);
//輸出數(shù)據(jù)內(nèi)容
byte[] receiveByte=receiveDp.getData(); //獲得緩沖數(shù)組
int len=receiveDp.getLength(); //獲得有效數(shù)據(jù)長度
String receiveContent=new String(receiveByte, 0, len);
System.out.println(receiveContent);
}
catch (Exception e)
{
// TODO: handle exception
}
finally{
//4關(guān)閉連接
ds.close();
}
}
}
socket的一些接口函數(shù)原理
第一次握手:客戶端需要發(fā)送一個syn j 包,試著去鏈接服務(wù)器端,于是客戶端我們需要提供一個鏈接函數(shù)
第二次握手:服務(wù)器端需要接收客戶端發(fā)送過來的syn J+1 包,然后在發(fā)送ack包,所以我們需要有服務(wù)器端接受處理函數(shù)
第三次握手:客戶端的處理函數(shù)和服務(wù)器端的處理函數(shù)
三次握手只是一個數(shù)據(jù)傳輸?shù)倪^程,但是,我們傳輸前需要一些準(zhǔn)備工作,比如將創(chuàng)建一個套接字,收集一些計算機的資源,將一些資源綁定套接字里面,以及接受和發(fā)送數(shù)據(jù)的函數(shù)等等,這些功能接口在一起構(gòu)成了socket的編程
下面大致的按照客戶端和服務(wù)端將所需的函數(shù)詳細(xì)的列舉出來
上面的兩個圖都概述了socket的通訊原理
socket的一些函數(shù)
創(chuàng)建套接字──socket()
應(yīng)用程序在使用套接字前,首先必須擁有一個套接字,系統(tǒng)調(diào)用socket()向應(yīng)用程序提供創(chuàng)建套接字的手段,其調(diào)用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol)
該調(diào)用要接收三個參數(shù):af、type、protocol。參數(shù)af指定通信發(fā)生的區(qū)域:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中僅支持AF_INET,它是網(wǎng)際網(wǎng)區(qū)域。因此,地址族與協(xié)議族相同。參數(shù)type 描述要建立的套接字的類型。這里分三種:
(1)一是TCP流式套接字(SOCK_STREAM)提供了一個面向連接、可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)無差錯、無重復(fù)地發(fā)送,且按發(fā)送順序接收。內(nèi)設(shè)流量控制,避免數(shù)據(jù)流超限;數(shù)據(jù)被看作是字節(jié)流,無長度限制。文件傳送協(xié)議(FTP)即使用流式套接字。
(2)二是數(shù)據(jù)報式套接字(SOCK_DGRAM)提供了一個無連接服務(wù)。數(shù)據(jù)包以獨立包形式被發(fā)送,不提供無錯保證,數(shù)據(jù)可能丟失或重復(fù),并且接收順序混亂。網(wǎng)絡(luò)文件系統(tǒng)(NFS)使用數(shù)據(jù)報式套接字。
(3)三是原始式套接字(SOCK_RAW)該接口允許對較低層協(xié)議,如IP、ICMP直接訪問。常用于檢驗新的協(xié)議實現(xiàn)或訪問現(xiàn)有服務(wù)中配置的新設(shè)備。
參數(shù)protocol說明該套接字使用的特定協(xié)議,如果調(diào)用者不希望特別指定使用的協(xié)議,則置為0,使用默認(rèn)的連接模式。根據(jù)這三個參數(shù)建立一個套接字,并將相應(yīng)的資源分配給它,同時返回一個整型套接字號。因此,socket()系統(tǒng)調(diào)用實際上指定了相關(guān)五元組中的“協(xié)議”這一元。
指定本地地址──bind()
當(dāng)一個套接字用socket()創(chuàng)建后,存在一個名字空間(地址族),但它沒有被命名。bind()將套接字地址(包括本地主機地址和本地端口地址)與所創(chuàng)建的套接字號聯(lián)系起來,即將名字賦予套接字,以指定本地半相關(guān)。其調(diào)用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數(shù)s是由socket()調(diào)用返回的并且未作連接的套接字描述符(套接字號)。參數(shù)name 是賦給套接字s的本地地址(名字),其長度可變,結(jié)構(gòu)隨通信域的不同而不同。namelen表明了name的長度。如果沒有錯誤發(fā)生,bind()返回0。否則返回SOCKET_ERROR。
建立套接字連接──connect()與accept()
這兩個系統(tǒng)調(diào)用用于完成一個完整相關(guān)的建立,其中connect()用于建立連接。accept()用于使服務(wù)器等待來自某客戶進程的實際連接。
connect()的調(diào)用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數(shù)s是欲建立連接的本地套接字描述符。參數(shù)name指出說明對方套接字地址結(jié)構(gòu)的指針。對方套接字地址長度由namelen說明。
如果沒有錯誤發(fā)生,connect()返回0。否則返回值SOCKET_ERROR。在面向連接的協(xié)議中,該調(diào)用導(dǎo)致本地系統(tǒng)和外部系統(tǒng)之間連接實際建立。
由于地址族總被包含在套接字地址結(jié)構(gòu)的前兩個字節(jié)中,并通過socket()調(diào)用與某個協(xié)議族相關(guān)。因此bind()和connect()無須協(xié)議作為參數(shù)。
accept()的調(diào)用格式如下:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
參數(shù)s為本地套接字描述符,在用做accept()調(diào)用的參數(shù)前應(yīng)該先調(diào)用過listen()。addr 指向客戶方套接字地址結(jié)構(gòu)的指針,用來接收連接實體的地址。addr的確切格式由套接字創(chuàng)建時建立的地址族決定。addrlen 為客戶方套接字地址的長度(字節(jié)數(shù))。如果沒有錯誤發(fā)生,accept()返回一個SOCKET類型的值,表示接收到的套接字的描述符。否則返回值INVALID_SOCKET。
accept()用于面向連接服務(wù)器。參數(shù)addr和addrlen存放客戶方的地址信息。調(diào)用前,參數(shù)addr 指向一個初始值為空的地址結(jié)構(gòu),而addrlen 的初始值為0;調(diào)用accept()后,服務(wù)器等待從編號為s的套接字上接受客戶連接請求,而連接請求是由客戶方的connect()調(diào)用發(fā)出的。當(dāng)有連接請求到達時,accept()調(diào)用將請求連接隊列上的第一個客戶方套接字地址及長度放入addr 和addrlen,并創(chuàng)建一個與s有相同特性的新套接字號。新的套接字可用于處理服務(wù)器并發(fā)請求。
四個套接字系統(tǒng)調(diào)用,socket()、bind()、connect()、accept(),可以完成一個完全五元相關(guān)的建立。socket()指定五元組中的協(xié)議元,它的用法與是否為客戶或服務(wù)器、是否面向連接無關(guān)。bind()指定五元組中的本地二元,即本地主機地址和端口號,其用法與是否面向連接有關(guān):在服務(wù)器方,無論是否面向連接,均要調(diào)用bind(),若采用面向連接,則可以不調(diào)用bind(),而通過connect()自動完成。若采用無連接,客戶方必須使用bind()以獲得一個唯一的地址。
監(jiān)聽連接──listen()
此調(diào)用用于面向連接服務(wù)器,表明它愿意接收連接。listen()需在accept()之前調(diào)用,其調(diào)用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
參數(shù)s標(biāo)識一個本地已建立、尚未連接的套接字號,服務(wù)器愿意從它上面接收請求。backlog表示請求連接隊列的最大長度,用于限制排隊請求的個數(shù),目前允許的最大值為5。如果沒有錯誤發(fā)生,listen()返回0。否則它返回SOCKET_ERROR。
listen()在執(zhí)行調(diào)用過程中可為沒有調(diào)用過bind()的套接字s完成所必須的連接,并建立長度為backlog的請求連接隊列。
調(diào)用listen()是服務(wù)器接收一個連接請求的四個步驟中的第三步。它在調(diào)用socket()分配一個流套接字,且調(diào)用bind()給s賦于一個名字之后調(diào)用,而且一定要在accept()之前調(diào)用。
數(shù)據(jù)傳輸──send()與recv()
當(dāng)一個連接建立以后,就可以傳輸數(shù)據(jù)了。常用的系統(tǒng)調(diào)用有send()和recv()。
send()調(diào)用用于s指定的已連接的數(shù)據(jù)報或流套接字上發(fā)送輸出數(shù)據(jù),格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
參數(shù)s為已連接的本地套接字描述符。buf 指向存有發(fā)送數(shù)據(jù)的緩沖區(qū)的指針,其長度由len 指定。flags 指定傳輸控制方式,如是否發(fā)送帶外數(shù)據(jù)等。如果沒有錯誤發(fā)生,send()返回總共發(fā)送的字節(jié)數(shù)。否則它返回SOCKET_ERROR。
recv()調(diào)用用于s指定的已連接的數(shù)據(jù)報或流套接字上接收輸入數(shù)據(jù),格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
參數(shù)s 為已連接的套接字描述符。buf指向接收輸入數(shù)據(jù)緩沖區(qū)的指針,其長度由len 指定。flags 指定傳輸控制方式,如是否接收帶外數(shù)據(jù)等。如果沒有錯誤發(fā)生,recv()返回總共接收的字節(jié)數(shù)。如果連接被關(guān)閉,返回0。否則它返回SOCKET_ERROR。
輸入/輸出多路復(fù)用──select()
select()調(diào)用用來檢測一個或多個套接字的狀態(tài)。對每一個套接字來說,這個調(diào)用可以請求讀、寫或錯誤狀態(tài)方面的信息。請求給定狀態(tài)的套接字集合由一個fd_set結(jié)構(gòu)指示。在返回時,此結(jié)構(gòu)被更新,以反映那些滿足特定條件的套接字的子集,同時, select()調(diào)用返回滿足條件的套接字的數(shù)目,其調(diào)用格式如下:
int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
參數(shù)nfds指明被檢查的套接字描述符的值域,此變量一般被忽略。
參數(shù)readfds指向要做讀檢測的套接字描述符集合的指針,調(diào)用者希望從中讀取數(shù)據(jù)。參數(shù)writefds 指向要做寫檢測的套接字描述符集合的指針。exceptfds指向要檢測是否出錯的套接字描述符集合的指針。timeout指向select()函數(shù)等待的最大時間,如果設(shè)為NULL則為阻塞操作。select()返回包含在fd_set結(jié)構(gòu)中已準(zhǔn)備好的套接字描述符的總數(shù)目,或者是發(fā)生錯誤則返回SOCKET_ERROR。
關(guān)閉套接字──closesocket()
closesocket()關(guān)閉套接字s,并釋放分配給該套接字的資源;如果s涉及一個打開的TCP連接,則該連接被釋放。closesocket()的調(diào)用格式如下:
BOOL PASCAL FAR closesocket(SOCKET s);
參數(shù)s待關(guān)閉的套接字描述符。如果沒有錯誤發(fā)生,closesocket()返回0。否則返回值SOCKET_ERROR。