第3章 基本TCP套接字編程_第1頁(yè)
第3章 基本TCP套接字編程_第2頁(yè)
第3章 基本TCP套接字編程_第3頁(yè)
第3章 基本TCP套接字編程_第4頁(yè)
第3章 基本TCP套接字編程_第5頁(yè)
已閱讀5頁(yè),還剩39頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、第第3 3章章 基本基本TCPTCP套接字套接字編程編程基本套接字函數(shù)socket#include int socket(int family, int type, int protocol) 返回:非負(fù)描述字成功;-1出錯(cuò)。family:協(xié)議族;type:套接字類型; protocol:一般為0,除原始套接字外。 family typeAF_INET IPv4協(xié)議SOCK_STREAM 字節(jié)流套接口AF_INET6IPv6協(xié)議SOCK_DGRAM 數(shù)據(jù)報(bào)套接口AF_LOCALunix域協(xié)議SOCK_RAW 原始套接口AF_ROUTE 路由套接口AF_KEY 密鑰套接口基本套接字函數(shù)bind#

2、include int bind(int sockfd, struct sockaddr *addr, socklen_len len) 返回:0成功;-1出錯(cuò)該函數(shù)給套接字分配一個(gè)本地協(xié)議地址,注意:協(xié)議地址addr是通用地址。一般而言,服務(wù)器調(diào)用此函數(shù),而客戶則很少調(diào)用它。綁定地址時(shí),可以指定地址和端口號(hào),也可以指定其中之一,甚至一個(gè)也不指定。通配地址:INADDR_ANY IP地址 端口 結(jié)果通配地址0內(nèi)核選擇IP地址和端口號(hào)通配地址非0內(nèi)核選擇IP地址,進(jìn)程指定端口本地IP0進(jìn)程指定IP地址,內(nèi)核選擇端口本地IP非0進(jìn)程指定IP地址和端口號(hào)bind函數(shù)(續(xù))另外,需要注意以下幾點(diǎn): 參

3、數(shù)addr中的相關(guān)字段在初始化時(shí),必須是網(wǎng)絡(luò)字節(jié)序; 如果由內(nèi)核來(lái)選擇IP地址和臨時(shí)端口號(hào),函數(shù)并不返回所選擇的值。為了獲得這些值,進(jìn)程必須調(diào)用getsockname函數(shù) 函數(shù)bind返回的一個(gè)常見(jiàn)錯(cuò)誤是:EADDRINUSE,我們可以通過(guò)設(shè)置套接口選項(xiàng)SO_REUSEADDR。bind函數(shù)的用法struct sockaddr_in addr;int port = 1234;addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(port);if (bind(fd, (st

4、ruct sockaddr *)&addr, sizeof(addr) = -1)/* 錯(cuò)誤處理 */基本套接字函數(shù)listen#include int listen(int sockfd, int backlog) 返回:0成功;-1出錯(cuò);函數(shù)listen僅被服務(wù)器調(diào)用,它完成兩件事情: 函數(shù)listen將未連接的套接字轉(zhuǎn)化成被動(dòng)套接字,指示內(nèi)核應(yīng)接受指向此套接字的連接請(qǐng)求; 函數(shù)的第二個(gè)參數(shù)規(guī)定了內(nèi)核為此套接字排隊(duì)的最大連接個(gè)數(shù);對(duì)于給定的監(jiān)聽(tīng)套接字,內(nèi)核要維護(hù)兩個(gè)隊(duì)列 未完成連接隊(duì)列 已完成連接隊(duì)列 兩個(gè)隊(duì)列之和不超過(guò)backlog;listen函數(shù)(續(xù))三路握手完成兩隊(duì)列之和不

5、能超過(guò)backlog已完成連接隊(duì)列(ESTABLISHED狀態(tài))未完成連接隊(duì)列(SYN_RCVD狀態(tài))新到達(dá)的SYN分節(jié)服務(wù)器TCPacceptlisten函數(shù)(續(xù))另外幾點(diǎn)說(shuō)明: 不同的實(shí)現(xiàn)對(duì)backlog有不同的解釋,如源自Berkeley的實(shí)現(xiàn)將backlog增加一個(gè)模糊因子,把它乘以1.5,再作為兩個(gè)隊(duì)列之和; 不要把backlog定義為0,因?yàn)橛行?shí)現(xiàn)允許1個(gè)連接排隊(duì),而有些實(shí)現(xiàn)不允許連接排隊(duì); 當(dāng)一個(gè)客戶SYN到達(dá)時(shí),若兩個(gè)隊(duì)列都是滿的,tcp就忽略此分節(jié),且不發(fā)送RST。這是因?yàn)?,這種情況是暫時(shí)的,客戶tcp將重發(fā)SYN,期望不久的將來(lái)就能在隊(duì)列中找到空閑條目?;咎捉幼趾瘮?shù)co

6、nnect#include int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 返回:0成功;-1出錯(cuò);函數(shù)connect激發(fā)TCP的三路握手過(guò)程;僅在成功或出錯(cuò)返回;錯(cuò)誤有以下幾種情況: 如果客戶沒(méi)有收到SYN分節(jié)的響應(yīng)(總共75秒,這之間需要可能需要重發(fā)若干次SYN),則返回ETIMEDOUT。 如果對(duì)客戶的SYN的響應(yīng)是RST,則表明該服務(wù)器主機(jī)在該端口上沒(méi)有進(jìn)程在等待。函數(shù)返回錯(cuò)誤ECONNREFUSED; 如果客戶發(fā)出的SYN在中間路由器上引發(fā)一個(gè)目的地不可達(dá)ICMP錯(cuò)誤,則如第一種情況,連

7、續(xù)發(fā)送SYN,直到規(guī)定時(shí)間,返回EHOSTUNREACH或ENETUNREACH。connect函數(shù)(續(xù))客戶在調(diào)用connect前不必非得調(diào)用bind函數(shù),此時(shí),內(nèi)核會(huì)選擇一個(gè)合適的IP地址和臨時(shí)端口號(hào);如果函數(shù)connect失敗,則套接字不可再用,必須關(guān)閉。不能再對(duì)此套接字再調(diào)用函數(shù)connect?;咎捉幼趾瘮?shù)accept#include int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 返回:非負(fù)描述字OK;-1出錯(cuò);accept函數(shù)由TCP服務(wù)器調(diào)用;從已完成連接隊(duì)列頭返回下一個(gè)已完成連接;如果

8、該隊(duì)列空,則進(jìn)程進(jìn)入睡眠狀態(tài)。函數(shù)返回的套接字為連接套接字,應(yīng)與監(jiān)聽(tīng)套接字區(qū)分開來(lái)該函數(shù)最多:一個(gè)既可能是新套接字也可能是錯(cuò)誤指示的整數(shù),一個(gè)客戶進(jìn)程的協(xié)議地址(由cliaddr所指),以及該地址的大?。ㄟ@后兩個(gè)參數(shù)是值結(jié)果參數(shù));也就是說(shuō),服務(wù)器可以通過(guò)參數(shù)cliaddr來(lái)得到請(qǐng)求連接并獲得成功的客戶的地址和端口號(hào);accept函數(shù)示例struct sockaddr_inservaddr, cliaddr;socklen_tlen;intlistenfd, connfd;connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &a

9、mp;len);if (connfd = -1)/* 出錯(cuò)處理 */printf(“ connection from %s, port = %dn”, inet_ntop(AF_INET, cliaddr.sin_addr, buff, sizeof(buf),ntohs(cliaddr.sin_port);基本套接字函數(shù)close#include int close(int sockfd); 返回:0OK;-1出錯(cuò);close函數(shù)缺省功能是將套接字做上“已關(guān)閉”標(biāo)記,并立即返回到進(jìn)程。這個(gè)套接字不能再為該進(jìn)程所用。正常情況下,close將引發(fā)向TCP的四分節(jié)終止序列,但在終止前將發(fā)送已排隊(duì)的

10、數(shù)據(jù);如果套接字描述符訪問(wèn)計(jì)數(shù)在調(diào)用close后大于0(在多個(gè)進(jìn)程共享同一個(gè)套接字的情況下),則不會(huì)引發(fā)TCP終止序列(即不會(huì)發(fā)送FIN分節(jié));基本套接字函數(shù)-shutdown#include int shutdown(int sockfd, int howto); 返回:0OK;-1出錯(cuò);該函數(shù)立即發(fā)送FIN分節(jié)(無(wú)論其訪問(wèn)計(jì)數(shù)是否大于0)。shutdown根據(jù)參數(shù)howto關(guān)閉指定方向的數(shù)據(jù)傳輸; SHUT_RD:關(guān)閉連接的讀這一半,不再接收套接字中的數(shù)據(jù)且現(xiàn)留在接收緩沖區(qū)的數(shù)據(jù)作廢; SHUT_WR :關(guān)閉連接的寫這一半,當(dāng)留在套接字發(fā)送緩沖區(qū)中的數(shù)據(jù)都被發(fā)送,后跟tcp連接終止序列,不

11、管訪問(wèn)計(jì)數(shù)是否大于0;此后將不能在執(zhí)行對(duì)套接字的任何寫操作; SHUT_RDWR:連接的讀、寫都關(guān)閉,這等效于調(diào)用shutdown兩次,一次調(diào)用是用SHUT_RD,第二次用SHUT_WR。數(shù)據(jù)數(shù)據(jù)FIN數(shù)據(jù)和FIN的確認(rèn)數(shù)據(jù)數(shù)據(jù)FIN數(shù)據(jù)和FIN的確認(rèn)writewriteshutdownRead返回大于0Read返回大于0Read返回0writewritecloseRead返回大于0Read返回0Read返回大于0客戶服務(wù)器調(diào)用shutdown關(guān)閉一半TCP連接基本套接字函數(shù)read#include int read(int fd, char *buf, int len);返回:大于0讀寫字節(jié)

12、大?。?1出錯(cuò);調(diào)用函數(shù)read時(shí),有如下幾種情況:套接字接收緩沖區(qū)接收到數(shù)據(jù),返回接收到的字節(jié)數(shù);tcp協(xié)議收到FIN數(shù)據(jù),返回0;tcp協(xié)議收到RST數(shù)據(jù),返回1,同時(shí)errno為ECONNRESET;進(jìn)程阻塞過(guò)程中接收到信號(hào),返回1,同時(shí)errno為EINTR?;咎捉幼趾瘮?shù)write#include int write(int fd, char *buf, int len); 返回:大于0讀寫字節(jié)大??;-1出錯(cuò);調(diào)用函數(shù)write,有如下幾種情況: 套接字發(fā)送緩沖區(qū)有足夠空間,返回發(fā)送的字節(jié)數(shù); tcp協(xié)議接收到RST數(shù)據(jù),返回1,同時(shí)errno為ECONNRESET; ; 進(jìn)程阻塞過(guò)

13、程中接收到信號(hào),返回1,同時(shí)errno為EINTR。數(shù)據(jù)傳輸函數(shù)send#include #include ssize_t send (int s, const void *msg, size_t len, int flags);返回:非0發(fā)送成功的數(shù)據(jù)長(zhǎng)度;-1出錯(cuò);flags 是傳輸控制標(biāo)志,其值定義如下: 0:常規(guī)操作,如同write()函數(shù) MSG_OOB,發(fā)送帶外數(shù)據(jù)。 MSG_DONTROUTE:忽略底層協(xié)議的路由設(shè)置,只能將數(shù)據(jù)發(fā)送給與發(fā)送機(jī)處在同一個(gè)網(wǎng)絡(luò)中的機(jī)器上。數(shù)據(jù)傳輸函數(shù)recv#include #include ssize_t recv(int s, void *buf

14、 ,size_t len, int flags); 返回:大于0表示成功接收的數(shù)據(jù)長(zhǎng)度;0: 對(duì)方已關(guān)閉,-1:出錯(cuò)。flags是傳輸控制標(biāo)志,其值定義如下: 0:常規(guī)操作,如同read()函數(shù); MSG_PEEK:只查看數(shù)據(jù)而不讀出數(shù)據(jù),后續(xù)讀操作仍然能讀該數(shù)據(jù); MSG_OOB:忽略常規(guī)數(shù)據(jù),而只讀帶外數(shù)據(jù); MSG_WAITALL:recv函數(shù)只有在將接收緩沖區(qū)填滿后才返回。TCP套接字編程實(shí)現(xiàn)TCP套接字基本步驟分為服務(wù)器端和客戶端兩部分:服務(wù)器端v創(chuàng)建套接字;v綁定套接字;v設(shè)置套接字為監(jiān)聽(tīng)模式,進(jìn)入被動(dòng)接受連接狀態(tài);v接受請(qǐng)求,建立連接v讀寫數(shù)據(jù) 終止連接TCP套接字編程(cont

15、.)客戶端步驟v 創(chuàng)建套接字v 與遠(yuǎn)程服務(wù)器建立連接v 讀/寫數(shù)據(jù); 終止連接TCP套接字編程(cont.)socket()bind()listen()accept()read()write()close()socket()connect()write()read()close()阻塞直到接收到客戶連接請(qǐng)求TCP服務(wù)器端TCP客戶端TCP服務(wù)器模板int main(void)int sockfd,connect_sock;if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1) perror(“create socket failed.”);exit(-1);/*

16、 bind sockfd to some address */* listen */loop if(connect_sock=accept(sockfd,NULL,NULL)=-1) perror(“Accept error.”); exit(-1); /* read and process request */close(connect_sock);close(sockfd);TCP客戶模板/* include some header files */int main(void)int sockfd;if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1)per

17、ror(“Create socket failed.”);exit(-1);/* connect to server */* send requst and receive response */close(sockfd);TCP套接字例程采用客戶/服務(wù)器模式,完成下列功能: 客戶根據(jù)用戶提供的IP地址,連接相應(yīng)的服務(wù)器; 服務(wù)器等待客戶的連接,一旦連接成功,則顯示客戶的IP地址,并發(fā)歡迎信息給客戶; 客戶接收服務(wù)器發(fā)送的信息并顯示;TCP服務(wù)器程序/*server.c*/#include #include #include #include #include #define PORT 123

18、4#define BACKLOG 1int main(void) int listenfd, connectfd; struct sockaddr_in server; struct sockaddr_in client; int sin_size; if(listenfd=socket(AF_INET, SOCK_STREAM, 0)=-1) perror(Create socket failed); exit(-1); TCP服務(wù)器程序(cont.) int opt = SO_REUSEADDR; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,

19、 &opt, sizeof(opt); bzero(&server, sizeof(server); server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)=-1) perror(Bind error); exit(-1); if (listen(listenfd, BACKLOG

20、) = -1) perror(listen error); exit(-1); TCP服務(wù)器程序(cont.)sin_size = sizeof(struct sockaddr_in); while(1) if (connectfd = accept(listenfd, (struct sockaddr *)&client, &sin_size) = -1) perror(accept error); exit(-1); printf(you get a connection from %sn, inet_ntoa(client.sin_addr); send(connectf

21、d,welcome to my servern,22, 0); close(connectfd); /* while */ close(listenfd);TCP客戶程序/*client.c*/#include #include #include #include #define PORT 1234#define MAXDATASIZE 100int main(int argc, char *argv) int fd, numbytes; char bufMAXDATASIZE; struct hostent * he; struct sockaddr_in server; if (argc

22、!= 2) printf(Usage: %s n, argv0); exit(-1); TCP客戶程序(cont.)if (he = gethostbyname(argv1) = NULL) perror(gethostbyname error.); exit(1); if (fd = socket(AF_INET, SOCK_STREAM, 0) = -1) perror(Create socket failed.); exit(1); bzero(&server, sizeof(server); server.sin_family = AF_INET; server.sin_por

23、t = htons(PORT); server.sin_addr = *(struct in_addr *) he-h_addr);TCP客戶程序(cont.)if (connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr) = -1) perror(connect failed.); exit(1); if( (numbytes = recv(fd, buf, MAXDATASIZE, 0) = -1) perror(recv error.); exit(1); bufnumbytes =0; printf(Ser

24、ver Message: %sn,buf); close(fd);三種異常情況下面我們考慮在以下三種異常情況發(fā)生后,tcp客戶服務(wù)器程序的反映; 服務(wù)器主機(jī)崩潰 服務(wù)器主機(jī)崩潰后重啟 服務(wù)器主機(jī)關(guān)機(jī)。服務(wù)器主機(jī)崩潰服務(wù)器主機(jī)崩潰時(shí),已有的網(wǎng)絡(luò)連接上發(fā)不出任何東西,此處我們假設(shè)主機(jī)崩潰,而不是由操作員執(zhí)行命令關(guān)機(jī)。同時(shí)假設(shè)應(yīng)用程序發(fā)出數(shù)據(jù)后,然后阻塞于從套接字讀取響應(yīng)。由于服務(wù)器主機(jī)崩潰,因此客戶tcp會(huì)持續(xù)重傳數(shù)據(jù)分節(jié),試圖從服務(wù)器接收一個(gè)ACK:源自Berkeley的實(shí)現(xiàn)將重傳12次。當(dāng)客戶tcp最終放棄時(shí),返回給客戶一個(gè)錯(cuò)誤,此時(shí)錯(cuò)誤或者是ETIMEDOUT,或者是因?yàn)橹虚g路由器判定服務(wù)器

25、主機(jī)不可達(dá),且以一個(gè)目的地不可達(dá)的ICMP消息響應(yīng),則錯(cuò)誤是EHOSTUNREACH或ENETUNREACH。以后會(huì)介紹,通過(guò)設(shè)置套接字選項(xiàng)可以更改tcp持續(xù)重傳等待的超時(shí)時(shí)間。服務(wù)器主機(jī)崩潰后重啟在這種情況下,如果客戶在主機(jī)崩潰重啟前不主動(dòng)發(fā)送數(shù)據(jù),那么客戶是不會(huì)知道服務(wù)器已崩潰的。在服務(wù)器重啟后:客戶向服務(wù)器發(fā)送一個(gè)數(shù)據(jù)分節(jié);由于服務(wù)器重啟后丟失了以前的連接信息(盡管在服務(wù)端口上有進(jìn)程監(jiān)聽(tīng),但連接套接字所在的端口無(wú)進(jìn)程等待),因此導(dǎo)致服務(wù)器主機(jī)的tcp響應(yīng)RST;當(dāng)客戶tcp收到RST,向客戶返回錯(cuò)誤,ECONNRESET如果客戶對(duì)服務(wù)器的崩潰很關(guān)心,即使客戶不主動(dòng)發(fā)送數(shù)據(jù),就需要其他技術(shù)

26、支持(如套接口選項(xiàng)SO_KEEPALIVE或某些客戶服務(wù)器心跳函數(shù))。服務(wù)器主機(jī)關(guān)機(jī)當(dāng)Unix主機(jī)關(guān)機(jī)時(shí),由init進(jìn)程給所有運(yùn)行的進(jìn)程發(fā)信號(hào)SIGTERM(我們的服務(wù)器程序可以捕獲該信號(hào),并在信號(hào)處理程序中正常關(guān)閉網(wǎng)絡(luò)連接)。如果服務(wù)器程序忽略了SIGTERM信號(hào),則init進(jìn)程會(huì)等待一段固定的時(shí)間(通常是5s20s),然后給所有還在運(yùn)行的程序發(fā)信號(hào)SIGKILL(該信號(hào)不能由服務(wù)器程序捕獲);服務(wù)器將由信號(hào)SIGKILL終止,其終止時(shí),所有打開的描述字被關(guān)閉,這導(dǎo)致向客戶發(fā)送FIN分節(jié);客戶收到FIN分節(jié)后,能推斷出服務(wù)器將終止服務(wù)。服務(wù)器在accept返回前夭折場(chǎng)景是:三路握手完成,連接

27、建立,然后客戶發(fā)送一個(gè)RST: 模擬這種情況的方法是:?jiǎn)?dòng)服務(wù)器,讓它調(diào)用socket, bind, listen,然后在調(diào)用accept前睡眠一小段時(shí)間。啟動(dòng)客戶,讓它調(diào)用socket和connect,一旦connect返回,立即設(shè)置選項(xiàng)SO_LINGER,以生成RST。這樣做的目的是:避免客戶進(jìn)入TIME_WAIT。不同實(shí)現(xiàn)對(duì)上述問(wèn)題處理不同。如BSD中,服務(wù)器進(jìn)程無(wú)法探知這一情況,而SVR4中,返回EPROTO,但Posix.1g卻返回ECONNABORTED。服務(wù)器端: if (listen(listenfd, BACKLOG) = -1) perror(listen error); exit(-1); sleep(10); if (connectfd = accept(listenfd, (struct sockaddr *)&client, &sin_size) = -1) perror(accept error); exit(-1); 客戶端: struct linger ling;if (connect(fd, (struct sockaddr *)&server,

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論