一帜乞、Socket簡(jiǎn)介
Socket是進(jìn)程通訊的一種方式搀玖,即調(diào)用這個(gè)網(wǎng)絡(luò)庫(kù)的一些API函數(shù)實(shí)現(xiàn)分布在不同主機(jī)的相關(guān)進(jìn)程之間的數(shù)據(jù)交換莉炉。
幾個(gè)定義:
(1)IP地址:即依照TCP/IP協(xié)議分配給本地主機(jī)的網(wǎng)絡(luò)地址豌拙,兩個(gè)進(jìn)程要通訊陕悬,任一進(jìn)程首先要知道通訊對(duì)方的位置,即對(duì)方的IP按傅。
(2)端口號(hào):用來(lái)辨別本地通訊進(jìn)程捉超,一個(gè)本地的進(jìn)程在通訊時(shí)均會(huì)占用一個(gè)端口號(hào),不同的進(jìn)程端口號(hào)不同唯绍,因此在通訊前必須要分配一個(gè)沒有被訪問的端口號(hào)拼岳。
(3)連接:指兩個(gè)進(jìn)程間的通訊鏈路。
(4)半相關(guān):網(wǎng)絡(luò)中用一個(gè)三元組可以在全局唯一標(biāo)志一個(gè)進(jìn)程:
(協(xié)議况芒,本地地址裂问,本地端口號(hào))
這樣一個(gè)三元組,叫做一個(gè)半相關(guān),它指定連接的每半部分牛柒。
(4)全相關(guān):一個(gè)完整的網(wǎng)間進(jìn)程通信需要由兩個(gè)進(jìn)程組成,并且只能使用同一種高層協(xié)議痊乾。也就是說(shuō)皮壁,不可能通信的一端用TCP協(xié)議,而另一端用UDP協(xié)議哪审。因此一個(gè)完整的網(wǎng)間通信需要一個(gè)五元組來(lái)標(biāo)識(shí):
(協(xié)議蛾魄,本地地址,本地端口號(hào),遠(yuǎn)地地址滴须,遠(yuǎn)地端口號(hào))
這樣一個(gè)五元組舌狗,叫做一個(gè)相關(guān)(association),即兩個(gè)協(xié)議相同的半相關(guān)才能組合成一個(gè)合適的相關(guān)扔水,或完全指定組成一連接痛侍。
二、客戶/服務(wù)器模式
在TCP/IP網(wǎng)絡(luò)應(yīng)用中魔市,通信的兩個(gè)進(jìn)程間相互作用的主要模式是客戶/服務(wù)器(Client/Server, C/S)模式主届,即客戶向服務(wù)器發(fā)出服務(wù)請(qǐng)求,服務(wù)器接收到請(qǐng)求后待德,提供相應(yīng)的服務(wù)君丁。客戶/服務(wù)器模式的建立基于以下兩點(diǎn):
(1)首先将宪,建立網(wǎng)絡(luò)的起因是網(wǎng)絡(luò)中軟硬件資源绘闷、運(yùn)算能力和信息不均等,需要共享较坛,從而造就擁有眾多資源的主機(jī)提供服務(wù)印蔗,資源較少的客戶請(qǐng)求服務(wù)這一非對(duì)等作用。
(2)其次燎潮,網(wǎng)間進(jìn)程通信完全是異步的喻鳄,相互通信的進(jìn)程間既不存在父子關(guān)系,又不共享內(nèi)存緩沖區(qū)确封,因此需要一種機(jī)制為希望通信的進(jìn)程間建立聯(lián)系除呵,為二者的數(shù)據(jù)交換提供同步,這就是基于客戶/服務(wù)器模式的TCP/IP爪喘。
服務(wù)器端:
其過程是首先服務(wù)器方要先啟動(dòng)颜曾,并根據(jù)請(qǐng)求提供相應(yīng)服務(wù):
(1)打開一通信通道并告知本地主機(jī),它愿意在某一公認(rèn)地址上的某端口(如FTP的端口可能為21)接收客戶請(qǐng)求秉剑;
(2)等待客戶請(qǐng)求到達(dá)該端口泛豪;
(3)接收到客戶端的服務(wù)請(qǐng)求時(shí),處理該請(qǐng)求并發(fā)送應(yīng)答信號(hào)侦鹏。接收到并發(fā)服務(wù)請(qǐng)求诡曙,要激活一新進(jìn)程來(lái)處理這個(gè)客戶請(qǐng)求(如UNIX系統(tǒng)中用fork、exec)略水。新進(jìn)程處理此客戶請(qǐng)求价卤,并不需要對(duì)其它請(qǐng)求作出應(yīng)答。服務(wù)完成后渊涝,關(guān)閉此新進(jìn)程與客戶的通信鏈路慎璧,并終止床嫌。
(4)返回第(2)步,等待另一客戶請(qǐng)求胸私。
(5)關(guān)閉服務(wù)器
客戶端:
(1)打開一通信通道厌处,并連接到服務(wù)器所在主機(jī)的特定端口;
(2)向服務(wù)器發(fā)服務(wù)請(qǐng)求報(bào)文岁疼,等待并接收應(yīng)答阔涉;繼續(xù)提出請(qǐng)求......
(3)請(qǐng)求結(jié)束后關(guān)閉通信通道并終止。
從上面所描述過程可知:
(1)客戶與服務(wù)器進(jìn)程的作用是非對(duì)稱的五续,因此代碼不同洒敏。
(2)服務(wù)器進(jìn)程一般是先啟動(dòng)的。只要系統(tǒng)運(yùn)行疙驾,該服務(wù)進(jìn)程一直存在凶伙,直到正常或強(qiáng)迫終止它碎。
介紹完基礎(chǔ)知識(shí)函荣,下面就介紹一些API函數(shù):
創(chuàng)建套接字──socket()
應(yīng)用程序在使用套接字前,首先必須擁有一個(gè)套接字扳肛,系統(tǒng)調(diào)用socket()向應(yīng)用程序提供創(chuàng)建套接字的手段傻挂,其調(diào)用格式如下:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
該調(diào)用要接收三個(gè)參數(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)提供了一個(gè)面向連接、可靠的數(shù)據(jù)傳輸服務(wù)政基,數(shù)據(jù)無(wú)差錯(cuò)贞铣、無(wú)重復(fù)地發(fā)送,且按發(fā)送順序接收沮明。內(nèi)設(shè)流量控制咕娄,避免數(shù)據(jù)流超限;數(shù)據(jù)被看作是字節(jié)流珊擂,無(wú)長(zhǎng)度限制圣勒。文件傳送協(xié)議(FTP)即使用流式套接字。
(2)二是數(shù)據(jù)報(bào)式套接字(SOCK_DGRAM)提供了一個(gè)無(wú)連接服務(wù)摧扇。數(shù)據(jù)包以獨(dú)立包形式被發(fā)送圣贸,不提供無(wú)錯(cuò)保證,數(shù)據(jù)可能丟失或重復(fù)扛稽,并且接收順序混亂吁峻。網(wǎng)絡(luò)文件系統(tǒng)(NFS)使用數(shù)據(jù)報(bào)式套接字。
(3)三是原始式套接字(SOCK_RAW)該接口允許對(duì)較低層協(xié)議在张,如IP用含、ICMP直接訪問。常用于檢驗(yàn)新的協(xié)議實(shí)現(xiàn)或訪問現(xiàn)有服務(wù)中配置的新設(shè)備帮匾。
參數(shù)protocol說(shuō)明該套接字使用的特定協(xié)議啄骇,如果調(diào)用者不希望特別指定使用的協(xié)議,則置為0瘟斜,使用默認(rèn)的連接模式缸夹。根據(jù)這三個(gè)參數(shù)建立一個(gè)套接字,并將相應(yīng)的資源分配給它螺句,同時(shí)返回一個(gè)整型套接字號(hào)虽惭。因此,socket()系統(tǒng)調(diào)用實(shí)際上指定了相關(guān)五元組中的“協(xié)議”這一元蛇尚。
指定本地地址──bind()
當(dāng)一個(gè)套接字用socket()創(chuàng)建后芽唇,存在一個(gè)名字空間(地址族),但它沒有被命名取劫。bind()將套接字地址(包括本地主機(jī)地址和本地端口地址)與所創(chuàng)建的套接字號(hào)聯(lián)系起來(lái)匆笤,即將名字賦予套接字,以指定本地半相關(guān)勇凭。其調(diào)用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數(shù)s是由socket()調(diào)用返回的并且未作連接的套接字描述符(套接字號(hào))疚膊。參數(shù)name 是賦給套接字s的本地地址(名字),其長(zhǎng)度可變虾标,結(jié)構(gòu)隨通信域的不同而不同寓盗。namelen表明了name的長(zhǎng)度。如果沒有錯(cuò)誤發(fā)生璧函,bind()返回0傀蚌。否則返回SOCKET_ERROR。
建立套接字連接──connect()與accept()
這兩個(gè)系統(tǒng)調(diào)用用于完成一個(gè)完整相關(guān)的建立蘸吓,其中connect()用于建立連接善炫。accept()用于使服務(wù)器等待來(lái)自某客戶進(jìn)程的實(shí)際連接。
connect()的調(diào)用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數(shù)s是欲建立連接的本地套接字描述符库继。參數(shù)name指出說(shuō)明對(duì)方套接字地址結(jié)構(gòu)的指針箩艺。對(duì)方套接字地址長(zhǎng)度由namelen說(shuō)明窜醉。
如果沒有錯(cuò)誤發(fā)生,connect()返回0艺谆。否則返回值SOCKET_ERROR榨惰。在面向連接的協(xié)議中,該調(diào)用導(dǎo)致本地系統(tǒng)和外部系統(tǒng)之間連接實(shí)際建立静汤。
由于地址族總被包含在套接字地址結(jié)構(gòu)的前兩個(gè)字節(jié)中琅催,并通過socket()調(diào)用與某個(gè)協(xié)議族相關(guān)。因此bind()和connect()無(wú)須協(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)的指針抹估,用來(lái)接收連接實(shí)體的地址缠黍。addr的確切格式由套接字創(chuàng)建時(shí)建立的地址族決定。addrlen 為客戶方套接字地址的長(zhǎng)度(字節(jié)數(shù))棋蚌。如果沒有錯(cuò)誤發(fā)生嫁佳,accept()返回一個(gè)SOCKET類型的值,表示接收到的套接字的描述符谷暮。否則返回值INVALID_SOCKET蒿往。
accept()用于面向連接服務(wù)器。參數(shù)addr和addrlen存放客戶方的地址信息湿弦。調(diào)用前瓤漏,參數(shù)addr 指向一個(gè)初始值為空的地址結(jié)構(gòu),而addrlen 的初始值為0颊埃;調(diào)用accept()后蔬充,服務(wù)器等待從編號(hào)為s的套接字上接受客戶連接請(qǐng)求,而連接請(qǐng)求是由客戶方的connect()調(diào)用發(fā)出的班利。當(dāng)有連接請(qǐng)求到達(dá)時(shí)饥漫,accept()調(diào)用將請(qǐng)求連接隊(duì)列上的第一個(gè)客戶方套接字地址及長(zhǎng)度放入addr 和addrlen,并創(chuàng)建一個(gè)與s有相同特性的新套接字號(hào)罗标。新的套接字可用于處理服務(wù)器并發(fā)請(qǐng)求庸队。
四個(gè)套接字系統(tǒng)調(diào)用,socket()闯割、bind()彻消、connect()、accept()宙拉,可以完成一個(gè)完全五元相關(guān)的建立宾尚。socket()指定五元組中的協(xié)議元,它的用法與是否為客戶或服務(wù)器谢澈、是否面向連接無(wú)關(guān)煌贴。bind()指定五元組中的本地二元御板,即本地主機(jī)地址和端口號(hào),其用法與是否面向連接有關(guān):在服務(wù)器方崔步,無(wú)論是否面向連接稳吮,均要調(diào)用bind(),若采用面向連接井濒,則可以不調(diào)用bind(),而通過connect()自動(dòng)完成列林。若采用無(wú)連接瑞你,客戶方必須使用bind()以獲得一個(gè)唯一的地址。
監(jiān)聽連接──listen()
此調(diào)用用于面向連接服務(wù)器希痴,表明它愿意接收連接者甲。listen()需在accept()之前調(diào)用,其調(diào)用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
參數(shù)s標(biāo)識(shí)一個(gè)本地已建立砌创、尚未連接的套接字號(hào)虏缸,服務(wù)器愿意從它上面接收請(qǐng)求。backlog表示請(qǐng)求連接隊(duì)列的最大長(zhǎng)度嫩实,用于限制排隊(duì)請(qǐng)求的個(gè)數(shù)刽辙,目前允許的最大值為5。如果沒有錯(cuò)誤發(fā)生甲献,listen()返回0宰缤。否則它返回SOCKET_ERROR。
listen()在執(zhí)行調(diào)用過程中可為沒有調(diào)用過bind()的套接字s完成所必須的連接晃洒,并建立長(zhǎng)度為backlog的請(qǐng)求連接隊(duì)列慨灭。
調(diào)用listen()是服務(wù)器接收一個(gè)連接請(qǐng)求的四個(gè)步驟中的第三步。它在調(diào)用socket()分配一個(gè)流套接字球及,且調(diào)用bind()給s賦于一個(gè)名字之后調(diào)用氧骤,而且一定要在accept()之前調(diào)用。
數(shù)據(jù)傳輸──send()與recv()
當(dāng)一個(gè)連接建立以后吃引,就可以傳輸數(shù)據(jù)了筹陵。常用的系統(tǒng)調(diào)用有send()和recv()。
send()調(diào)用用于s指定的已連接的數(shù)據(jù)報(bào)或流套接字上發(fā)送輸出數(shù)據(jù)际歼,格式如下:
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);
參數(shù)s為已連接的本地套接字描述符惶翻。buf 指向存有發(fā)送數(shù)據(jù)的緩沖區(qū)的指針,其長(zhǎng)度由len 指定鹅心。flags 指定傳輸控制方式吕粗,如是否發(fā)送帶外數(shù)據(jù)等。如果沒有錯(cuò)誤發(fā)生旭愧,send()返回總共發(fā)送的字節(jié)數(shù)颅筋。否則它返回SOCKET_ERROR宙暇。
recv()調(diào)用用于s指定的已連接的數(shù)據(jù)報(bào)或流套接字上接收輸入數(shù)據(jù),格式如下:
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
參數(shù)s 為已連接的套接字描述符议泵。buf指向接收輸入數(shù)據(jù)緩沖區(qū)的指針占贫,其長(zhǎng)度由len 指定。flags 指定傳輸控制方式先口,如是否接收帶外數(shù)據(jù)等型奥。如果沒有錯(cuò)誤發(fā)生,recv()返回總共接收的字節(jié)數(shù)碉京。如果連接被關(guān)閉厢汹,返回0。否則它返回SOCKET_ERROR谐宙。
輸入/輸出多路復(fù)用──select()
select()調(diào)用用來(lái)檢測(cè)一個(gè)或多個(gè)套接字的狀態(tài)烫葬。對(duì)每一個(gè)套接字來(lái)說(shuō),這個(gè)調(diào)用可以請(qǐng)求讀凡蜻、寫或錯(cuò)誤狀態(tài)方面的信息搭综。請(qǐng)求給定狀態(tài)的套接字集合由一個(gè)fd_set結(jié)構(gòu)指示。在返回時(shí)划栓,此結(jié)構(gòu)被更新兑巾,以反映那些滿足特定條件的套接字的子集,同時(shí)茅姜, 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指向要做讀檢測(cè)的套接字描述符集合的指針奋姿,調(diào)用者希望從中讀取數(shù)據(jù)。參數(shù)writefds 指向要做寫檢測(cè)的套接字描述符集合的指針素标。exceptfds指向要檢測(cè)是否出錯(cuò)的套接字描述符集合的指針称诗。timeout指向select()函數(shù)等待的最大時(shí)間,如果設(shè)為NULL則為阻塞操作头遭。select()返回包含在fd_set結(jié)構(gòu)中已準(zhǔn)備好的套接字描述符的總數(shù)目寓免,或者是發(fā)生錯(cuò)誤則返回SOCKET_ERROR。
關(guān)閉套接字──closesocket()
closesocket()關(guān)閉套接字s计维,并釋放分配給該套接字的資源袜香;如果s涉及一個(gè)打開的TCP連接,則該連接被釋放鲫惶。closesocket()的調(diào)用格式如下:
BOOL PASCAL FAR closesocket(SOCKET s);
參數(shù)s待關(guān)閉的套接字描述符蜈首。如果沒有錯(cuò)誤發(fā)生,closesocket()返回0。否則返回值SOCKET_ERROR欢策。
以上就是SOCKET API一些常用的API函數(shù)吆寨,下面是一段代碼:
//客戶端代碼:
include <WINSOCK2.H>
include <stdio.h>
pragma comment(lib,"ws2_32.lib")
int main()
{
int err;
WORD versionRequired;
WSADATA wsaData;
versionRequired=MAKEWORD(1,1);
err=WSAStartup(versionRequired,&wsaData);//協(xié)議庫(kù)的版本信息
if (!err)
{
printf("客戶端嵌套字已經(jīng)打開!\n");
}
else
{
printf("客戶端的嵌套字打開失敗!\n");
return 0;//結(jié)束
}
SOCKET clientSocket=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN clientsock_in;
clientsock_in.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
clientsock_in.sin_family=AF_INET;
clientsock_in.sin_port=htons(6000);
//bind(clientSocket,(SOCKADDR*)&clientsock_in,strlen(SOCKADDR));//注意第三個(gè)參數(shù)
//listen(clientSocket,5);
connect(clientSocket,(SOCKADDR*)&clientsock_in,sizeof(SOCKADDR));//開始連接
char receiveBuf[100];
recv(clientSocket,receiveBuf,101,0);
printf("%s\n",receiveBuf);
send(clientSocket,"hello,this is client",strlen("hello,this is client")+1,0);
closesocket(clientSocket);
WSACleanup();
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//服務(wù)器端代碼:
include <WINSOCK2.H>
include <stdio.h>
pragma comment(lib,"ws2_32.lib")
int main()
{
//創(chuàng)建套接字
WORD myVersionRequest;
WSADATA wsaData;
myVersionRequest=MAKEWORD(1,1);
int err;
err=WSAStartup(myVersionRequest,&wsaData);
if (!err)
{
printf("已打開套接字\n");
}
else
{
//進(jìn)一步綁定套接字
printf("嵌套字未打開!");
return 0;
}
SOCKET serSocket=socket(AF_INET,SOCK_STREAM,0);//創(chuàng)建了可識(shí)別套接字
//需要綁定的參數(shù)
SOCKADDR_IN addr;
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//ip地址
addr.sin_port=htons(6000);//綁定端口
bind(serSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR));//綁定完成
listen(serSocket,5);//其中第二個(gè)參數(shù)代表能夠接收的最多的連接數(shù)
//////////////////////////////////////////////////////////////////////////
//開始進(jìn)行監(jiān)聽
//////////////////////////////////////////////////////////////////////////
SOCKADDR_IN clientsocket;
int len=sizeof(SOCKADDR);
while (1)
{
SOCKET serConn=accept(serSocket,(SOCKADDR*)&clientsocket,&len);//如果這里不是accept而是conection的話。踩寇。就會(huì)不斷的監(jiān)聽
char sendBuf[100];
sprintf(sendBuf,"welcome %s to bejing",inet_ntoa(clientsocket.sin_addr));//找對(duì)對(duì)應(yīng)的IP并且將這行字打印到那里
send(serConn,sendBuf,strlen(sendBuf)+1,0);
char receiveBuf[100];//接收
recv(serConn,receiveBuf,strlen(receiveBuf)+1,0);
printf("%s\n",receiveBuf);
closesocket(serConn);//關(guān)閉
WSACleanup();//釋放資源的操作
}
return 0;
}