|
Socket網(wǎng)絡(luò)編程指導(dǎo) 1/37 guoxiaol@mail.ustc.edu.cn 什么是Socket? 2/37 BSD Socket(伯克立套接字)是通過標(biāo)準(zhǔn)的UNIX文件描述符和其它程序通訊的一個(gè)方法,目前已經(jīng)被廣泛移植到各個(gè)平臺(tái)。 Socket是獨(dú)立于具體協(xié)議的網(wǎng)絡(luò)編程接口。在ISO模型中,主要位于會(huì)話層和傳輸層。 Socket的類型 3/37 流式套接字(SOCK_STREAM) 數(shù)據(jù)報(bào)式套接字(SOCK_DGRAM) 原始式套接字(SOCK_RAW) 4/37 Socket所在層次示意圖 Application program Stream Socket Interface TCP UDP Datagram Socket Interface Raw Socket Interface IP Physical and data link layers 基本套接字調(diào)用 5/37 創(chuàng)建套接字 socket(); 接受連接 accept(); Socket相關(guān)的數(shù)據(jù)結(jié)構(gòu) 6/37 struct sockaddr_in { }; struct in_addr { in_addr_t s_addr; /* 存儲(chǔ)32bit 的IP地址*/ } 網(wǎng)絡(luò)字節(jié)順序和主機(jī)字節(jié)順序 7/37 Big-Endian Byte Order:字節(jié)的高位在內(nèi)存中放在存儲(chǔ)單元的起始位置 00001010 00010111 00001110 00000110 00001010 00010111 00001110 00000110 Memory Little-Endian Byte Order : 與Big-Endian相反 A A+1 A+2 A+3 8/40 Host byte order( Little-Endian ) 16-bit 32-bit Network byte order(Big-Endian) 16-bit 32-bit htons() ntohs() htonl() ntohl() 網(wǎng)絡(luò)字節(jié)順序和主機(jī)字節(jié)順序的轉(zhuǎn)換 IP地址的轉(zhuǎn)換 9/37 int inet_aton(const char* strptr, struct in_addr *addrptr); 從點(diǎn)狀十進(jìn)制到32位2進(jìn)制的轉(zhuǎn)換,如“202.38.64.185” 到 11001010,00100110,01000000,10111001 char *inet_ntoa(struct in_addr inadd); 與inet_aton()的功能相反 相關(guān)的內(nèi)存操作函數(shù) 10/37 void *memset(void *buffer, int c, int count); 把buffer所指內(nèi)存區(qū)域的前count個(gè)字節(jié)設(shè)置成字符c。 void *memcpy(void *dest, void *src, unsigned int count); 由src所指內(nèi)存區(qū)域復(fù)制count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域。 Void bzero(void *s, int n ); 置字節(jié)字符串s的前n個(gè)字節(jié)為零。 域名和IP地址的轉(zhuǎn)換 11/37 struct hostent *gethostbyname(const char *name); struct hostent { 建立Socket 12/37 int socket(int domain, int type, int protocol); 參數(shù)說明: domain:通信使用的協(xié)議族,即網(wǎng)絡(luò)的類型,對(duì)于 TCP/IP來說,是AF_INET type: SOCK_STREAM / SOCK_DGRAM protocol: 通常為0 返回整形的socket描述符,如果出錯(cuò),返回-1 Socket的配置 13/37 Socket描述符是一個(gè)指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針,它指向描述符表入口。調(diào)用Socket()函數(shù)時(shí),將建立一個(gè)Socket,為一個(gè)Socket數(shù)據(jù)結(jié)構(gòu)分配存儲(chǔ)空間。 兩個(gè)網(wǎng)絡(luò)程序之間的一個(gè)網(wǎng)絡(luò)連接包括五種信息:通信協(xié)議、本地主機(jī)地址和端口、遠(yuǎn)端主機(jī)地址和端口。 在使用socket進(jìn)行網(wǎng)絡(luò)傳輸以前,必須配置該socket。 面向連接的socket客戶端調(diào)用connect()函數(shù)在socket數(shù)據(jù)結(jié)構(gòu)中保存本地和遠(yuǎn)端信息。 無連接socket的客戶端和服務(wù)端以及面向連接socket的服務(wù)端通過調(diào)用bind()函數(shù)來配置本地信息。 綁定Socket 14/37 int bind(int sockfd,struct sockaddr_in *my_addr, int addrlen); sockfd是socket()返回的socket描述符; my_addr是指向包含本機(jī)IP地址及端口號(hào)等信息的 sockaddr類型的指針; addrlen一般被設(shè)置為sizeof(struct sockaddr_in)
綁定前sockaddr_in的初始化 15/37 my_addr.sin_family = AF_INET; //選擇網(wǎng)絡(luò)類型為TCP/IP my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222"); my_addr.sin_port = htons( 8888 ); //選擇端口8888 addr_len = sizeof(struct sockaddr_in); memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero)); 建立連接(客戶端) 16/37 面向連接的客戶程序使用connect函數(shù)來配置socket并與遠(yuǎn)端服務(wù)器建立一個(gè)TCP連接,其函數(shù)原型為: serv_addr是包含遠(yuǎn)端主機(jī)IP地址和端口號(hào)的指針;addrlen是遠(yuǎn)端地址結(jié)構(gòu)的長度 成功則返回0,出現(xiàn)錯(cuò)誤時(shí)返回-1 建立連接(服務(wù)器端) 17/37 服務(wù)器監(jiān)聽端口:listen函數(shù)使socket處于被動(dòng)的監(jiān)聽模式,并為該socket建立一個(gè)輸入數(shù)據(jù)隊(duì)列,將到達(dá)的服務(wù)請(qǐng)求保存在此隊(duì)列中,直到程序處理它們。 backlog:請(qǐng)求連接隊(duì)列的最大長度 成功返回0,出錯(cuò)返回-1 建立連接(服務(wù)器端) 18/37 accept()函數(shù)讓服務(wù)器接收客戶的連接請(qǐng)求。在建立好輸入隊(duì)列后,服務(wù)器就調(diào)用accept函數(shù),然后睡眠并等待客戶的連接請(qǐng)求。 addr是指向sockaddr_in變量的指針,該變量存放提出連接請(qǐng)求服務(wù)的主機(jī)的信息 返回新的socket描述符,和請(qǐng)求連接進(jìn)程的地址聯(lián)系起來在新的socket描述符上進(jìn)行數(shù)據(jù)傳輸操作。原來的socket繼續(xù)listen 數(shù)據(jù)傳輸(1) 19/37 send()和recv()這兩個(gè)函數(shù)用于面向連接的socket上進(jìn)行數(shù)據(jù)傳輸。 send() 返回實(shí)際發(fā)送的字節(jié)數(shù),可能會(huì)少于希望發(fā)送的數(shù)據(jù)。在程序中應(yīng)該將send()的返回值與欲發(fā)送的字節(jié)數(shù)進(jìn)行比較。當(dāng)返回值與len不匹配時(shí),應(yīng)該進(jìn)行處理。 數(shù)據(jù)傳輸(2) 20/37 recv()函數(shù)原型為: buf 是存放接收數(shù)據(jù)的緩沖區(qū);len是緩沖區(qū)的長度。flags也被置為0。 recv()返回實(shí)際接收的字節(jié)數(shù),當(dāng)出現(xiàn)錯(cuò)誤時(shí),返回-1 數(shù)據(jù)傳輸(3) 21/37 sendto()和recvfrom()用于在無連接的數(shù)據(jù)報(bào)socket方 式下進(jìn)行數(shù)據(jù)傳輸。由于本地socket沒有與遠(yuǎn)端機(jī)器建立連接,所以在發(fā)送數(shù)據(jù)時(shí)要指明目的地址。 sendto()函數(shù)原型為: int sendto(int sockfd, const void *buf,int buflen, unsigned int flags, const struct sockaddr_in *to, int tolen); 數(shù)據(jù)傳輸(4) 22/37 recvfrom()函數(shù)原型為: recvfrom()函數(shù)返回接收到的字節(jié)數(shù),當(dāng)出錯(cuò)時(shí)返回-1 結(jié)束傳輸 23/37 close()函數(shù)用于釋放socket,停止在該socket上的任何數(shù)據(jù)操作: close(sockfd); 也可以調(diào)用shutdown() 來關(guān)閉該socket 該函數(shù)允許只停止某個(gè)方向上的數(shù)據(jù)傳輸,而一個(gè)方向上的數(shù)據(jù)傳輸繼續(xù)進(jìn)行。 C/S結(jié)構(gòu) 24/37 服務(wù)器端要先啟動(dòng),提供相應(yīng)服務(wù): 客戶端: 流程圖 25/37 TCP服務(wù)器端 (循環(huán)服務(wù)器) TCP客戶端 socket( ) bind( ) listen( ) accept( ) socket( ) send( ) connect( ) recv( ) recv( ) send( ) close( ) close( ) UDP服務(wù)器端 UDP客戶端 socket( ) bind( ) listen( ) recvfrom( ) sendto( ) socket( ) bind( ) close( ) close( ) 簡(jiǎn)單的例子 26/37 int sockfd, newsockfd,addr_len, sendnum; struct sockaddr_in my_addr, their_addr; char * msg = “welcome”; sockfd = socket( AF_INET, SOCK_STREAM, 0 ); //建立socket my_addr.sin_family = AF_INET; //選擇網(wǎng)絡(luò)類型為TCP/IP my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222"); my_addr.sin_port = htons( 8888 ); //選擇端口8888 addr_len = sizeof( struct sockaddr_in); memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero)); bind(sockfd, (struct sockaddr *)&my_addr, addr_len); //綁定socket listen(sockfd,10); //監(jiān)聽,等待連接,等待連接隊(duì)列最大長度為10 簡(jiǎn)單的例子(續(xù)) 27/37 While( 1 ) { newsockfd = accept( sockfd, (struct sockaddr *)&my_addr, addr_len); sendnum = send(newsockfd, msg, strlen(msg)+1, 0); …… close(newsockfd); } close(sockfd); 阻塞與非阻塞(1) 28/37 阻塞函數(shù):指其完成指定的任務(wù)之前不允許程序調(diào)用另一個(gè)函數(shù),在Windows下還會(huì)阻塞本線程消息的發(fā)送。 eg: recv( ) ,當(dāng)socket工作在阻塞模式的時(shí)候,如果沒有數(shù)據(jù)的情況下調(diào)用該函數(shù),則當(dāng)前線程會(huì)被掛起,直到有數(shù)據(jù)為止。 非阻塞函數(shù):指操作啟動(dòng)之后,如果可以立即得到結(jié)果就返回結(jié)果,否則返回表示結(jié)果需要等待的錯(cuò)誤信息,不等待任務(wù)完成函數(shù)就返回。 使用非阻塞I/O的方式:select() 例子: while(1){//執(zhí)行循環(huán) 一邊輸出一邊也不要忘了輸入 FD_ZERO(&wt_set); FD_ZERO(&rd_set); FD_CLR(s,&wt_set); FD_CLR(s,&rd_set); FD_SET(s,&wt_set); FD_SET(s,&rd_set); timeout.tv_sec = 0; timeout.tv_usec =500000; z=select(s+1,&rd_set,&wt_set,NULL,&timeout); if(FD_ISSET(s,&rd_set)){//有數(shù)據(jù)可讀 z=recv(s,&recvBuff,sizeof recvBuff-1,0); 29/37 阻塞與非阻塞(2) 30/37 在Berkeley socket函數(shù)部分中,不涉及網(wǎng)絡(luò)I/O、本地端工作的函數(shù)是非阻塞函數(shù) 在Berkeley socket函數(shù)部分中,網(wǎng)絡(luò)I/O的函數(shù)是可阻塞函數(shù),也就是它們可以阻塞執(zhí)行,也可以不阻塞執(zhí)行。這些函數(shù)都使用了一個(gè)socket,如果它們使用的socket是阻塞的,則這些函數(shù)是阻塞函數(shù);如果它們使用的socket是非阻塞的,則這些函數(shù)是非阻塞函數(shù)。 并發(fā)服務(wù)器 31/37 TCP服務(wù)器端(并發(fā)服務(wù)器) socket( ) bind( ) listen( ) accept( ) send( ) recv( ) close( ) fork( ) //派生新進(jìn)程 close( ) 主進(jìn)程在accept之后派生新進(jìn)程,然后主進(jìn)程繼續(xù)listen,處理新的連接請(qǐng)求 新進(jìn)程自行和客戶端通信,新進(jìn)程和主進(jìn)程搶占CPU WinSock API 32/37 WinSock是一個(gè)基于Socket模型的API,在Microsoft Windows操作系統(tǒng)類中使用。 它在Berkeley接口函數(shù)的基礎(chǔ)之上,還增加了基于消息驅(qū)動(dòng)機(jī)制的Windows擴(kuò)展函數(shù)。 Winscok1.1只支持TCP/IP網(wǎng)絡(luò),WinSock2.0增加了對(duì)更多協(xié)議的支持。 Windows下的Socket編程(1) 33/37 和linux下基本相同,需要包含winsock2.h 需要使用Ws_32.lib,可以用以下語句通告程序編譯時(shí)調(diào)用該庫: #pragma comment(lib,"Ws2_32.lib") ; WinSock以DLL的形式提供,在調(diào)用任何WinSock API之前,必須調(diào)用函數(shù)WSAStartup()進(jìn)行初始化,最后,調(diào)用函數(shù)WSACleanUp()作清理工作。 Windows下的Socket編程(2) 34/37 WSADATA wsd; //設(shè)置WINSOCK的版本 WORD wVersionRequested=MAKEWORD(2,2); WSAStartup(wVersionRequested,&wsd) ; //初始化 。。。。。。。。。 WSACleanUp(); Windows下的Socket編程(3) 35/37 MFC提供了兩個(gè)類CAsyncSocket和CSocket來封裝WinSock API,提供了更簡(jiǎn)單的網(wǎng)絡(luò)編程接口。 CAsyncSocket在較低層次上封裝了WinSock API,缺省情況下,使用該類創(chuàng)建的socket是非阻塞的socket,所有操作都會(huì)立即返回,如果沒有得到結(jié)果,返回WSAEWOULDBLOCK,表示是一個(gè)阻塞操作。 Windows下的Socket編程(4) 36/37 CSocket是CAsyncSocket的派生類,缺省情況下使用該類創(chuàng)建的socket是非阻塞的socket,但是CSocket的網(wǎng)絡(luò)I/O是阻塞的,它在完成任務(wù)之后才返回。 CSocket的阻塞不是建立在“阻塞”socket的基礎(chǔ)上,而是在“非阻塞”socket上實(shí)現(xiàn)的阻塞操作 網(wǎng)絡(luò)編程作業(yè)要求 37/37 不分組,每人獨(dú)立完成。 基于C/S或P2P結(jié)構(gòu),使用UDP或TCP協(xié)議皆可。 最好使用基本SOCKET API,不反對(duì)使用CAsyncSocket和 CSocket類,但不準(zhǔn)使用和傳輸相關(guān)的控件。 期末提交設(shè)計(jì)文檔,源碼,及可執(zhí)行文件。 提交時(shí)間為12月份,具體提交日期及提交方式待定。 可選題目I BBS發(fā)帖程序 通過term方式(202.38.64.3:23)或者通過web方式 完成在test版發(fā)一貼的功能(多發(fā)會(huì)被永久封賬號(hào)) Bbs賬號(hào)和密碼使用命令行參數(shù)或其他方式設(shè)置,不要直接寫在程序里 對(duì)于term方式下的程序,要求能監(jiān)視程序運(yùn)行過程(也就是說在程序運(yùn)行的時(shí)候要把服務(wù)器的輸出打印到屏幕) Referrence: http協(xié)議:http://en./wiki/HTTP 38/37 可選題目II 完成一個(gè)HTTP服務(wù)器 使用HTTP 1.1協(xié)議 支持最大至少10個(gè)并發(fā)連接(fork創(chuàng)建子進(jìn)程) 要求服務(wù)器程序運(yùn)行以后,能在瀏覽器中訪問文件,正常顯示 Web server的根目錄使用命令行參數(shù)或者其他方式制定,不要寫在程序代碼里 文件不存在時(shí)返回瀏覽器404錯(cuò)誤 39/37 40/37 謝謝 |
|
|