套接字編程的總結(jié)_第1頁
套接字編程的總結(jié)_第2頁
套接字編程的總結(jié)_第3頁
套接字編程的總結(jié)_第4頁
套接字編程的總結(jié)_第5頁
已閱讀5頁,還剩51頁未讀 繼續(xù)免費(fèi)閱讀

付費(fèi)下載

下載本文檔

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

文檔簡介

套接字編程的總結(jié)第1篇套接字編程的總結(jié)第1篇

ServerSocket是創(chuàng)建TCP服務(wù)端Socket的API。

服務(wù)器使用的TCPSocket對象(傳入的端口,就是要公開的端口,一般稱為監(jiān)聽(listen)端口)

注意:

accept:接起電話(服務(wù)器是電話鈴響的這一方)

Socket對象:建立起的連接

close:掛電話(誰都可以掛)

Socket是客戶端Socket,或服務(wù)端中接收到客戶端建立連接(accept方法)的請求后,返回的服務(wù)端Socket。

不管是客戶端還是服務(wù)端Socket,都是雙方建立連接以后,保存的對端信息,及用來與對方收發(fā)數(shù)據(jù)的。

注意:

注意:

TCP發(fā)送數(shù)據(jù)時(shí),需要先建立連接,什么時(shí)候關(guān)閉連接就決定是短連接還是長連接:

對比以上長短連接,兩者區(qū)別如下:

擴(kuò)展了解:

基于BIO(同步阻塞IO)的長連接會一直占用系統(tǒng)資源。對于并發(fā)要求很高的服務(wù)端系統(tǒng)來說,這樣的消耗是不能承受的。

由于每個(gè)連接都需要不停的阻塞等待接收數(shù)據(jù),所以每個(gè)連接都會在一個(gè)線程中運(yùn)行。一次阻塞等待對應(yīng)著一次請求、響應(yīng),不停處理也就是長連接的特性:一直不關(guān)閉連接,不停的處理請求。

實(shí)際應(yīng)用時(shí),服務(wù)端一般是基于NIO(即同步非阻塞IO)來實(shí)現(xiàn)長連接,性能可以極大的提升。

現(xiàn)在還遺留一個(gè)問題:

如果同時(shí)多個(gè)長連接客戶端,連接該服務(wù)器,能否正常處理?

需要在IDEA配置客戶端支持同時(shí)運(yùn)行多個(gè)實(shí)例!

所以可以使用多線程解決長連接客戶端不支持同時(shí)在線的問題:

將任務(wù)專門交給其他線程來處理,主線程只負(fù)責(zé)接受socket。

這里僅演示短連接,長連接和多線程在博主的個(gè)人倉庫下:

TCP服務(wù)端:

TCP客戶端:

套接字編程的總結(jié)第2篇fd為要寫入的文件的描述符,buf為要寫入的數(shù)據(jù)的緩沖區(qū)地址,nbytes為要寫入的數(shù)據(jù)的字節(jié)數(shù)。write()函數(shù)會將緩沖區(qū)buf中的nbytes個(gè)字節(jié)寫入文件fd,成功則返回寫入的字節(jié)數(shù),失敗則返回-1。read()的原型為:

fd為要讀取的文件的描述符,buf為要接收數(shù)據(jù)的緩沖區(qū)地址,nbytes為要讀取的數(shù)據(jù)的字節(jié)數(shù)。

套接字編程的總結(jié)第3篇由系統(tǒng)提供用于網(wǎng)絡(luò)通信的技術(shù),是基于TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元。基于Socket套接字的網(wǎng)絡(luò)程序開發(fā)就是網(wǎng)絡(luò)編程。

Socket套接字主要針對傳輸層協(xié)議,分為三類:1、流套接字:使用傳輸層TCP協(xié)議TCP的特點(diǎn):有連接、可靠傳輸、面向字節(jié)流,全雙工對于字節(jié)流來說,可以簡單的理解為,傳輸數(shù)據(jù)是基于IO流,流式數(shù)據(jù)的特征就是在IO流沒有關(guān)閉的情況下,是無邊界的數(shù)據(jù),可以多次發(fā)送,也可以分開多次接收。

2、數(shù)據(jù)報(bào)套接字:使用傳輸層UDP協(xié)議UDP的特點(diǎn):無連接、不可靠傳輸、面向數(shù)據(jù)報(bào),全雙工對于數(shù)據(jù)報(bào)來說,可以簡單的理解為,傳輸數(shù)據(jù)是一塊一塊的,發(fā)送一塊數(shù)據(jù)假如100個(gè)字節(jié),必須一次發(fā)送,接收也必須一次接收100個(gè)字節(jié),而不能分100次,每次接收1個(gè)字節(jié)。

3、原始套接字

套接字編程的總結(jié)第4篇只能處理IPv4的ip地址不可重入函數(shù)注意參數(shù)是structin_addr

支持IPv4和IPv6可重入函數(shù)其中inet_pton和inet_ntop不僅可以轉(zhuǎn)換IPv4的in_addr,還可以轉(zhuǎn)換IPv6的in6_addr。

socket模型流程圖

在Linux下創(chuàng)建socket

綁定端口號

socket()函數(shù)用來創(chuàng)建套接字,確定套接字的各種屬性,然后服務(wù)器端要用bind()函數(shù)將套接字與特定的IP地址和端口綁定起來,只有這樣,流經(jīng)該IP地址和端口的數(shù)據(jù)才能交給套接字處理;而客戶端要用connect()函數(shù)建立連接。

套接字編程的總結(jié)第5篇1)首先會檢查緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),那么就讀取,否則函數(shù)會被阻塞,直到網(wǎng)絡(luò)上有數(shù)據(jù)到來。

2)如果要讀取的數(shù)據(jù)長度小于緩沖區(qū)中的數(shù)據(jù)長度,那么就不能一次性將緩沖區(qū)中的所有數(shù)據(jù)讀出,剩余數(shù)據(jù)將不斷積壓,直到有read()/recv()函數(shù)再次讀取。

3)直到讀取到數(shù)據(jù)后read()/recv()函數(shù)才會返回,否則就一直被阻塞。

這就是TCP套接字的阻塞模式。所謂阻塞,就是上一步動作沒有完成,下一步動作將暫停,直到上一步動作完成后才能繼續(xù),以保持同步性。

TCP套接字默認(rèn)情況下是阻塞模式

和C語言教程一樣,我們從一個(gè)簡單的“HelloWorld!”程序切入socket編程。

演示了Linux下的代碼,是服務(wù)器端代碼,是客戶端代碼,要實(shí)現(xiàn)的功能是:客戶端從服務(wù)器讀取一個(gè)字符串并打印出來。

服務(wù)器端代碼:

客戶端代碼:

先編譯

并運(yùn)行:

正常情況下,程序運(yùn)行到accept()函數(shù)就會被阻塞,等待客戶端發(fā)起請求。接下來編譯

并運(yùn)行:

client運(yùn)行后,通過connect()函數(shù)向server發(fā)起請求,處于監(jiān)聽狀態(tài)的server被激活,執(zhí)行accept()函數(shù),接受客戶端的請求,然后執(zhí)行write()函數(shù)向client傳回?cái)?shù)據(jù)。client接收到傳回的數(shù)據(jù)后,connect()就運(yùn)行結(jié)束了,然后使用read()將數(shù)據(jù)讀取出來。需要注意的是:server只接受一次client請求,當(dāng)server向client傳回?cái)?shù)據(jù)后,程序就運(yùn)行結(jié)束了。如果想再次接收到服務(wù)器的數(shù)據(jù),必須再次運(yùn)行server,所以這是一個(gè)非常簡陋的socket程序,不能夠一直接受客戶端的請求。

套接字編程的總結(jié)第6篇源文件包含迭代的、無連接TIME服務(wù)器所用的代碼。本題程序調(diào)用了自定義例程庫函數(shù)passivesock分配和綁定服務(wù)器套接口。

該結(jié)構(gòu)體即本篇博客開頭的背景知識部分所提到的新的通用套接字地址結(jié)構(gòu)體,兼容IPv6。

intrecvfrom(ints,void*buf,intlen,unsignedintflags,structsockaddr*from,int*fromlen)

用于從(已連接)套接口上接收數(shù)據(jù),并捕獲數(shù)據(jù)發(fā)送源的地址。

uint32htonl(uint32hl)

htonl()將主機(jī)數(shù)轉(zhuǎn)換成無符號長整型的網(wǎng)絡(luò)字節(jié)順序。本函數(shù)將一個(gè)32位數(shù)從主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序??深惐扔趎tohl()

intsendto(sockets,constvoid*msg,intlen,unsignedintflags,conststructsockaddr*to,inttolen)

sendto()指向一指定目的地發(fā)送數(shù)據(jù),sendto()適用于發(fā)送未建立連接的UDP數(shù)據(jù)包(參數(shù)為SOCK_DGRAM)。sendto()用來將數(shù)據(jù)由指定的socket傳給對方主機(jī)。

參數(shù)s為已建好連線的socket,如果利用UDP協(xié)議則不需經(jīng)過連線操作

參數(shù)msg指向欲連線的數(shù)據(jù)內(nèi)容

參數(shù)len數(shù)據(jù)內(nèi)容長度

參數(shù)flags一般設(shè)0

參數(shù)to用來指定欲傳送的網(wǎng)絡(luò)地址

參數(shù)tolen為sockaddr的結(jié)構(gòu)長度

套接字編程的總結(jié)第7篇源文件中定義的函數(shù)passivesock包含分配和綁定服務(wù)器套接口的細(xì)節(jié)。該函數(shù)通常作為庫例程被其它程序調(diào)用(如和等)。

在本篇博客前面部分有講解到addrinfo結(jié)構(gòu)體,其中hints參數(shù)的ai_flags成員沒有細(xì)說,在服務(wù)器庫例程中又有涉及,故在此敘述下:

ai_flags參數(shù):

AI_PASSIVE:設(shè)置了AI_PASSIVE標(biāo)志,則

AI_CANONNAME:請求canonical(主機(jī)的officialname)名字。如果設(shè)置了該標(biāo)志,那么res返回的第一個(gè)structaddrinfo中的ai_canonname域會存儲officialname的指針。

AI_NUMERICHOST:阻止域名解析。

AI_NUMERICSERV:阻止服務(wù)名解析。

AI_V4MAPPED:當(dāng)ai_family指定為AF_INT6(IPv6)時(shí),如果沒有找到IPv6地址,那么會返回IPv4-mappedIPv6地址。

intsetsockopt(ints,intlevel,intoptname,constvoid*optval,socklen_toptlen)

函數(shù)說明:setsockopt()用來設(shè)置參數(shù)s所指定的socket狀態(tài)

參數(shù)level代表欲設(shè)置的網(wǎng)絡(luò)層,一般設(shè)成SOL_SOCKET以存取socket層

參數(shù)optname代表欲設(shè)置的選項(xiàng),有下列幾種數(shù)值:

SO_DEBUG打開或關(guān)閉排錯(cuò)模式

SO_REUSEADDR允許在bind()過程中本地地址可重復(fù)使用

SO_TYPE返回socket形態(tài)

SO_ERROR返回socket已發(fā)生的錯(cuò)誤原因

SO_DONTROUTE送出的數(shù)據(jù)包不要利用路由設(shè)備來傳輸

SO_BROADCAST使用廣播方式傳送

SO_SNDBUF設(shè)置送出的暫存區(qū)大小

SO_RCVBUF設(shè)置接收的暫存區(qū)大小

SO_KEEPALIVE定期確定連線是否已終止.

SO_OOBINLINE當(dāng)接收到OOB數(shù)據(jù)時(shí)會馬上送至標(biāo)準(zhǔn)輸入設(shè)備

SO_LINGER確保數(shù)據(jù)安全且可靠的傳送出去.

參數(shù)optval代表欲設(shè)置的值

參數(shù)optlen則為optval的長度

intbind(intsocket,conststructsockaddr*address,socklen_taddress_len)

通過socket系統(tǒng)調(diào)用創(chuàng)建的文件描述符并不能直接使用,TCP/UDP協(xié)議中所涉及的協(xié)議、IP、端口等基本要素并未體現(xiàn),而bind()系統(tǒng)調(diào)用就是將這些要素與文件描述符關(guān)聯(lián)起來。

intlisten(intsocket,intbacklog)

使用socket系統(tǒng)調(diào)用創(chuàng)建一個(gè)套接字時(shí),它被假設(shè)是一個(gè)主動套接字(客戶端套接字),而調(diào)用listen()系統(tǒng)調(diào)用就是將這個(gè)主動套接字轉(zhuǎn)換成被動套接字,指示內(nèi)核應(yīng)接受指向該套接字的連接請求。返回值為0則成功,-1表示失敗并設(shè)置errno值。

socket:socket監(jiān)聽文件描述符。

backlog:設(shè)置未完成連接隊(duì)列和已完成連接隊(duì)列各自的隊(duì)列長度(注意:不同的系統(tǒng)對該值的解釋會存在差異)。Linux系統(tǒng)下,SYNQUEUE隊(duì)列長度閾值存放在/proc/sys/net/ipv4/tcp_max_syn_backlog文件中,ACCEPTQUEUE隊(duì)列長度閾值存放在/proc/sys/net/core/somaxconn文件中。兩個(gè)隊(duì)列長度的計(jì)算公式如下:

套接字編程的總結(jié)第8篇

注:三次握手,指在建立鏈接過程中,客戶端先向服務(wù)端發(fā)出syn請求,然后服務(wù)端對客戶端進(jìn)行ack回復(fù)及syn請求,客戶端再進(jìn)行ack請求,來回了三次才能確定可以建立鏈接(因?yàn)門CP協(xié)議是面向連接的,所以必須確定客戶端和服務(wù)端均在線)

以上函數(shù)的實(shí)現(xiàn)及解析會在代碼中實(shí)現(xiàn)介紹

直接上代碼,代碼里面詳細(xì)的解析

阿鯉在這里直接給大家github的鏈接,里面有以下四種類型

注意:因?yàn)閠cp協(xié)議存在三次握手,在傳輸信息時(shí)會服務(wù)端每次都會新建立一個(gè)新的socket所以在多個(gè)客戶端向服務(wù)端發(fā)出請求時(shí),會覆蓋原先的socket的操作句柄(fd),導(dǎo)致之前的客戶端鏈接失效,所以需要多線程或多進(jìn)程實(shí)現(xiàn)

套接字編程的總結(jié)第9篇

為了執(zhí)行網(wǎng)絡(luò)I/O,一個(gè)進(jìn)程必須做的第一件事就是調(diào)用socket函數(shù)(本質(zhì)就是打開網(wǎng)絡(luò)文件),指定期望通信的協(xié)議類型(使用IPV4的TCP、使用IPV6的UDP、Unix域字節(jié)流協(xié)議等)。

參數(shù)說明:

domain參數(shù):指明協(xié)議族,即你想要使用什么協(xié)議(IPV4、IPV6...),它是下列表格中的某個(gè)常值。該參數(shù)也往往被稱為協(xié)議域。

規(guī)定:我們接下來所使用的套接字所采用的協(xié)議都是AF_INET(IPV4協(xié)議)

type參數(shù):指明套接字的類型,它是下列表格中的某個(gè)常值。

如果你是要TCP通信的話,就要是要SOCK_STREAM作為類型,UDP就使用SOCK_DGRAM作為類型。

protocol參數(shù):創(chuàng)建套接字的協(xié)議類別。你可以指明為TCP或UDP,但該字段一般直接設(shè)置為0就可以了,設(shè)置為0表示的就是默認(rèn),此時(shí)會根據(jù)傳入的前兩個(gè)參數(shù)自動推導(dǎo)出你最終需要使用的是哪種協(xié)議。

返回值說明:

套接字創(chuàng)建成功返回一個(gè)文件描述符,創(chuàng)建失敗返回-1,同時(shí)錯(cuò)誤碼會被設(shè)置。

bind函數(shù)是把一個(gè)協(xié)議地址賦予一個(gè)套接字。

參數(shù)說明:

sockfd參數(shù):綁定的文件的文件描述符。也就是我們創(chuàng)建套接字時(shí)獲取到的文件描述符。

addr參數(shù):這個(gè)參數(shù)是指向一個(gè)特定于協(xié)議的地址結(jié)構(gòu)的指針。里面包含了協(xié)議族、端口號、IP地址等。(見下一節(jié)sockaddr結(jié)構(gòu)中的介紹)

addrlen參數(shù):是該協(xié)議的地址結(jié)構(gòu)的長度。

返回值說明:

綁定成功返回0,綁定失敗返回-1,同時(shí)錯(cuò)誤碼會被設(shè)置。

listen函數(shù)僅由TCP服務(wù)器調(diào)用,表明服務(wù)器對外宣告它愿意接受連接請求,它做兩件事:

1.當(dāng)socket函數(shù)創(chuàng)建一個(gè)套接字時(shí),它被假設(shè)為一個(gè)主動套接字,也就是說將調(diào)用connect發(fā)起連接的客戶端套接字。listen函數(shù)把一個(gè)未連接的套接字轉(zhuǎn)換成一個(gè)被動套接字,指示內(nèi)核應(yīng)接受指向該套接字的連接請求。簡單來說,服務(wù)器調(diào)用listen函數(shù),就是告訴客戶端你可以連接我了。

2.第二個(gè)參數(shù)規(guī)定了內(nèi)核應(yīng)該為相應(yīng)的套接字排隊(duì)的最大連接個(gè)數(shù)。backlog提供了一個(gè)提示,提示系統(tǒng)該進(jìn)程要入隊(duì)的未完成連接的請求數(shù)量。其實(shí)際由系統(tǒng)決定,對于TCP而言,默認(rèn)是128。

一旦隊(duì)列滿了,系統(tǒng)就會拒絕多余的連接請求,所有backlog的值應(yīng)該基于服務(wù)器期望負(fù)載和處理量來選擇,其中處理量是指接受連接請求與啟動服務(wù)的數(shù)量。

一旦服務(wù)器調(diào)用了listen,所用的套接字就能接受連接請求。使用accept函數(shù)獲得的連接請求并建立連接。

返回值:成功返回0,失敗返回-1;

本函數(shù)通常應(yīng)該在調(diào)用socket和bind這兩個(gè)函數(shù)之后,并在調(diào)用accept函數(shù)之前調(diào)用。

accept函數(shù)是由TCP服務(wù)器調(diào)用,用于從已完成連接隊(duì)列隊(duì)頭返回下一個(gè)已完成連接。如果已完成連接隊(duì)列為空,那么進(jìn)程將被投入睡眠。

如果accept成功,那么其返回值是由內(nèi)核自動生成的一個(gè)全新描述符,代表與所返回客戶的TCP連接。我們常常稱它的第一個(gè)參數(shù)為監(jiān)聽套接字(listening

socket)描述符(由socket創(chuàng)建,隨后用作bind和listen的第一個(gè)參數(shù)的描述符),稱它的返回值為已連接套接字(connected

socket)

描述符。區(qū)分這兩個(gè)套接字非常重要。一個(gè)服務(wù)器通常僅僅創(chuàng)建一個(gè)監(jiān)聽套接字,它在該服務(wù)器的生命期內(nèi)一直存在。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶連接創(chuàng)建一個(gè)已連接套接字(也就是說對于它的TCP三路握手過程已經(jīng)完成)。當(dāng)服務(wù)器完成對某個(gè)給定客戶的服務(wù)時(shí),相應(yīng)的已連接套接字就被關(guān)閉。

總的來說,函數(shù)accept所返回的文件描述符是新的套接字描述符,該描述符連接到調(diào)用connect的客戶端。這個(gè)新的套接字描述符和原始套接字(sockfd)具有相同的套接字類型和地址族。傳給accept的原始套接字沒有關(guān)聯(lián)到這個(gè)連接,而是繼續(xù)保持監(jiān)聽狀態(tài)并接受其他連接請求。

TCP客戶用connect函數(shù)來建立與TCP服務(wù)器的連接。

參數(shù)說明:

sockfd參數(shù):是由socket函數(shù)返回的套接字描述符,第二個(gè)以及第三個(gè)參數(shù)分別是指向套接字地址結(jié)構(gòu)的指針和該結(jié)構(gòu)的大小。

在connect中指定的地址是我們想要與之通信服務(wù)器地址。如果sockfd沒有綁定到一個(gè)地址,connect會給調(diào)用者綁定一個(gè)默認(rèn)地址。

返回值說明:

若成功則為0,若出錯(cuò)則為-1;

從介紹的套接字函數(shù)接口來看,bind函數(shù)、accept函數(shù)和conect函數(shù)都有一個(gè)structsockaddr的結(jié)構(gòu)體指針,我們在介紹參數(shù)的時(shí)候也已經(jīng)說了,這種結(jié)構(gòu)是指向一個(gè)特定于協(xié)議的地址結(jié)構(gòu)的指針。里面包含了協(xié)議族、端口號、IP地址等。

在網(wǎng)絡(luò)通信的時(shí)候,其標(biāo)準(zhǔn)方式有多種,比如:IPV4套接字地址結(jié)構(gòu)——structsockaddr_in,Unix域套接字地址結(jié)構(gòu)——structsockaddr_un;前者屬于網(wǎng)絡(luò)通信,后者屬于域間通信;

也就是說我們的套接字接口就這么一套,但是通信方式確有多種,你只需要給這個(gè)結(jié)構(gòu)體(structsockaddr)傳輸你想要的通信方式即可;其實(shí)也不難看出,這種就類似于多態(tài),所有的通信方式都是子類,structsockaddr就是父類,父類指向不同的子類,就使用不同的方法;

我們要做的就是在使用的時(shí)進(jìn)行強(qiáng)制類型轉(zhuǎn)換即可;可能你會想到在C語言中有一個(gè)指針void*,它的功能就是可以接受任意類型的指針,再進(jìn)行強(qiáng)轉(zhuǎn)也可以。但是,早期在設(shè)計(jì)的時(shí)候還沒有void*這種指針,所以這種用法一直延續(xù)至今。

大多數(shù)套接字函數(shù)都需要指向套接字地址結(jié)構(gòu)的指針作為參數(shù)。每個(gè)協(xié)議族都定義了它自己的套接字地址結(jié)構(gòu)。這些地址結(jié)構(gòu)的名字均已sockaddr_開頭。

由于通信方式的種類有多種,套接字接口只有一套,如果給每個(gè)通信方式都設(shè)計(jì)一套接口,單從技術(shù)的角度來說,完全是可以的。但是從使用者和學(xué)習(xí)者來講,無疑是增加了負(fù)擔(dān)。所以早期在設(shè)計(jì)的時(shí)候,就單獨(dú)設(shè)計(jì)了一個(gè)通用的套接字地址結(jié)構(gòu),我們只要給這個(gè)通用的套接字地址結(jié)構(gòu)傳入不同的套接字地址結(jié)構(gòu),然后進(jìn)行強(qiáng)轉(zhuǎn)。在地址結(jié)構(gòu)中給到我們想要通信的IP地址、端口號以及所采用的協(xié)議族。

其中3個(gè)結(jié)構(gòu)里都包含了__SOCKADDR_COMMON這個(gè)宏,我們先把它的定義找到;

這三個(gè)結(jié)構(gòu)的第一個(gè)字段都是一個(gè)unsignedshortint類型,只不過用宏來定義了三個(gè)不同的名字,至此第一個(gè)結(jié)構(gòu)就清楚了,在一般環(huán)境下(short一般為2個(gè)字節(jié)),整個(gè)結(jié)構(gòu)占用16個(gè)字節(jié),變量sa_family占用2個(gè)字節(jié),變量sa_data保留14個(gè)字節(jié)用于保存IP地址信息。

接著我們發(fā)現(xiàn)第二個(gè)結(jié)構(gòu)中還有in_port_t和structin_addr兩個(gè)類型沒有定義,繼續(xù)找下去吧:

這么看來sockaddr_in這個(gè)結(jié)構(gòu)也不復(fù)雜,除了一開始的2個(gè)字節(jié)表示sin_family,然后是2個(gè)字節(jié)的變量sin_port表示端口,接著是4個(gè)字節(jié)的變量sin_addr表示IP地址,最后是8個(gè)字節(jié)變量sin_zero填充尾部,用來與結(jié)構(gòu)sockaddr對齊。

那接下來我們整理一下,為了看的清楚,部分結(jié)構(gòu)使用偽代碼,不能通過編譯,主要是方便理解:

附圖:源碼結(jié)構(gòu)

套接字編程的總結(jié)第10篇當(dāng)套接字處于監(jiān)聽狀態(tài)時(shí),可以通過accept()函數(shù)來接收客戶端請求。

它的參數(shù)與listen()和connect()是相同的:sock為服務(wù)器端套接字,addr為sockaddr_in結(jié)構(gòu)體變量,addrlen為參數(shù)addr的長度,可由sizeof()求得。

accept()返回一個(gè)新的套接字來和客戶端通信,addr保存了客戶端的IP地址和端口號,而sock是服務(wù)器端的套接字,大家注意區(qū)分。后面和客戶端通信時(shí),要使用這個(gè)新生成的套接字,而不是原來服務(wù)器端的套接字。

套接字編程的總結(jié)第11篇舉個(gè)栗子:

關(guān)于端口被占用的問題:

如果一個(gè)進(jìn)程A已經(jīng)綁定了一個(gè)端口,再啟動一個(gè)進(jìn)程B綁定該端口,就會報(bào)錯(cuò),這種情況也叫端口被占用。對于java進(jìn)程來說,端口被占用的常見報(bào)錯(cuò)信息如下:

此時(shí)需要檢查進(jìn)程B綁定的是哪個(gè)端口,再查看該端口被哪個(gè)進(jìn)程占用。以下為通過端口號查進(jìn)程的方式:

在cmd輸入netstat-ano|findstr端口號,則可以顯示對應(yīng)進(jìn)程的pid。如以下命令顯示了8888進(jìn)程的pid

在任務(wù)管理器中,通過pid查找進(jìn)程解決端口被占用的問題:

套接字編程的總結(jié)第12篇網(wǎng)絡(luò)編程的核心,是SocketAPI,這是一個(gè)由操作系統(tǒng)給應(yīng)用程序提供的網(wǎng)絡(luò)編程API。

并且我們認(rèn)為SocketAPI是和傳輸層密切相關(guān)的。

Socket套接字主要針對傳輸層協(xié)議分為以下幾類:

流套接字:使用傳輸層TCP協(xié)議

數(shù)據(jù)報(bào)套接字:使用傳輸層UDP協(xié)議

無連接、有連接:

打電話就是有連接的,需要連接建立了才能通信。連接建立需要對方來接收,如果連接沒有建立好,就通信不了。

發(fā)短信、發(fā)微信就是無連接的。

不可靠傳輸、可靠傳輸:

網(wǎng)絡(luò)環(huán)境天然就是復(fù)雜的,不可能保證傳輸?shù)臄?shù)據(jù)100%能夠到達(dá)。發(fā)送方能知道自己的消息是發(fā)送過去了還是丟了,就是可靠\不可靠傳輸。

面向字節(jié)流、面向數(shù)據(jù)報(bào):

數(shù)據(jù)傳輸就和文件讀寫類似,“流式”的,就叫面向字節(jié)流

數(shù)據(jù)傳輸以一個(gè)個(gè)的“數(shù)據(jù)報(bào)”(可能是若干字節(jié),帶有一定格式的)為基本單位,就叫面向數(shù)據(jù)報(bào)。

全雙工、半雙工:

一個(gè)通信通道,可以雙向傳輸,既可以發(fā)送也可以接收就叫做全雙工。

只能單向傳輸?shù)木徒凶霭腚p工。

Java中使用UDP協(xié)議通信,主要基于DatagramSocket類來創(chuàng)建數(shù)據(jù)報(bào)套接字,并使用DatagramPacket作為發(fā)送或接收的UDP數(shù)據(jù)報(bào),對于一次發(fā)送及接收UDP數(shù)據(jù)報(bào)的流程如下:

以上只是一次發(fā)送端的UDP數(shù)據(jù)報(bào)發(fā)送,及接收端的數(shù)據(jù)報(bào)接收,并沒有返回的數(shù)據(jù)。也就只有請求,沒有響應(yīng)。對于一個(gè)服務(wù)器來說,重要的是提供多個(gè)客戶端的請求處理及響應(yīng),流程如下:

首先先了解一些注意事項(xiàng):

1.客戶端和服務(wù)器:開發(fā)時(shí),一般是基于一個(gè)主機(jī)開啟兩個(gè)進(jìn)程作為客戶端和服務(wù)器,但真實(shí)的場景一般都是不同主機(jī)。

2.注意目的IP和目的端口號,標(biāo)識了一次數(shù)據(jù)傳輸時(shí)要發(fā)送數(shù)據(jù)的終點(diǎn)主機(jī)和進(jìn)程。

編程我們是使用流套接字和數(shù)據(jù)報(bào)套接字,基于傳輸層的TCP或UDP協(xié)議,但應(yīng)用層協(xié)議,也需要考慮,這塊我們在后續(xù)來說明如何設(shè)計(jì)應(yīng)用層協(xié)議。

4.關(guān)于端口被占用的問題:

如果一個(gè)進(jìn)程A已經(jīng)綁定了一個(gè)端口,再啟動一個(gè)進(jìn)程B綁定該端口,就會報(bào)錯(cuò),這就叫端口被占用。對于Java進(jìn)程來說,端口被占用的常見報(bào)錯(cuò)信息如下:

在cmd輸入:netstat-ano|findstr端口號就可以顯示對應(yīng)進(jìn)程的pid,然后在任務(wù)管理器中通過pid查找進(jìn)程。

解決方法:

如果占用端口的進(jìn)程A不需要運(yùn)行,就可以關(guān)閉A后,再啟動需要綁定該端口的進(jìn)程B。

如果需要運(yùn)行A進(jìn)程,則可以修改進(jìn)程B的綁定端口,換為其他沒有使用的端口。

DatagramSocket是UDPSocket,用于發(fā)送和接收UDP數(shù)據(jù)報(bào)。

在操作系統(tǒng)中,把這個(gè)socket對象也當(dāng)成是一個(gè)文件來處理的,相當(dāng)于是文件描述符表上的一項(xiàng)。只不過普通文件對應(yīng)的設(shè)備是硬盤,而socket文件對應(yīng)的設(shè)備是網(wǎng)卡。

DatagramSocket構(gòu)造方法:

DatagramSocket方法:

DatagramPacket是UDPSocket發(fā)送和接收的數(shù)據(jù)報(bào)。

DatagramPacket構(gòu)造方法:

DatagramPacket方法:

普通的服務(wù)器:收到請求,根據(jù)請求計(jì)算響應(yīng),返回響應(yīng)。

而echoserver(回顯服務(wù)器)省略了其中的根據(jù)請求計(jì)算響應(yīng),請求是啥,就返回啥。

先來看一遍完整代碼:

我們一點(diǎn)一點(diǎn)來解析:

在操作系統(tǒng)內(nèi)核中,使用了一種特殊的叫做_socket_這樣的文件來抽象表示網(wǎng)卡,因此進(jìn)行網(wǎng)絡(luò)通信,勢必需要先有一個(gè)socket對象。

同時(shí)對于服務(wù)器來說,創(chuàng)建socket對象的同時(shí),要讓他綁定上一個(gè)具體的端口號,如果是操作系統(tǒng)隨機(jī)分配的端口,此時(shí)客戶端就不知道這個(gè)端口是啥了,也就無法進(jìn)行通信了。

對于UDP來說,傳輸數(shù)據(jù)的基本單位是DatagramPacket,并且用一個(gè)while循環(huán)來表示循環(huán)接收請求,用DatagramPacket來表示接收到的,然后再用receive把這個(gè)數(shù)據(jù)報(bào)給網(wǎng)卡接收到。

此時(shí)的DatagramPacket是一個(gè)特殊的對象,并不方便直接進(jìn)行處理,可以把這里包含的數(shù)據(jù)拿出來,通過構(gòu)造字符串的方式來存到request里面去。

之前給的最大長度是4096,但是這里的空間不一定用滿了,可能只用了一小部分,因此就通過getLength獲取到實(shí)際的數(shù)據(jù)報(bào)長度,只把這個(gè)實(shí)際的有效部分給構(gòu)造成字符串即可。

緊接著我們用一個(gè)process方法來表示服務(wù)器的響應(yīng)。實(shí)際開發(fā)中這個(gè)部分是最重要的,服務(wù)器的響應(yīng)是整個(gè)網(wǎng)絡(luò)編程最核心的部分之一。

獲取到客戶端的ip和端口號(這兩個(gè)信息本身就在requestpacket中)

通過send方法把responsePacket方法里面的信息傳出去。

主要的工作流程:

1.讀取請求并解析

2.根據(jù)請求計(jì)算相應(yīng)

3.構(gòu)造響應(yīng)并且寫回客戶端

一次通信,需要有兩個(gè)ip,兩個(gè)端口,客戶端的ip是,客戶端的端口是系統(tǒng)自動分配的,服務(wù)器ip和端口需要告訴客戶端,才能順利把消息發(fā)給服務(wù)器。

先來看完整代碼:

通過socket,IP和端口我們才能和服務(wù)器端連接起來。

在客戶端中,需要用戶自己輸入,獲取到用戶的request后,需要打包成requestPacket然后通過發(fā)送給服務(wù)器。

完成上一步后,等待服務(wù)器的響應(yīng),到客戶端這邊用receive接收,類型為responsePacket。最后再轉(zhuǎn)換成String類型的response打印出來。

客戶端發(fā)送給服務(wù)器后,就進(jìn)入阻塞等待,這里的receive能阻塞,是因?yàn)椴僮飨到y(tǒng)原生提供的API就是阻塞的函數(shù),這里的阻塞不是Java實(shí)現(xiàn)的,而是系統(tǒng)內(nèi)核里實(shí)現(xiàn)的。

同時(shí)最后的main函數(shù)中,應(yīng)該指定好ip和端口號,以便客戶端能訪問到服務(wù)器端。

同時(shí)也可以打開這個(gè)選項(xiàng),同時(shí)開啟多個(gè)客戶端,共用一個(gè)服務(wù)器。

針對上述的程序,來看看端口沖突是什么效果,一個(gè)端口只能被一個(gè)進(jìn)程使用,如果有多個(gè)就不行。

TCP和UDP的差別還是有不少的,比如一個(gè)有連接一個(gè)無連接,一個(gè)是可以直接發(fā)送,一個(gè)需要數(shù)據(jù)報(bào)打包發(fā)送。

ServerSocket是創(chuàng)建TCP服務(wù)端Socket的API。

構(gòu)造方法:

方法:

Socket是客戶端Socket,或服務(wù)端中接收到客戶端建立連接(accept方法)的請求后,返回的服務(wù)端Socket。不管是客戶端還是服務(wù)端Socket,都是雙方建立連接以后,保存的對端信息,及用來與對方收發(fā)數(shù)據(jù)的。

構(gòu)造方法:

方法:

在這里有serverSocket和clientSocket,這兩個(gè)socket是不同的,serverSocket接收端口號和Ip地址,然后通過clientSocket和客戶端連接。因?yàn)樾枰B接上后才能發(fā)送消息,所以每用到一個(gè)clientSocket就會有一個(gè)客戶端連接上來,都會返回/創(chuàng)建一個(gè)Socket對象,Socket就是文件,每次創(chuàng)建一個(gè)clientSocket對象,就要占用一個(gè)文件描述符表的位置。

因此這里的socket需要釋放。前面的socket都沒有釋放,一方面這些socket生面周期更長,另一方面這些socket也不多。但是此處的clientSocket數(shù)量多,每個(gè)客戶端都有一個(gè),生命周期也更短。

accept如果沒有連接到客戶端,就會一直阻塞。

要注意,TCPserver一次性只能處理一個(gè)客戶端

通過clientSocket進(jìn)行processConnection進(jìn)行了具體的連接以后,通過trywithresources來完成InputStream和outputStream來完成字節(jié)流的傳輸。

通過InputStream接收到服務(wù)器端的數(shù)據(jù)后,再通過scanner寫入到request,request傳入到process方法返回服務(wù)器相應(yīng)的數(shù)據(jù)。接下來應(yīng)該用outputStream來寫入服務(wù)器返回的數(shù)據(jù),但是outputStream中并沒有writeString這樣的功能,所以此處用println來寫入。

并且println中會在發(fā)送的數(shù)據(jù)后面自動帶上\n換行,TCP協(xié)議是面向字節(jié)流的協(xié)議,但是接收方如何知道這一次一共需要讀多少字節(jié)呢?這就需要我們再數(shù)據(jù)傳輸中進(jìn)行明確的規(guī)定:

此處代碼中,隱式約定使用了\n來作為當(dāng)前代碼的請求、相應(yīng)分割約定。

所以這里的println也可以當(dāng)做是服務(wù)器發(fā)送給客戶端的發(fā)送行為。

通過socket來接收服務(wù)器的ip和端口號。

和服務(wù)器不同的是,客戶端方需要讀取用戶自己輸入的數(shù)據(jù),所以通過來接收用戶輸入的,但是最終是需要用到流式傳輸中,所以需要用trywithresources來包含InputStream和outputStream。

通過request接收到用戶輸入的數(shù)據(jù)后,用PrintWriter來寫入,再通過println來發(fā)送。并且以防萬一,我們用flush來刷新緩沖區(qū)避免數(shù)據(jù)傳輸失敗。

等待服務(wù)器返回消息后,用responseScanner來接收InputStream傳輸?shù)臄?shù)據(jù),再打印出來。

當(dāng)前咱們的服務(wù)器同一時(shí)刻只能給一個(gè)客戶端提供服務(wù),這是不科學(xué)的。當(dāng)前啟動服務(wù)器后,先后啟動兩個(gè)客戶端,客戶端1可以看到正常的上線提示,但是客戶端2沒有任何提醒。當(dāng)結(jié)束客戶端1后,客戶端2馬上顯示上線。

當(dāng)客戶端連接上服務(wù)器之后,代碼執(zhí)行到processConnection這個(gè)方法中的while循環(huán)了,此時(shí)意味著,只要這個(gè)循環(huán)不結(jié)束,processConnection方法就結(jié)束不了。進(jìn)一步的也就無法調(diào)用到第二次的accept。

解決辦法就是:使用多線程

其實(shí)修改的部分很小,只要在啟動連接的時(shí)候,作為一個(gè)單獨(dú)的線程啟動就大功告成。

但是呢,這里的多線程版本的程序,最大的問題就是可能會涉及到頻繁申請釋放線程,當(dāng)客戶端數(shù)量足夠多,也會造成很大的資源消耗。

所以解決辦法就是:使用線程池

通過線程池的方法,就能進(jìn)一步減少消耗。

但是呢,如果客戶端都在響應(yīng),就算使用了線程池了但是還是不夠,而且如果客戶端非常多,客戶端連接遲遲不斷開,就會導(dǎo)致機(jī)器上有很多線程。

解決辦法就是:IO多路復(fù)用,IO多路轉(zhuǎn)接

給這個(gè)線程安排一個(gè)集合,這個(gè)集合就放了一堆連接。這個(gè)線程就來負(fù)責(zé)監(jiān)聽這個(gè)集合,哪個(gè)連接有數(shù)據(jù)來了,線程就處理哪個(gè)連接。這其實(shí)就是因?yàn)?,雖然連接有很多很多,但是這些連接的請求并非完全嚴(yán)格的同時(shí),總還是有先后的。

TCP發(fā)送數(shù)據(jù)時(shí),需要先建立連接,什么時(shí)候關(guān)閉連接就決定是短連接還是長連接:

短連接:每次接收到數(shù)據(jù)并返回響應(yīng)后,都關(guān)閉連接,即是短連接。也就是說,短連接只能一次收發(fā)數(shù)據(jù)。

長連接:不關(guān)閉連接,一直保持連接狀態(tài),雙方不停的收發(fā)數(shù)據(jù),即是長連接。也就是說,長連接可以多次收發(fā)數(shù)據(jù)。

對比以上長短連接,兩者區(qū)別如下:

建立連接、關(guān)閉連接的耗時(shí):短連接每次請求、響應(yīng)都需要建立連接,關(guān)閉連接;而長連接只需要第一次建立連接,之后的請求、響應(yīng)都可以直接傳輸。相對來說建立連接,關(guān)閉連接也是要耗時(shí)的,長連接效率更高。

主動發(fā)送請求不同:短連接一般是客戶端主動向服務(wù)端發(fā)送請求;而長連接可以是客戶端主動發(fā)送請求,也可以是服務(wù)端主動發(fā)。

兩者的使用場景有不同:短連接適用于客戶端請求頻率不高的場景,如瀏覽網(wǎng)頁等。長連接適用于客戶端與服務(wù)端通信頻繁的場景,如聊天室,實(shí)時(shí)游戲等。

套接字編程的總結(jié)第13篇發(fā)送信息:應(yīng)用層-》傳輸層-》網(wǎng)絡(luò)層-》鏈路層-》物理層

接受信息:物理層-》鏈路層-》網(wǎng)絡(luò)層-》傳輸層-》應(yīng)用層

如下圖,假設(shè)你要使用扣扣發(fā)送一個(gè)hello;

注意:每一層都會記錄上層所使用的協(xié)議

套接字編程的總結(jié)第14篇對于UDP協(xié)議來說,具有無連接,面向數(shù)據(jù)報(bào)的特征,即每次都是沒有建立連接,并且一次發(fā)送全部數(shù)據(jù)報(bào),一次接收全部的數(shù)據(jù)報(bào)。

java中使用UDP協(xié)議通信,主要基于DatagramSocket類來創(chuàng)建數(shù)據(jù)報(bào)套接字,并使用DatagramPacket作為發(fā)送或接收的UDP數(shù)據(jù)報(bào)。對于一次發(fā)送及接收UDP數(shù)據(jù)報(bào)的流程如下:

DatagramSocket是UDPSocket,用于發(fā)送和接收UDP數(shù)據(jù)報(bào)。

注意:

UDP服務(wù)器(Server):采用一個(gè)固定端口,方便客戶端(Client)進(jìn)行通信;使用DatagramSocket(intport),就可以綁定到本機(jī)指定的端口,此方法可能有錯(cuò)誤風(fēng)險(xiǎn),提示該端口已經(jīng)被其他進(jìn)程占用。

UDP客戶端(Client):不需要采用固定端口(也可以用固定端口),采用隨機(jī)端口;使用DatagramSocket(),綁定到本機(jī)任意一個(gè)隨機(jī)端口

注意:

一旦通信雙方邏輯意義上有了通信線路,雙方地位就平等了(誰都可以作為發(fā)送方和接收方)

發(fā)送方調(diào)用的就是send()方法,接收方調(diào)用的就是receive()方法

通信結(jié)束后,雙方都應(yīng)該調(diào)用close()方法進(jìn)行資源回收

DatagramPacket是UDPSocket發(fā)送和接收的數(shù)據(jù)報(bào)。

這個(gè)類就是定義的報(bào)文包:通信過程中的數(shù)據(jù)抽象

可以理解為:發(fā)送/接受的一個(gè)信封(五元組+信件)

注意:

注意:

InetSocketAddress(SocketAddress的子類)構(gòu)造方法:

以下僅展示部分代碼,完整代碼可以看博主的gitee倉庫:

UDP客戶端:

UDP服務(wù)端:

自定義的日志類(記得導(dǎo)入此類):

套接字編程的總結(jié)第15篇

服務(wù)端

客戶端

結(jié)果:注意:1、因?yàn)門CP是有連接通信,所以我們要用多線程的方式來接收客戶端的請求,否則一個(gè)服務(wù)端只能鏈接客戶端,當(dāng)我們用多線程的方式來接待客戶端,此時(shí)一個(gè)服務(wù)端就可以調(diào)用多個(gè)線程來對應(yīng)的響應(yīng)多個(gè)客戶端;2、為什么UDP版本不需要使用多線程?因?yàn)閁DP是無連接通信,客戶端不需要服務(wù)端確認(rèn)接收也可以發(fā)送,簡言之就是UDP沒有TCP依賴性強(qiáng);UDP就像是發(fā)信息,直接發(fā)送內(nèi)容即可,不需要確認(rèn)對方是否正在盯著手機(jī)看。但是TCP就像是打電話,我們撥電話之后,要等到對方接電話才可以說談話內(nèi)容,如果對方不接電話,我們就沒法進(jìn)行溝通。在TCP版本的情況下運(yùn)用多線程,就是我們撥電話之后,對方派A和我們溝通,又來一個(gè)電話,對方派B來溝通。

套接字編程的總結(jié)第16篇源文件中定義的函數(shù)connectsock分配套接字和連接該套接字,該函數(shù)通常作為庫例程被其他程序調(diào)用(如和等)。

庫例程:例程的作用類似于函數(shù),但含義更為豐富一些。例程是某個(gè)系統(tǒng)對外提供的功能接口或服務(wù)的集合。故一個(gè)庫例程可以理解為一個(gè)庫函數(shù),可以方便地被別的程序調(diào)用的庫函數(shù)。

IPv4的sockaddr_in結(jié)構(gòu)體:指明了端點(diǎn)地址,其包括用來識別地址類型的2字節(jié)字段(必須為AF_INET),還有一個(gè)2字節(jié)的端口號字段,一個(gè)4字節(jié)的具體IP地址的字段,還有一個(gè)未使用的8字節(jié)字段。

IPv6的sockaddr_in6結(jié)構(gòu)體

family參數(shù)根據(jù)協(xié)議族的不同,選擇AF_INET或AF_INET6。

通用套接口地址結(jié)構(gòu)

**新的通用套接口地址結(jié)構(gòu):**兼容IPv6地址

考試中的庫例程程序相當(dāng)于把復(fù)習(xí)資料里的示例程序更加抽象化了。

包含在中,主要在網(wǎng)絡(luò)編程解析時(shí)使用。

getaddrinfo(constchar*restrictnodename,constchar*restrictservname,conststructaddrinfo*restricthints,structaddrinfo**restrictres)

返回值0表示成功,非0表示錯(cuò)誤。

該方法是在IPv6中引入,協(xié)議無關(guān),即可用于IPv4也可用于IPv6。getaddrinfo()函數(shù)可以處理名稱到地址以及服務(wù)到端口的這兩種轉(zhuǎn)換,返回的是一個(gè)structaddrinfo的結(jié)構(gòu)體指針而不是一個(gè)地址清單。

nodename參數(shù):主機(jī)名,或者是點(diǎn)分十進(jìn)制地址串(IPv4),或16進(jìn)制串(IPv6)

servname參數(shù):服務(wù)名,可以是端口號,也可以是已定義的服務(wù)名稱如“https”等

hints參數(shù):指向用戶指定的structaddrinfo結(jié)構(gòu)體,只能設(shè)定其中ai_family,ai_socktype,ai_protocol和ai_flags四個(gè)域,其他域必須為0或者是NULL。其中:

ai_family:指定返回地址的協(xié)議簇,AF_INET(IPv4),AF_INET6(IPv6),AF_UNSPEC(IPv4andIPv6)

ai_socktype:用于設(shè)定返回地址的socket類型,常用有SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,設(shè)置為0表示所有類型等

ai_protocol:指定協(xié)議,常用有IPPROTO_TCP、IPPROTO_UDP等,設(shè)置為0表示所有協(xié)議

ai_flags:附加選項(xiàng),AI_PASSIVE,AI_CANONNAME等(不考故不贅述

res參數(shù):獲取一個(gè)指向存儲結(jié)果的structaddrinfo結(jié)構(gòu)體,使用完成后調(diào)用freeaddrinfo()釋放存儲結(jié)果空間

釋放addrinfo結(jié)構(gòu)體動態(tài)內(nèi)存;

freeaddrinfo(structaddrinfo*ai)

socket編程需要基于一個(gè)文件描述符,即socket文件描述符。socket系統(tǒng)調(diào)用就是用來創(chuàng)建socket文件描述符。成功返回0,失敗返回-1并設(shè)置errno值。

intsocket(intdomain,inttype,intprotocol);

創(chuàng)建主動套接字的一方(客戶端)調(diào)用connect()系統(tǒng)調(diào)用,可建立與被動套接字的一方(服務(wù)端)的連接。成功返回0,失敗返回-1并設(shè)置errno值。

intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen)

**sockfd參數(shù):**連接文件描述符

addr參數(shù):指定協(xié)議的地址結(jié)構(gòu)體指針

addrlen參數(shù):協(xié)議地址結(jié)構(gòu)體長度

close()一個(gè)TCP套接字的默認(rèn)行為是把該套接字標(biāo)記為關(guān)閉,此后不能再對該文件描述符進(jìn)行讀寫操作。TCP協(xié)議將嘗試發(fā)送已排隊(duì)等待發(fā)送到對端的任何數(shù)據(jù),發(fā)送完畢后發(fā)生的是正常的TCP連接終止序列。成功返回0,失敗返回-1并設(shè)置errno值。

intclose(intfd)

char*gai_strerror(interror)

getaddrinfo出錯(cuò)時(shí)返回非零值,gai_strerror根據(jù)返回的非零值返回指向?qū)?yīng)的出錯(cuò)信息字符串的指針。

char*strerror(interrnum)

從內(nèi)部數(shù)組中搜索錯(cuò)誤號errnum,并返回一個(gè)指向錯(cuò)誤消息字符串的指針。

注:本篇博客的所有程序都省略了頭文件引用

注意:return函數(shù)有沒有括號“()”都是正確的,為了簡明,一般省略不寫

套接字編程的總結(jié)第17篇網(wǎng)絡(luò)上的主機(jī)通過不同的進(jìn)程,以編程的方式實(shí)現(xiàn)網(wǎng)絡(luò)通信,我們稱之為網(wǎng)絡(luò)編程。我們只要滿足不同的進(jìn)程即可,所以即便是同一個(gè)主機(jī),只要是不同的進(jìn)程,基于網(wǎng)絡(luò)傳輸數(shù)據(jù),也是屬于網(wǎng)絡(luò)編程

在一次網(wǎng)絡(luò)數(shù)據(jù)傳輸時(shí),有發(fā)送端、接收端、收發(fā)端三方注意:發(fā)送端和接收端只是相對的,只是一次網(wǎng)絡(luò)數(shù)據(jù)傳輸產(chǎn)生數(shù)據(jù)流向后的概念。

一般來說,獲取一個(gè)網(wǎng)絡(luò)資源,涉及到兩次網(wǎng)絡(luò)數(shù)據(jù)傳輸:第一次:請求數(shù)據(jù)的發(fā)送;第二次:響應(yīng)數(shù)據(jù)的發(fā)送;

服務(wù)端:提供服務(wù)的一方進(jìn)程,成為服務(wù)端,可以提供對外服務(wù);客戶端:獲取服務(wù)的一方進(jìn)程,成為客戶端;對于服務(wù)來說,一般提供1、客戶端獲取服務(wù)資源。2、客戶端保存資源在服務(wù)端

套接字編程的總結(jié)第18篇源文件包含針對TIME服務(wù)的簡單UDP客戶程序。本題程序調(diào)用了自定義例程庫函數(shù)connectsock分配套接口和連接該套接口。

size_twrite(intflides,constvoid*buf,size_tnbytes)

write系統(tǒng)調(diào)用,將緩存區(qū)buf中的前nbytes字節(jié)寫入到與文件描述符flides有關(guān)的文件中,write系統(tǒng)調(diào)用返回的是實(shí)際寫入到文件中的字節(jié)數(shù)。

uint32_tntohl(uint32_tnetlong);

ntohl()將一個(gè)無符號長整形數(shù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為主機(jī)字節(jié)順序,返回一個(gè)以主機(jī)字節(jié)順序表達(dá)的數(shù)。

structtm*localtime(consttime_t*timer)

把從1970-1-1零點(diǎn)零分到當(dāng)前時(shí)間系統(tǒng)所偏移的秒數(shù)時(shí)間轉(zhuǎn)換為本地時(shí)間。

timer是指向表示日歷時(shí)間的time_t值的指針,timer的值被分解為tm結(jié)構(gòu),并用本地時(shí)區(qū)表示。

char*asctime(conststructtm*timeptr)

把timeptr指向的tm結(jié)構(gòu)體中儲存的時(shí)間轉(zhuǎn)換為字符串。

套接字編程的總結(jié)第19篇源文件包含針對DAYTIME服務(wù)的簡單TCP客戶程序。本題程序調(diào)用了自定義例程庫函數(shù)connectsock分配套接口和連接該套接口。

ssize_tread[1](intfd,void*buf,size_tcount);

read()會把參數(shù)fd所指的文件傳送count個(gè)字節(jié)到buf指針?biāo)傅膬?nèi)存中。若參數(shù)count為0,則read()不會有作用并返回0。返回值為實(shí)際讀取到的字節(jié)數(shù),如果返回0,表示已到達(dá)文件尾或是無可讀取的數(shù)據(jù),此外文件讀寫位置會隨讀取到的字節(jié)移動。

套接字編程的總結(jié)第20篇Socket套接字,是由系統(tǒng)提供用于網(wǎng)絡(luò)通信的技術(shù),是基于TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元?;赟ocket套接字的網(wǎng)絡(luò)程序開發(fā)就是網(wǎng)絡(luò)編程。

Socket是站在應(yīng)用層,做網(wǎng)絡(luò)編程很重要的一個(gè)概念

傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層、物理層都是通過OS+硬件來提供服務(wù)的,而應(yīng)用層要享受OS提供的網(wǎng)絡(luò)服務(wù),需要通過OS提供的服務(wù)窗口(Socket)來享受服務(wù)。

拓展:

OS原生的提供的系統(tǒng)調(diào)用(Linux上的網(wǎng)絡(luò)編程):

套接字編程的總結(jié)第21篇字節(jié)序:cpu在內(nèi)存中對數(shù)據(jù)存儲的順序,并且主機(jī)字節(jié)序分別為大端字節(jié)序和小端字節(jié)序,

小端字節(jié)序:低地址存低位

eg:X86架構(gòu)

大端字節(jié)序:低地址存高位

eg:MIPS架構(gòu)

在網(wǎng)絡(luò)通信中并不知道對端主機(jī)是大端還是小端因此兩個(gè)不同主機(jī)字節(jié)的主機(jī)在進(jìn)行通信時(shí)會造成數(shù)據(jù)二義,字節(jié)序?qū)W(wǎng)絡(luò)通信造成影響主要針對存儲大于一個(gè)字節(jié)的類型(int16_t,int32_t,short,int,long,float,double);

為了避免因?yàn)橹鳈C(jī)字節(jié)序不同導(dǎo)致的數(shù)據(jù)二義性,因此規(guī)定網(wǎng)絡(luò)通信使用統(tǒng)一字節(jié)標(biāo)準(zhǔn),把這個(gè)統(tǒng)一的標(biāo)準(zhǔn)字節(jié)序稱為網(wǎng)絡(luò)字節(jié)序:大端字節(jié)序,所以在編寫網(wǎng)絡(luò)通信程序中,若是傳輸大于一個(gè)字節(jié)類型的數(shù)據(jù),就需要考慮字節(jié)序的轉(zhuǎn)換gong

注意:可使用聯(lián)合體使用大小端的判斷

字節(jié)序轉(zhuǎn)換接口:

uint32_thtonl(uint32_thostlong);//把32位主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序

uint16_thtons(uint16_thostshort);

uint32_tntohl(uint32_tnetlong);

uint16_tntohs(uint16_tnetshort);//把16位網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序

套接字編程的總結(jié)第22篇基本TCP客戶/服務(wù)器通信流程如下,主要為了大家能夠更好的理解

配有代碼詳解圖

TCP服務(wù)端代碼入下:

詳解:

單進(jìn)程版我們一般不用

詳解:

對于上面的所給的案例,多線程和線程池版本沒有再去仔細(xì)的講解,首先基本的通信過程都是一樣的,只要對進(jìn)程和線程相關(guān)的函數(shù)掌握以及相關(guān)知識點(diǎn)的概念掌握很熟練的話,理解起來很容易。

套接字編程的總結(jié)第23篇源文件包含ECHO服務(wù)器的代碼,它使用并發(fā)進(jìn)程為多個(gè)客戶提供并發(fā)服務(wù)。源文件包含ECHO服務(wù)器的代碼,它使用迭代的、無連接算法為多個(gè)客戶提供服務(wù)。本題程序調(diào)用了自定義例程庫函數(shù)passivesock分配和綁定服務(wù)器套接口。

pid_tfork(void)

返回值:若成功調(diào)用一次則返回兩個(gè)值,子進(jìn)程返回0,父進(jìn)程返回子進(jìn)程ID;否則,出錯(cuò)返回-1。

一個(gè)現(xiàn)有進(jìn)程可以調(diào)用fork函數(shù)創(chuàng)建一個(gè)新進(jìn)程。由fork創(chuàng)建的新進(jìn)程被稱為子進(jìn)程。fork函數(shù)被調(diào)用一次但返回兩次。兩次返回的唯一區(qū)別是子進(jìn)程中返回0值而父進(jìn)程中返回子進(jìn)程ID。子進(jìn)程是父進(jìn)程的副本,它將獲得父進(jìn)程數(shù)據(jù)空間、堆、棧等資源的副本,即存儲空間的“副本”,意味著父子進(jìn)程間不共享這些存儲空間。子進(jìn)程有了獨(dú)立的地址空間,故無法確定fork之后是子進(jìn)程先運(yùn)行還是父進(jìn)程先運(yùn)行,這依賴于系統(tǒng)的實(shí)現(xiàn)。

套接字編程的總結(jié)第24篇源文件包含針對ECHO服務(wù)的簡單TCP客戶程序。源文件包含針對ECHO服務(wù)的簡單UDP客戶程序。本題程序調(diào)用了自定義例程庫函數(shù)connectsock分配套接口和連接該套接口。

char*fgets(char*str,intn,FILE*stream)

從指定的流stream讀取一行,并把它存儲在str所指向的字符串內(nèi)。當(dāng)讀取(n-1)個(gè)字符時(shí),或者讀取到換行符時(shí),或者到達(dá)文件末尾時(shí),它會停止,具體視情況而定。

參數(shù):

intfputs(constchar*str,FILE*stream)

把字符串寫入到指定的流stream中,但不包括空字符。

參數(shù):

套接字編程的總結(jié)第25篇將一個(gè)客戶端的套接字關(guān)聯(lián)上一個(gè)地址沒有多少新意,可以讓系統(tǒng)選一個(gè)默認(rèn)的地址。然而,對于服務(wù)器,需要給一個(gè)接收客戶端請求的服務(wù)器套接字關(guān)聯(lián)上一個(gè)眾所周知的地址??蛻舳藨?yīng)有一種方法來發(fā)現(xiàn)連接服務(wù)器所需要的地址,最簡單的方法就是服務(wù)器保留一個(gè)地址并且注冊在/etc/services或者某個(gè)名字服務(wù)中。

使用bind函數(shù)來關(guān)聯(lián)地址和套接字。

對于使用的地址有以下一些限制。

對于因特網(wǎng)域,如果指定IP地址為INADDR_ANY(中定義的),套接字端點(diǎn)可以被綁定到所有的系統(tǒng)網(wǎng)絡(luò)接口上。這意味著可以接收這個(gè)系統(tǒng)所安裝的任何一個(gè)網(wǎng)卡的數(shù)據(jù)包。在下一節(jié)中可以看到,如果調(diào)用connect或listen,但沒有將地址綁定到套接字上,系統(tǒng)會選一個(gè)地址綁定到套接字上。

可以調(diào)用getsockname函數(shù)來發(fā)現(xiàn)綁定到套接字上的地址。

調(diào)用getsockname之前,將alenp設(shè)置為一個(gè)指向整數(shù)的指針,該整數(shù)指定緩沖區(qū)sockaddr的長度。返回時(shí),該整數(shù)會被設(shè)置成返回地址的大小。如果地址和提供的緩沖區(qū)長度不匹配,地址會被自動截?cái)喽粓?bào)錯(cuò)。如果當(dāng)前沒有地址綁定到該套接字,則其結(jié)果是未定義的。

如果套接字已經(jīng)和對等方連接,可以調(diào)用getpeername函數(shù)來找到對方的地址。

除了返回對等方的地址,函數(shù)getpeername和getsockname一樣。

如果要處理一個(gè)面向連接的網(wǎng)絡(luò)服務(wù)(SOCK_STREAM或SOCK_SEQPACKET),那么在開始交換數(shù)據(jù)以前,需要在請求服務(wù)的進(jìn)程套接字(客戶端)和提供服務(wù)的進(jìn)程套接字(服務(wù)器)之間建立一個(gè)連接。使用connect函數(shù)來建立連接。

在connect中指定的地址是我們想與之通信的服務(wù)器地址。如果sockfd沒有綁定到一個(gè)地址,connect會給調(diào)用者綁定一個(gè)默認(rèn)地址。

當(dāng)嘗試連接服務(wù)器時(shí),出于一些原因,連接可能會失敗。要想一個(gè)連接請求成功,要連接的計(jì)算機(jī)必須是開啟的,并且正在運(yùn)行,服務(wù)器必須綁定到一個(gè)想與之連接的地址上,并且服務(wù)器的等待連接隊(duì)列要有足夠的空間(后面會有更詳細(xì)的介紹)。因此,應(yīng)用程序必須能夠處理connect返回的錯(cuò)誤,這些錯(cuò)誤可能是由一些瞬時(shí)條件引起的。

如果套接字描述符處于非阻塞模式,那么在連接不能馬上建立時(shí),connect將會返回?1并且將errno設(shè)置為特殊的錯(cuò)誤碼EINPROGRESS。應(yīng)用程序可以使用poll或者select來判斷文件描述符何時(shí)可寫。如果可寫,連接完成。

connect函數(shù)還可以用于無連接的網(wǎng)絡(luò)服務(wù)(SOCK_DGRAM)。這看起來有點(diǎn)矛盾,實(shí)際上卻是一個(gè)不錯(cuò)的選擇。如果用SOCK_DGRAM套接字調(diào)用connect,傳送的報(bào)文的目標(biāo)地址會設(shè)置成connect調(diào)用中所指定的地址,這樣每次傳送報(bào)文時(shí)就不需要再提供地址。另外,僅能接收來自指定地址的報(bào)文。

服務(wù)器調(diào)用listen函數(shù)來宣告它愿意接受連接請求。

參數(shù)backlog提供了一個(gè)提示,提示系統(tǒng)該進(jìn)程所要入隊(duì)的未完成連接請求數(shù)量。其實(shí)際值由系統(tǒng)決定,但上限由中的SOMAXCONN指定。

一旦隊(duì)列滿,系統(tǒng)就會拒絕多余的連接請求,所以backlog的值應(yīng)該基于服務(wù)器期望負(fù)載和處理量來選擇,其中處理量是指接受連接請求與啟動服務(wù)的數(shù)量。

一旦服務(wù)器調(diào)用了listen,所用的套接字就能接收連接請求。使用accept函數(shù)獲得連接請求并建立連接。

函數(shù)accept所返回的文件描述符是套接字描述符,該描述符連接到調(diào)用connect的客戶端。這個(gè)新的套接字描述符和原始套接字(sockfd)具有相同的套接字類型和地址族。傳給accept的原始套接字沒有關(guān)聯(lián)到這個(gè)連接,而是繼續(xù)保持可用狀態(tài)并接收其他連接請求。

如果不關(guān)心客戶端標(biāo)識,可以將參數(shù)addr和len設(shè)為NULL。否則,在調(diào)用accept之前,將addr參數(shù)設(shè)為足夠大的緩沖區(qū)來存放地址,并且將len指向的整數(shù)設(shè)為這個(gè)緩沖區(qū)的字節(jié)大小。返回時(shí),accept會在緩沖區(qū)填充客戶端的地址,并且更新指向len的整數(shù)來反映該地址的大小。

如果沒有連接請求在等待,accept會阻塞直到一個(gè)請求到來。如果sockfd處于非阻塞模式,accept會返回?1,并將errno設(shè)置為EAGAIN或EWOULDBLOCK。

本文中討論的所有平臺都將EAGAIN定義為EWOULDBLOCK。

如果服務(wù)器調(diào)用accept,并且當(dāng)前沒有連接請求,服務(wù)器會阻塞直到一個(gè)請求到來。另外,服務(wù)器可以使用poll或select來等待一個(gè)請求的到來。在這種情況下,一個(gè)帶有等待連接請求的套接字會以可讀的方式出現(xiàn)。

盡管可以通過read和write交換數(shù)據(jù),但這就是這兩個(gè)函數(shù)所能做的一切。如果想指定選項(xiàng),從多個(gè)客戶端接收數(shù)據(jù)包,或者發(fā)送帶外數(shù)據(jù),就需要使用6個(gè)為數(shù)據(jù)傳遞而設(shè)計(jì)的套接字函數(shù)中的一個(gè)。

3個(gè)函數(shù)用來發(fā)送數(shù)據(jù),3個(gè)用于接收數(shù)據(jù)。首先,考查用于發(fā)送數(shù)據(jù)的函數(shù)。

最簡單的是send,它和write很像,但是可以指定標(biāo)志來改變處理傳輸數(shù)據(jù)的方式。

類似write,使用send時(shí)套接字必須已經(jīng)連接。參數(shù)buf和nbytes的含義與write中的一致。

然而,與write不同的是,send支持第4個(gè)參數(shù)flags。圖16-13總結(jié)了這些標(biāo)志。

即使send成功返回,也并不表示連接的另一端的進(jìn)程就一定接收了數(shù)據(jù)。我們所能保證的只是當(dāng)send成功返回時(shí),數(shù)據(jù)已經(jīng)被無錯(cuò)誤地發(fā)送到網(wǎng)絡(luò)驅(qū)動程序上。

對于支持報(bào)文邊界的協(xié)議,如果嘗試發(fā)送的單個(gè)報(bào)文的長度超過協(xié)議所支持的最大長度,那么send會失敗,并將errno設(shè)為EMSGSIZE。對于字節(jié)流協(xié)議,send會阻塞直到整個(gè)數(shù)據(jù)傳輸完成。函數(shù)sendto和send很類似。區(qū)別在于sendto可以在無連接的套接字上指定一個(gè)目標(biāo)地址。

對于面向連接的套接字,目標(biāo)地址是被忽略的,因?yàn)檫B接中隱含了目標(biāo)地址。對于無連接的套接字,除非先調(diào)用connect設(shè)置了目標(biāo)地址,否則不能使用send。sendto提供了發(fā)送報(bào)文的另一種方式。

通過套接字發(fā)送數(shù)據(jù)時(shí),還有一個(gè)選擇。可以調(diào)用帶有msghdr結(jié)構(gòu)的sendmsg來指定多重緩沖區(qū)傳輸數(shù)據(jù),這和writev函數(shù)很相似。

定義了msghdr結(jié)構(gòu),它至少有以下成員:

函數(shù)recv和read相似,但是recv可以指定標(biāo)志來控制如何接收數(shù)據(jù)。

當(dāng)指定MSG_PEEK標(biāo)志時(shí),可以查看下一個(gè)要讀取的數(shù)據(jù)但不真正取走它。當(dāng)再次調(diào)用read或其中一個(gè)recv函數(shù)時(shí),會返回剛才查看的數(shù)據(jù)。

對于SOCK_STREAM套接字,接收的數(shù)據(jù)可以比預(yù)期的少。MSG_WAITALL標(biāo)志會阻止這種行為,直到所請求的數(shù)據(jù)全部返回,recv函數(shù)才會返回。對于SOCK_DGRAM和SOCK_SEQPACKET套接字,MSG_WAITALL標(biāo)志沒有改變什么行為,因?yàn)檫@些基于報(bào)文的套接字類型一次讀取就返回整個(gè)報(bào)文。

如果發(fā)送者已經(jīng)調(diào)用shutdown來結(jié)束傳輸,或者網(wǎng)絡(luò)協(xié)議支持按默認(rèn)的順序關(guān)閉并且發(fā)送端已經(jīng)關(guān)閉,那么當(dāng)所有的數(shù)據(jù)接收完畢后,recv會返回0。

如果有興趣定位發(fā)送者,可以使用recvfrom來得到數(shù)據(jù)發(fā)送者的源地址。

如果addr非空,它將包含數(shù)據(jù)發(fā)送者的套接字端點(diǎn)地址。當(dāng)調(diào)用recvfrom時(shí),需要設(shè)置addrlen參數(shù)指向一個(gè)整數(shù),該整數(shù)包含addr所指向的套接字緩沖區(qū)的字節(jié)長度。返回時(shí),該整數(shù)設(shè)為該地址的實(shí)際字節(jié)長度。

因?yàn)榭梢垣@得發(fā)送者的地址,recvfrom通常用于無連接的套接字。否則,recvfrom等同于recv。

為了將接收到的數(shù)據(jù)送入多個(gè)緩沖區(qū),類似于readv,或者想接收輔助數(shù)據(jù),可以使用recvmsg。

recvmsg用msghdr結(jié)構(gòu)(在sendmsg中見到過)指定接收數(shù)據(jù)的輸入緩沖區(qū)。可以設(shè)置參數(shù)flags來改變r(jià)ecvmsg的默認(rèn)行為。返回時(shí),msghdr結(jié)構(gòu)中的msg_flags字段被設(shè)為所接收數(shù)據(jù)的各種特征。(進(jìn)入recvmsg時(shí)msg_flags被忽略。)recvmsg中返回的各種可能值總結(jié)在圖16-15中。

套接字機(jī)制提供了兩個(gè)套接字選項(xiàng)接口來控制套接字行為。一個(gè)接口用來設(shè)置選項(xiàng),另一個(gè)接口可以查詢選項(xiàng)的狀態(tài)??梢垣@取或設(shè)置以下3種選項(xiàng)。

可以使用setsockopt函數(shù)來設(shè)置套接字選項(xiàng)。

參數(shù)level標(biāo)識了選項(xiàng)應(yīng)用的協(xié)議。如果選項(xiàng)是通用的套接字層次選項(xiàng),則level設(shè)置成SOL_SOCKET。否則,level設(shè)置成控制這個(gè)選項(xiàng)的協(xié)議編號。對于TCP選項(xiàng),level是IPPROTO_TCP,對于IP,level是IPPROTO_IP。圖16-21總結(jié)了SingleUNIXSpecification中定義的通用套接字層次選項(xiàng)。

參數(shù)val根據(jù)選項(xiàng)的不同指向一個(gè)數(shù)據(jù)結(jié)構(gòu)或者一個(gè)整數(shù)。一些選項(xiàng)是on/off開關(guān)。如果整數(shù)非0,則啟用選項(xiàng)。如果整數(shù)為0,則禁止選項(xiàng)。參數(shù)len指定了val指向的對象的大小。

可以使用getsockopt函數(shù)來查看選項(xiàng)的當(dāng)前值。

參數(shù)lenp是一個(gè)指向整數(shù)的指針。在調(diào)用getsockopt之前,設(shè)置該整數(shù)為復(fù)制選項(xiàng)緩沖區(qū)的長度。如果選項(xiàng)的實(shí)際長度大于此值,則選項(xiàng)會被截?cái)?。如果?shí)際長度正好小于此值,那么返回時(shí)將此值更新為實(shí)際長度。

帶外數(shù)據(jù)(out-of-banddata)是一些通信協(xié)議所支持的可選功能,與普通數(shù)據(jù)相比,它允許更高優(yōu)先級的數(shù)據(jù)傳輸。帶外數(shù)據(jù)先行傳輸,即使傳輸隊(duì)列已經(jīng)有數(shù)據(jù)。TCP支持帶外數(shù)據(jù),但是UDP不支持。套接字接口對帶外數(shù)據(jù)的支持很大程度上受TCP帶外數(shù)據(jù)具體實(shí)現(xiàn)的影響。

TCP將帶外數(shù)據(jù)稱為緊急數(shù)據(jù)(urgentdata)。TCP僅支持一個(gè)字節(jié)的緊急數(shù)據(jù),但是允許緊急數(shù)據(jù)在普通數(shù)據(jù)傳遞機(jī)制數(shù)據(jù)流之外傳輸。為了產(chǎn)生緊急數(shù)據(jù),可以在3個(gè)send函數(shù)中的任何一個(gè)里指定MSG_OOB標(biāo)志。如果帶MSG_OOB標(biāo)志發(fā)送的字節(jié)數(shù)超過一個(gè)時(shí),最后一個(gè)字節(jié)將被視為緊急數(shù)據(jù)字節(jié)。

如果通過套接字安排了信號的產(chǎn)生,那么緊急數(shù)據(jù)被接收時(shí),會發(fā)送SIGURG信號。在節(jié)和節(jié)中可以看到,在fcntl中使用F_SETOWN命令來設(shè)置一個(gè)套接字的所有權(quán)。如果fcntl中的第三個(gè)參數(shù)為正值,那么它指定的就是進(jìn)程ID。如果為非-1的負(fù)值,那么它代表的就是進(jìn)程組ID。因此,可以通過調(diào)用以下函數(shù)安排進(jìn)程接收套接字的信號:

F_GETOWN命令可以用來獲得當(dāng)前套接字所有權(quán)。對于F_SETOWN命令,負(fù)值代表進(jìn)程組ID,正值代表進(jìn)程ID。因此,調(diào)用

將返回owner,如果owner為正值,則等于配置為接收套接字信號的進(jìn)程的ID。如果owner為負(fù)值,其絕對值為接收套接字信號的進(jìn)程組的ID。

TCP支持緊急標(biāo)記(urgentmark)的概念,即在普通數(shù)據(jù)流中緊急數(shù)據(jù)所在的位置。如果采用套接字選項(xiàng)SO_OOBINLINE,那么可以在普通數(shù)據(jù)中接收緊急數(shù)據(jù)。為幫助判斷是否已經(jīng)到達(dá)緊急標(biāo)記,可以使用函數(shù)sockatmark。

當(dāng)下一個(gè)要讀取的字節(jié)在緊急標(biāo)志處時(shí),sockatmark返回1。

當(dāng)帶外數(shù)據(jù)出現(xiàn)在套接字讀取隊(duì)列時(shí),select函數(shù)會返回一個(gè)文件描述符并且有一個(gè)待處理的異常條件??梢栽谄胀〝?shù)據(jù)流上接收緊急數(shù)據(jù),也可以在其中一個(gè)recv函數(shù)中采用MSG_OOB標(biāo)志在其他隊(duì)列數(shù)據(jù)之前接收緊急數(shù)據(jù)。TCP隊(duì)列僅用一個(gè)字節(jié)的緊急數(shù)據(jù)。如果在接收當(dāng)前的緊急數(shù)據(jù)字節(jié)之前又有新的緊急數(shù)據(jù)到來,那么已有的字節(jié)會被丟棄。

通常,recv函數(shù)沒有數(shù)據(jù)可用時(shí)會阻塞等待。同樣地,當(dāng)套接字輸出隊(duì)列沒有足夠空間來發(fā)送消息時(shí),send函數(shù)會阻塞。在套接字非阻塞模式下,行為會改變。在這種情況下,這些函數(shù)不會阻塞而是會失敗,將errno設(shè)置為EWOULDBLOCK或者EAGAIN。當(dāng)這種情況發(fā)生時(shí),可以使用poll或select來判斷能否接收或者傳輸數(shù)據(jù)。

在基于套接字的異步I/O中,當(dāng)從套接字中讀取數(shù)據(jù)時(shí),或者當(dāng)套接字寫隊(duì)列中空間變得可用時(shí),可以安排要發(fā)送的信號SIGIO。啟用異步I/O是一個(gè)兩步驟的過程。

可以使用3種方式來完成第一個(gè)步驟。

要完成第二個(gè)步驟,有兩個(gè)選擇。

寫一個(gè)程序判斷所使用系統(tǒng)的字節(jié)序。

寫一個(gè)程序,在至少兩種不同的平臺上打印出所支持套接字的stat結(jié)構(gòu)成員,并且描述這些結(jié)果的不同之處。

圖16-17的程序只在一個(gè)端點(diǎn)上提供了服務(wù)。修改這個(gè)程序,同時(shí)支持多個(gè)端點(diǎn)(每個(gè)端點(diǎn)具有一個(gè)不同的地址)上的服務(wù)。

寫一個(gè)客戶端程序和服務(wù)端程序,返回指定主機(jī)上當(dāng)前運(yùn)行的進(jìn)程數(shù)量。

在圖16-18的程序中,服務(wù)器等待子進(jìn)程執(zhí)行uptime,子進(jìn)程完成后退出,服務(wù)器才接受下一個(gè)連接請求。重新設(shè)計(jì)服務(wù)器,使得處理一個(gè)請求時(shí)并不拖延處理到來的連接請求。

寫兩個(gè)庫例程:一個(gè)在套接字上允許異步I/O,一個(gè)在套接字上不允許異步I/O。使用圖16-23來保證函數(shù)能夠在所有平臺上運(yùn)行,并且支持盡可能多的套接字類型。

隨書練習(xí)源碼地址

套接字編程的總結(jié)第26篇sockaddr結(jié)構(gòu)的出現(xiàn)

套接字不僅支持跨網(wǎng)絡(luò)的進(jìn)程間通信,還支持本地的進(jìn)程間通信(域間套接字)。在進(jìn)行跨網(wǎng)絡(luò)通信時(shí)我們需要傳遞的端口號和IP地址,而本地通信則不需要,因此套接字提供了sockaddr_in結(jié)構(gòu)體和sockaddr_un結(jié)構(gòu)體,其中sockaddr_in結(jié)構(gòu)體是用于跨網(wǎng)絡(luò)通信的,而sockaddr_un結(jié)構(gòu)體是用于本地通信的。

為了讓套接字的網(wǎng)絡(luò)通信和本地通信能夠使用同一套函數(shù)接口,于是就出現(xiàn)了sockeaddr結(jié)構(gòu)體,該結(jié)構(gòu)體與sockaddr_in和sockaddr_un的結(jié)構(gòu)都不相同,但這三個(gè)結(jié)構(gòu)體頭部的16個(gè)比特位都是一樣的,這個(gè)字段叫做協(xié)議家族。

此時(shí)當(dāng)我們在傳遞在傳參時(shí),就不用傳入sockeaddr_in或sockeaddr_un這樣的結(jié)構(gòu)體,而統(tǒng)一傳入sockeaddr這樣的結(jié)構(gòu)體。在設(shè)置參數(shù)時(shí)就可以通過設(shè)置協(xié)議家族這個(gè)字段,來表明我們是要進(jìn)行網(wǎng)絡(luò)通信還是本地通信,在這些API內(nèi)部就可以提取sockeaddr結(jié)構(gòu)頭部的16位進(jìn)行識別,進(jìn)而得出我們是要進(jìn)行網(wǎng)絡(luò)通信還是本地通信,然后執(zhí)行對應(yīng)的操作。此時(shí)我們就通過通用sockaddr結(jié)構(gòu),將套接字網(wǎng)絡(luò)通信和本地通信的參數(shù)類型進(jìn)行了統(tǒng)一。

套接字編程的總結(jié)第27篇網(wǎng)絡(luò)協(xié)議指定了字節(jié)序,因此異構(gòu)計(jì)算機(jī)系統(tǒng)能夠交換協(xié)議信息而不會被字節(jié)序所混淆。TCP/IP協(xié)議棧使用大端字節(jié)序。應(yīng)用程序交換格式化數(shù)據(jù)時(shí),字節(jié)序問題就會出現(xiàn)。對于TCP/IP,地址用網(wǎng)絡(luò)字節(jié)序來表示,所以應(yīng)用程序有時(shí)需要在處理器的字節(jié)序與網(wǎng)絡(luò)字節(jié)序之間轉(zhuǎn)換它們。例如,以一種易讀的形式打印一個(gè)地址時(shí),這種轉(zhuǎn)換很常見。

對于TCP/IP應(yīng)用程序,有4個(gè)用來在處理器字節(jié)序和網(wǎng)絡(luò)字節(jié)序之間實(shí)施轉(zhuǎn)換的函數(shù)。

h表示“主機(jī)”字節(jié)序,n表示“網(wǎng)絡(luò)”字節(jié)序。l表示“長”(即4字節(jié))整數(shù),s表示“短”(即4字節(jié))整數(shù)。雖然在使用這些函數(shù)時(shí)包含的是頭文件,但系統(tǒng)實(shí)現(xiàn)經(jīng)常是在其他頭文件中聲明這些函數(shù)的,只是這些頭文件都包含在中。對于系統(tǒng)來說,把這些函數(shù)實(shí)現(xiàn)為宏也是很常見的。

一個(gè)地址標(biāo)識一個(gè)特定通信域的套接字端點(diǎn),地址格式與這個(gè)特定的通信域相關(guān)。為使不同格式地址能夠傳入到套接字函數(shù),地址會被強(qiáng)制轉(zhuǎn)換成一個(gè)通用的地址結(jié)構(gòu)sockaddr:

因特網(wǎng)地址定義在頭文件中。在IPv4因特網(wǎng)域(AF_INET)中,套接字地址用結(jié)構(gòu)sockaddr_in表示:

數(shù)據(jù)類型in_port_t定義成uint16_t。數(shù)據(jù)類型in_addr_t定義成uint32_t。這些整數(shù)類型在中定義并指定了相應(yīng)的位數(shù)。

與AF_INET域相比較,IPv6因特網(wǎng)域(AF_INET6)套接字地址用結(jié)構(gòu)sockaddr_in6表示:

注意,盡管sockaddr_in與sockaddr_in6結(jié)構(gòu)相差比較大,但它們均被強(qiáng)制轉(zhuǎn)換成sockaddr結(jié)構(gòu)輸入到套接字例程中。

有時(shí),需要打印出能被人理解而不是計(jì)算機(jī)所理解的地址格式。BSD網(wǎng)絡(luò)軟件包含函數(shù)inet_addr和inet_ntoa,用于二進(jìn)制地址格式與點(diǎn)分十進(jìn)制字符表示()之間的相互轉(zhuǎn)換。但是這些函數(shù)僅適用于IPv4地址。有兩個(gè)新函數(shù)inet_ntop和inet_pton具有相似的功能,而且同時(shí)支持IPv4地址和IPv6地址。

函數(shù)inet_ntop將網(wǎng)絡(luò)字節(jié)序的二進(jìn)制地址轉(zhuǎn)換成文本字符串格式。inet_pton將文本字符串格式轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序的二進(jìn)制地址。參數(shù)domain僅支持兩個(gè)值:AF_INET和AF_INET6。

對于inet_ntop,參數(shù)size指定了保存文本字符串的緩沖區(qū)(str)的大小。兩個(gè)常數(shù)用于簡化工作:INET_ADDRSTRLEN定義了足夠大的空間來存放一個(gè)表示IPv4地址的文本字符串;INET6_ADDRSTRLEN定義了足夠大的空間來存放一個(gè)表示IPv6地址的文本字符串。對于inet_pton,如果domain是AF_INET,則緩沖區(qū)addr需要足夠大的空間來存放一個(gè)32位地址,如果domain是AF_INET6,則需要足夠大的空間來存放一個(gè)128位地址。

歷史上,BSD網(wǎng)絡(luò)軟件提供了訪問各種網(wǎng)絡(luò)配置信息的接口。這些函數(shù)返回的網(wǎng)絡(luò)配置信息被存放在許多地方。這個(gè)信息可以存放在靜態(tài)文件(如/etc/hosts和/etc/services)中,也可以由名字服務(wù)管理,如域名系統(tǒng)(DomainNameSystem,DNS)或者網(wǎng)絡(luò)信息服務(wù)(NetworkInformationService,NIS)。無論這個(gè)信息放在何處,都可以用同樣的函數(shù)訪問它。

通過調(diào)用gethostent,可以找到給定計(jì)算機(jī)系統(tǒng)的主機(jī)信息。

如果主機(jī)數(shù)據(jù)庫文件沒有打開,gethostent會打開它。函數(shù)gethostent返回文件中的下一個(gè)條目。函數(shù)sethostent會打開文件,如果文件已經(jīng)被打開,那么將其回繞。當(dāng)stayopen參數(shù)設(shè)置成非0值時(shí),調(diào)用gethostent之后,文件將依然是打開的。函數(shù)endhostent可以關(guān)閉文件。

當(dāng)gethostent返回時(shí),會得到一個(gè)指向hostent結(jié)構(gòu)的指針,該結(jié)構(gòu)可能包含一個(gè)靜態(tài)的數(shù)據(jù)緩沖區(qū),每次調(diào)用gethostent,緩沖區(qū)都會被覆蓋。hostent結(jié)構(gòu)至少包含以下成員:

返回的地址采用網(wǎng)絡(luò)字節(jié)序。

能夠采用一套相似的接口來獲得網(wǎng)絡(luò)名字和網(wǎng)絡(luò)編號。

netent結(jié)構(gòu)至少包含以下字段:

網(wǎng)絡(luò)編號按照網(wǎng)絡(luò)字節(jié)序返回。地址類型是地址族常量之一(如AF_INET)。

我們可以用以下函數(shù)在協(xié)議名字和協(xié)議編號之間進(jìn)行映射。

定義的protoent結(jié)構(gòu)至少包含以下成員:

套接字編程的總結(jié)第28篇IP協(xié)議規(guī)定網(wǎng)絡(luò)上

溫馨提示

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

最新文檔

評論

0/150

提交評論