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

下載本文檔

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

文檔簡(jiǎn)介

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

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

服務(wù)器使用的TCPSocket對(duì)象(傳入的端口,就是要公開(kāi)的端口,一般稱(chēng)為監(jiān)聽(tīng)(listen)端口)

注意:

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

Socket對(duì)象:建立起的連接

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

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

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

注意:

注意:

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

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

擴(kuò)展了解:

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

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

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

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

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

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

所以可以使用多線程解決長(zhǎng)連接客戶(hù)端不支持同時(shí)在線的問(wèn)題:

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

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

TCP服務(wù)端:

TCP客戶(hù)端:

套接字編程的總結(jié)第2篇fd為要寫(xiě)入的文件的描述符,buf為要寫(xiě)入的數(shù)據(jù)的緩沖區(qū)地址,nbytes為要寫(xiě)入的數(shù)據(jù)的字節(jié)數(shù)。write()函數(shù)會(huì)將緩沖區(qū)buf中的nbytes個(gè)字節(jié)寫(xiě)入文件fd,成功則返回寫(xiě)入的字節(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ò)通信的基本操作單元?;赟ocket套接字的網(wǎng)絡(luò)程序開(kāi)發(fā)就是網(wǎng)絡(luò)編程。

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

2、數(shù)據(jù)報(bào)套接字:使用傳輸層UDP協(xié)議UDP的特點(diǎn):無(wú)連接、不可靠傳輸、面向數(shù)據(jù)報(bào),全雙工對(duì)于數(shù)據(jù)報(bào)來(lái)說(shuō),可以簡(jiǎn)單的理解為,傳輸數(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

綁定端口號(hào)

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

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

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

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

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

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

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

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

服務(wù)器端代碼:

客戶(hù)端代碼:

先編譯

并運(yùn)行:

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

并運(yùn)行:

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

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

該結(jié)構(gòu)體即本篇博客開(kāi)頭的背景知識(shí)部分所提到的新的通用套接字地址結(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ú)符號(hào)長(zhǎng)整型的網(wǎng)絡(luò)字節(jié)順序。本函數(shù)將一個(gè)32位數(shù)從主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序??深?lèi)比于ntohl()

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

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

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

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

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

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

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

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

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

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

ai_flags參數(shù):

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

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

AI_NUMERICHOST:阻止域名解析。

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

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

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

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

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

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

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

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

SO_TYPE返回socket形態(tài)

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

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

SO_BROADCAST使用廣播方式傳送

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

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

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

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

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

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

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

intbind(intsocket,conststructsockaddr*address,socklen_taddress_len)

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

intlisten(intsocket,intbacklog)

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

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

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

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

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

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

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

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

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

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

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

參數(shù)說(shuō)明:

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

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

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

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

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

返回值說(shuō)明:

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

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

參數(shù)說(shuō)明:

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

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

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

返回值說(shuō)明:

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

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

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

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

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

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

返回值:成功返回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)核自動(dòng)生成的一個(gè)全新描述符,代表與所返回客戶(hù)的TCP連接。我們常常稱(chēng)它的第一個(gè)參數(shù)為監(jiān)聽(tīng)套接字(listening

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

socket)

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

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

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

參數(shù)說(shuō)明:

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

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

返回值說(shuō)明:

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

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

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

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

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

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

由于通信方式的種類(lèi)有多種,套接字接口只有一套,如果給每個(gè)通信方式都設(shè)計(jì)一套接口,單從技術(shù)的角度來(lái)說(shuō),完全是可以的。但是從使用者和學(xué)習(xí)者來(lái)講,無(wú)疑是增加了負(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地址、端口號(hào)以及所采用的協(xié)議族。

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

這三個(gè)結(jié)構(gòu)的第一個(gè)字段都是一個(gè)unsignedshortint類(lèi)型,只不過(guò)用宏來(lái)定義了三個(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è)類(lèi)型沒(méi)有定義,繼續(xù)找下去吧:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

無(wú)連接、有連接:

打電話就是有連接的,需要連接建立了才能通信。連接建立需要對(duì)方來(lái)接收,如果連接沒(méi)有建立好,就通信不了。

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

不可靠傳輸、可靠傳輸:

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

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

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

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

全雙工、半雙工:

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

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

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

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

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

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

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

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

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

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

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

解決方法:

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

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

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

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

DatagramSocket構(gòu)造方法:

DatagramSocket方法:

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

DatagramPacket構(gòu)造方法:

DatagramPacket方法:

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

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

先來(lái)看一遍完整代碼:

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

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

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

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

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

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

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

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

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

主要的工作流程:

1.讀取請(qǐng)求并解析

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

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

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

先來(lái)看完整代碼:

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

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

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

客戶(hù)端發(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和端口號(hào),以便客戶(hù)端能訪問(wèn)到服務(wù)器端。

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

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

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

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

構(gòu)造方法:

方法:

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

構(gòu)造方法:

方法:

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

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

accept如果沒(méi)有連接到客戶(hù)端,就會(huì)一直阻塞。

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

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

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

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

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

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

通過(guò)socket來(lái)接收服務(wù)器的ip和端口號(hào)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

注意:

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

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

注意:

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

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

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

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

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

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

注意:

注意:

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

以下僅展示部分代碼,完整代碼可以看博主的gitee倉(cāng)庫(kù):

UDP客戶(hù)端:

UDP服務(wù)端:

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

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

服務(wù)端

客戶(hù)端

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

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

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

IPv4的sockaddr_in結(jié)構(gòu)體:指明了端點(diǎn)地址,其包括用來(lái)識(shí)別地址類(lèi)型的2字節(jié)字段(必須為AF_INET),還有一個(gè)2字節(jié)的端口號(hào)字段,一個(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地址

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

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

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

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

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

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

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

hints參數(shù):指向用戶(hù)指定的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類(lèi)型,常用有SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,設(shè)置為0表示所有類(lèi)型等

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

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

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

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

freeaddrinfo(structaddrinfo*ai)

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

intsocket(intdomain,inttype,intprotocol);

創(chuàng)建主動(dòng)套接字的一方(客戶(hù)端)調(diào)用connect()系統(tǒng)調(diào)用,可建立與被動(dòng)套接字的一方(服務(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)體長(zhǎng)度

close()一個(gè)TCP套接字的默認(rèn)行為是把該套接字標(biāo)記為關(guān)閉,此后不能再對(duì)該文件描述符進(jìn)行讀寫(xiě)操作。TCP協(xié)議將嘗試發(fā)送已排隊(duì)等待發(fā)送到對(duì)端的任何數(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ò)誤號(hào)errnum,并返回一個(gè)指向錯(cuò)誤消息字符串的指針。

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

注意:return函數(shù)有沒(méi)有括號(hào)“()”都是正確的,為了簡(jiǎn)明,一般省略不寫(xiě)

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

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

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

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

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

size_twrite(intflides,constvoid*buf,size_tnbytes)

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

uint32_tntohl(uint32_tnetlong);

ntohl()將一個(gè)無(wú)符號(hào)長(zhǎng)整形數(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)體中儲(chǔ)存的時(shí)間轉(zhuǎn)換為字符串。

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

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

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

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

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

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

拓展:

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

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

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

eg:X86架構(gòu)

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

eg:MIPS架構(gòu)

在網(wǎng)絡(luò)通信中并不知道對(duì)端主機(jī)是大端還是小端因此兩個(gè)不同主機(jī)字節(jié)的主機(jī)在進(jìn)行通信時(shí)會(huì)造成數(shù)據(jù)二義,字節(jié)序?qū)W(wǎng)絡(luò)通信造成影響主要針對(duì)存儲(chǔ)大于一個(gè)字節(jié)的類(lèi)型(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é)序稱(chēng)為網(wǎng)絡(luò)字節(jié)序:大端字節(jié)序,所以在編寫(xiě)網(wǎng)絡(luò)通信程序中,若是傳輸大于一個(gè)字節(jié)類(lèi)型的數(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客戶(hù)/服務(wù)器通信流程如下,主要為了大家能夠更好的理解

配有代碼詳解圖

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

詳解:

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

詳解:

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

套接字編程的總結(jié)第23篇源文件包含ECHO服務(wù)器的代碼,它使用并發(fā)進(jìn)程為多個(gè)客戶(hù)提供并發(fā)服務(wù)。源文件包含ECHO服務(wù)器的代碼,它使用迭代的、無(wú)連接算法為多個(gè)客戶(hù)提供服務(wù)。本題程序調(diào)用了自定義例程庫(kù)函數(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)程被稱(chēng)為子進(jìn)程。fork函數(shù)被調(diào)用一次但返回兩次。兩次返回的唯一區(qū)別是子進(jìn)程中返回0值而父進(jìn)程中返回子進(jìn)程ID。子進(jìn)程是父進(jìn)程的副本,它將獲得父進(jìn)程數(shù)據(jù)空間、堆、棧等資源的副本,即存儲(chǔ)空間的“副本”,意味著父子進(jìn)程間不共享這些存儲(chǔ)空間。子進(jìn)程有了獨(dú)立的地址空間,故無(wú)法確定fork之后是子進(jìn)程先運(yùn)行還是父進(jìn)程先運(yùn)行,這依賴(lài)于系統(tǒng)的實(shí)現(xiàn)。

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

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

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

參數(shù):

intfputs(constchar*str,FILE*stream)

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

參數(shù):

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

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

對(duì)于使用的地址有以下一些限制。

對(duì)于因特網(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,但沒(méi)有將地址綁定到套接字上,系統(tǒng)會(huì)選一個(gè)地址綁定到套接字上。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

本文中討論的所有平臺(tái)都將EAGAIN定義為EWOULDBLOCK。

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

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

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

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

類(lèi)似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)被無(wú)錯(cuò)誤地發(fā)送到網(wǎng)絡(luò)驅(qū)動(dòng)程序上。

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

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

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

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

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

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

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

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

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

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

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

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

recvmsg用msghdr結(jié)構(gòu)(在sendmsg中見(jiàn)到過(guò))指定接收數(shù)據(jù)的輸入緩沖區(qū)??梢栽O(shè)置參數(shù)flags來(lái)改變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)接口來(lái)控制套接字行為。一個(gè)接口用來(lái)設(shè)置選項(xiàng),另一個(gè)接口可以查詢(xún)選項(xiàng)的狀態(tài)。可以獲取或設(shè)置以下3種選項(xiàng)。

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

參數(shù)level標(biāo)識(shí)了選項(xiàng)應(yīng)用的協(xié)議。如果選項(xiàng)是通用的套接字層次選項(xiàng),則level設(shè)置成SOL_SOCKET。否則,level設(shè)置成控制這個(gè)選項(xiàng)的協(xié)議編號(hào)。對(duì)于TCP選項(xiàng),level是IPPROTO_TCP,對(duì)于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開(kāi)關(guān)。如果整數(shù)非0,則啟用選項(xiàng)。如果整數(shù)為0,則禁止選項(xiàng)。參數(shù)len指定了val指向的對(duì)象的大小。

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

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

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

TCP將帶外數(shù)據(jù)稱(chēng)為緊急數(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ù)超過(guò)一個(gè)時(shí),最后一個(gè)字節(jié)將被視為緊急數(shù)據(jù)字節(jié)。

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

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

將返回owner,如果owner為正值,則等于配置為接收套接字信號(hào)的進(jìn)程的ID。如果owner為負(fù)值,其絕對(duì)值為接收套接字信號(hào)的進(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ù)會(huì)返回一個(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ù)到來(lái),那么已有的字節(jié)會(huì)被丟棄。

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

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

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

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

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

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

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

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

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

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

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

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

套接字不僅支持跨網(wǎng)絡(luò)的進(jìn)程間通信,還支持本地的進(jìn)程間通信(域間套接字)。在進(jìn)行跨網(wǎng)絡(luò)通信時(shí)我們需要傳遞的端口號(hào)和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)我們?cè)趥鬟f在傳參時(shí),就不用傳入sockeaddr_in或sockeaddr_un這樣的結(jié)構(gòu)體,而統(tǒng)一傳入sockeaddr這樣的結(jié)構(gòu)體。在設(shè)置參數(shù)時(shí)就可以通過(guò)設(shè)置協(xié)議家族這個(gè)字段,來(lái)表明我們是要進(jìn)行網(wǎng)絡(luò)通信還是本地通信,在這些API內(nèi)部就可以提取sockeaddr結(jié)構(gòu)頭部的16位進(jìn)行識(shí)別,進(jìn)而得出我們是要進(jìn)行網(wǎng)絡(luò)通信還是本地通信,然后執(zhí)行對(duì)應(yīng)的操作。此時(shí)我們就通過(guò)通用sockaddr結(jié)構(gòu),將套接字網(wǎng)絡(luò)通信和本地通信的參數(shù)類(lèi)型進(jìn)行了統(tǒng)一。

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

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

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

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

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

數(shù)據(jù)類(lèi)型in_port_t定義成uint16_t。數(shù)據(jù)類(lèi)型in_addr_t定義成uint32_t。這些整數(shù)類(lèi)型在中定義并指定了相應(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。

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

歷史上,BSD網(wǎng)絡(luò)軟件提供了訪問(wèn)各種網(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)。無(wú)論這個(gè)信息放在何處,都可以用同樣的函數(shù)訪問(wèn)它。

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

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

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

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

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

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

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

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

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

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

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 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ì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論