理解socket
套接字(socket)屬于糟糕的翻譯,因?yàn)樗А!疤捉印鄙钪杏玫蒙?,加個(gè)“字”,成了套接字。當(dāng)你學(xué)了socket以后,你還是很難將名字與它的意思聯(lián)系起來。香港翻譯成:網(wǎng)路插座 感覺貼近一些
socket編程流程
socket編程就是套路,很多代碼都是可重用的。而且步驟很固定,下面給出一個(gè)流程(事實(shí)上網(wǎng)絡(luò)編程就是按照這個(gè)流程來的,希望大家能記住這個(gè))。
函數(shù)順序
大家只要牢記這一流程去操作,那么進(jìn)行簡單的單道程序設(shè)計(jì)是很簡單的,也就是我們常說的一對(duì)一的服務(wù)器和客戶端得關(guān)系。如果你想要?jiǎng)?chuàng)建一個(gè)服務(wù)器同時(shí)連接多個(gè)客戶端的話就要去了解 多進(jìn)程或者多線程了。但是這個(gè)效率并不是很高,這樣一直開下去負(fù)擔(dān)是很大的,所以就會(huì)有高并發(fā)。大家可以先學(xué)習(xí)函數(shù)順序,多進(jìn)程、高并發(fā)等概念等將來深入之后再回過來了解。
套接字函數(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ù)無差錯(cuò)、無重復(fù)地發(fā)送,且按發(fā)送順序接收。內(nèi)設(shè)流量控制,避免數(shù)據(jù)流超限;數(shù)據(jù)被看作是字節(jié)流,無長度限制。文件傳送協(xié)議(FTP)即使用流式套接字。
(2)二是數(shù)據(jù)報(bào)式套接字(SOCK_DGRAM)提供了一個(gè)無連接服務(wù)。數(shù)據(jù)包以獨(dú)立包形式被發(fā)送,不提供無錯(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說明該套接字使用的特定協(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)系起來,即將名字賦予套接字,以指定本地半相關(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的本地地址(名字),其長度可變,結(jié)構(gòu)隨通信域的不同而不同。namelen表明了name的長度。如果沒有錯(cuò)誤發(fā)生,bind()返回0。否則返回SOCKET_ERROR。
(三)建立套接字連接-connect()與accept()
這兩個(gè)系統(tǒng)調(diào)用用于完成一個(gè)完整相關(guān)的建立,其中connect()用于建立連接。accept()用于使服務(wù)器等待來自某客戶進(jìn)程的實(shí)際連接。
(1)connect()的調(diào)用格式如下:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
參數(shù)s是欲建立連接的本地套接字描述符。參數(shù)name指出說明對(duì)方套接字地址結(jié)構(gòu)的指針。對(duì)方套接字地址長度由namelen說明。如果沒有錯(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()無須協(xié)議作為參數(shù)。
(2)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)的指針,用來接收連接實(shí)體的地址。addr的確切格式由套接字創(chuàng)建時(shí)建立的地址族決定。addrlen 為客戶方套接字地址的長度(字節(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è)客戶方套接字地址及長度放入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ù)器、是否面向連接無關(guān)。bind()指定五元組中的本地二元,即本地主機(jī)地址和端口號(hào),其用法與是否面向連接有關(guān):在服務(wù)器方,無論是否面向連接,均要調(diào)用bind(),若采用面向連接,則可以不調(diào)用bind(),而通過connect()自動(dòng)完成。若采用無連接,客戶方必須使用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ì)列的最大長度,用于限制排隊(duì)請(qǐng)求的個(gè)數(shù),目前允許的最大值為5。如果沒有錯(cuò)誤發(fā)生,listen()返回0。否則它返回SOCKET_ERROR。
listen()在執(zhí)行調(diào)用過程中可為沒有調(diào)用過bind()的套接字s完成所必須的連接,并建立長度為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()。
(1)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ū)的指針,其長度由len 指定。flags 指定傳輸控制方式,如是否發(fā)送帶外數(shù)據(jù)等。如果沒有錯(cuò)誤發(fā)生,send()返回總共發(fā)送的字節(jié)數(shù)。否則它返回SOCKET_ERROR。
(2)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ū)的指針,其長度由len 指定。flags 指定傳輸控制方式,如是否接收帶外數(shù)據(jù)等。如果沒有錯(cuò)誤發(fā)生,recv()返回總共接收的字節(jié)數(shù)。如果連接被關(guān)閉,返回0。否則它返回SOCKET_ERROR。
(六)關(guān)閉套接字-close()
close()關(guān)閉套接字s,并釋放分配給該套接字的資源;如果s涉及一個(gè)打開的TCP連接,則該連接被釋放。
簡單代碼實(shí)現(xiàn)TCP協(xié)議的服務(wù)器
這段代碼實(shí)現(xiàn)了一個(gè)簡單的單線程TCP服務(wù)器,它可以接受客戶端連接,并發(fā)送響應(yīng)消息。當(dāng)客戶端連接時(shí),服務(wù)器會(huì)接收客戶端發(fā)送的消息,并向客戶端發(fā)送"Hello, Client!"的響應(yīng)消息。這是一個(gè)單線程服務(wù)器,只能處理一個(gè)客戶端連接。
現(xiàn)在我們來逐步解釋代碼:
(一)包含了一些必要的頭文件,包括stdio.h、stdlib.h、string.h、sys/socket.h、arpa/inet.h和unistd.h。這些頭文件提供了與套接字、網(wǎng)絡(luò)通信等相關(guān)的函數(shù)和數(shù)據(jù)結(jié)構(gòu)。
(二)定義了一些常量,包括服務(wù)器的IP地址(SERVER_IP)、端口號(hào)(SERVER_PORT)以及接收數(shù)據(jù)的緩沖區(qū)大?。˙UFFER_SIZE)。
(三)主函數(shù)main()開始執(zhí)行,定義了一些變量:server_fd表示服務(wù)器套接字,clientSocket表示客戶端套接字,serveraddr和clientaddr分別表示服務(wù)器和客戶端的地址信息,clientaddrlen表示客戶端地址信息的長度,buffer表示用于存儲(chǔ)數(shù)據(jù)的緩沖區(qū)。
(四)創(chuàng)建客戶端套接字:調(diào)用socket函數(shù)創(chuàng)建一個(gè)服務(wù)器端套接字,并檢查返回值是否為-1。如果創(chuàng)建失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。
(五)設(shè)置服務(wù)器地址信息:將服務(wù)器地址結(jié)構(gòu)體serveraddr的各個(gè)字段清零,然后設(shè)置地址族為IPv4(AF_INET),端口號(hào)為SERVER_PORT,IP地址為INADDR_ANY,表示服務(wù)器可以監(jiān)聽任意網(wǎng)絡(luò)接口上的連接。
(六)綁定套接字到指定地址和端口:調(diào)用bind函數(shù)將套接字綁定到指定的地址和端口,如果綁定失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。
(七)監(jiān)聽連接請(qǐng)求:調(diào)用listen函數(shù)開始監(jiān)聽連接請(qǐng)求,如果監(jiān)聽失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。服務(wù)器可以接受的等待連接的最大數(shù)量為5。
(八)打印服務(wù)器監(jiān)聽的端口號(hào),表示服務(wù)器已經(jīng)在該端口號(hào)上進(jìn)行監(jiān)聽。
(九)接受客戶端連接:調(diào)用accept函數(shù)接受客戶端的連接請(qǐng)求。如果接受失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。
(十)打印客戶端的IP地址,表示客戶端已成功連接。
(十一)接收數(shù)據(jù):使用recv函數(shù)接收客戶端發(fā)送的數(shù)據(jù),并將數(shù)據(jù)存儲(chǔ)在buffer緩沖區(qū)中。如果接收失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。
(十二)打印接收到的客戶端消息。
(十三)發(fā)送響應(yīng):使用send函數(shù)向客戶端發(fā)送響應(yīng)消息,即"Hello, Client!"。如果發(fā)送失敗,程序打印出錯(cuò)信息,并使用exit(1)終止程序。
(十四)關(guān)閉套接字:調(diào)用close函數(shù)關(guān)閉客戶端套接字和服務(wù)器套接字。
(十五)返回0,表示程序順利執(zhí)行結(jié)束。