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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

套接字編程的總結第1篇套接字編程的總結第1篇

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

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

注意:

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

Socket對象:建立起的連接

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

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

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

注意:

注意:

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

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

擴展了解:

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

由于每個連接都需要不停的阻塞等待接收數據,所以每個連接都會在一個線程中運行。一次阻塞等待對應著一次請求、響應,不停處理也就是長連接的特性:一直不關閉連接,不停的處理請求。

實際應用時,服務端一般是基于NIO(即同步非阻塞IO)來實現長連接,性能可以極大的提升。

現在還遺留一個問題:

如果同時多個長連接客戶端,連接該服務器,能否正常處理?

需要在IDEA配置客戶端支持同時運行多個實例!

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

將任務專門交給其他線程來處理,主線程只負責接受socket。

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

TCP服務端:

TCP客戶端:

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

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

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

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

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

3、原始套接字

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

支持IPv4和IPv6可重入函數其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換IPv6的in6_addr。

socket模型流程圖

在Linux下創(chuàng)建socket

綁定端口號

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

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

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

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

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

TCP套接字默認情況下是阻塞模式

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

演示了Linux下的代碼,是服務器端代碼,是客戶端代碼,要實現的功能是:客戶端從服務器讀取一個字符串并打印出來。

服務器端代碼:

客戶端代碼:

先編譯

并運行:

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

并運行:

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

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

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

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

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

uint32htonl(uint32hl)

htonl()將主機數轉換成無符號長整型的網絡字節(jié)順序。本函數將一個32位數從主機字節(jié)順序轉換成網絡字節(jié)順序??深惐扔趎tohl()

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

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

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

參數msg指向欲連線的數據內容

參數len數據內容長度

參數flags一般設0

參數to用來指定欲傳送的網絡地址

參數tolen為sockaddr的結構長度

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

在本篇博客前面部分有講解到addrinfo結構體,其中hints參數的ai_flags成員沒有細說,在服務器庫例程中又有涉及,故在此敘述下:

ai_flags參數:

AI_PASSIVE:設置了AI_PASSIVE標志,則

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

AI_NUMERICHOST:阻止域名解析。

AI_NUMERICSERV:阻止服務名解析。

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

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

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

參數level代表欲設置的網絡層,一般設成SOL_SOCKET以存取socket層

參數optname代表欲設置的選項,有下列幾種數值:

SO_DEBUG打開或關閉排錯模式

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

SO_TYPE返回socket形態(tài)

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

SO_DONTROUTE送出的數據包不要利用路由設備來傳輸

SO_BROADCAST使用廣播方式傳送

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

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

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

SO_OOBINLINE當接收到OOB數據時會馬上送至標準輸入設備

SO_LINGER確保數據安全且可靠的傳送出去.

參數optval代表欲設置的值

參數optlen則為optval的長度

intbind(intsocket,conststructsockaddr*address,socklen_taddress_len)

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

intlisten(intsocket,intbacklog)

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

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

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

套接字編程的總結第8篇

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

以上函數的實現及解析會在代碼中實現介紹

直接上代碼,代碼里面詳細的解析

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

注意:因為tcp協(xié)議存在三次握手,在傳輸信息時會服務端每次都會新建立一個新的socket所以在多個客戶端向服務端發(fā)出請求時,會覆蓋原先的socket的操作句柄(fd),導致之前的客戶端鏈接失效,所以需要多線程或多進程實現

套接字編程的總結第9篇

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

參數說明:

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

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

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

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

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

返回值說明:

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

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

參數說明:

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

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

addrlen參數:是該協(xié)議的地址結構的長度。

返回值說明:

綁定成功返回0,綁定失敗返回-1,同時錯誤碼會被設置。

listen函數僅由TCP服務器調用,表明服務器對外宣告它愿意接受連接請求,它做兩件事:

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

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

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

一旦服務器調用了listen,所用的套接字就能接受連接請求。使用accept函數獲得的連接請求并建立連接。

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

本函數通常應該在調用socket和bind這兩個函數之后,并在調用accept函數之前調用。

accept函數是由TCP服務器調用,用于從已完成連接隊列隊頭返回下一個已完成連接。如果已完成連接隊列為空,那么進程將被投入睡眠。

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

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

socket)

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

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

TCP客戶用connect函數來建立與TCP服務器的連接。

參數說明:

sockfd參數:是由socket函數返回的套接字描述符,第二個以及第三個參數分別是指向套接字地址結構的指針和該結構的大小。

在connect中指定的地址是我們想要與之通信服務器地址。如果sockfd沒有綁定到一個地址,connect會給調用者綁定一個默認地址。

返回值說明:

若成功則為0,若出錯則為-1;

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

在網絡通信的時候,其標準方式有多種,比如:IPV4套接字地址結構——structsockaddr_in,Unix域套接字地址結構——structsockaddr_un;前者屬于網絡通信,后者屬于域間通信;

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

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

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

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

其中3個結構里都包含了__SOCKADDR_COMMON這個宏,我們先把它的定義找到;

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

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

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

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

附圖:源碼結構

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

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

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

套接字編程的總結第11篇舉個栗子:

關于端口被占用的問題:

如果一個進程A已經綁定了一個端口,再啟動一個進程B綁定該端口,就會報錯,這種情況也叫端口被占用。對于java進程來說,端口被占用的常見報錯信息如下:

此時需要檢查進程B綁定的是哪個端口,再查看該端口被哪個進程占用。以下為通過端口號查進程的方式:

在cmd輸入netstat-ano|findstr端口號,則可以顯示對應進程的pid。如以下命令顯示了8888進程的pid

在任務管理器中,通過pid查找進程解決端口被占用的問題:

套接字編程的總結第12篇網絡編程的核心,是SocketAPI,這是一個由操作系統(tǒng)給應用程序提供的網絡編程API。

并且我們認為SocketAPI是和傳輸層密切相關的。

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

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

數據報套接字:使用傳輸層UDP協(xié)議

無連接、有連接:

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

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

不可靠傳輸、可靠傳輸:

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

面向字節(jié)流、面向數據報:

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

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

全雙工、半雙工:

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

只能單向傳輸的就叫做半雙工。

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

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

首先先了解一些注意事項:

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

2.注意目的IP和目的端口號,標識了一次數據傳輸時要發(fā)送數據的終點主機和進程。

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

4.關于端口被占用的問題:

如果一個進程A已經綁定了一個端口,再啟動一個進程B綁定該端口,就會報錯,這就叫端口被占用。對于Java進程來說,端口被占用的常見報錯信息如下:

在cmd輸入:netstat-ano|findstr端口號就可以顯示對應進程的pid,然后在任務管理器中通過pid查找進程。

解決方法:

如果占用端口的進程A不需要運行,就可以關閉A后,再啟動需要綁定該端口的進程B。

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

DatagramSocket是UDPSocket,用于發(fā)送和接收UDP數據報。

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

DatagramSocket構造方法:

DatagramSocket方法:

DatagramPacket是UDPSocket發(fā)送和接收的數據報。

DatagramPacket構造方法:

DatagramPacket方法:

普通的服務器:收到請求,根據請求計算響應,返回響應。

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

先來看一遍完整代碼:

我們一點一點來解析:

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

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

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

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

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

緊接著我們用一個process方法來表示服務器的響應。實際開發(fā)中這個部分是最重要的,服務器的響應是整個網絡編程最核心的部分之一。

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

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

主要的工作流程:

1.讀取請求并解析

2.根據請求計算相應

3.構造響應并且寫回客戶端

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

先來看完整代碼:

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

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

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

客戶端發(fā)送給服務器后,就進入阻塞等待,這里的receive能阻塞,是因為操作系統(tǒng)原生提供的API就是阻塞的函數,這里的阻塞不是Java實現的,而是系統(tǒng)內核里實現的。

同時最后的main函數中,應該指定好ip和端口號,以便客戶端能訪問到服務器端。

同時也可以打開這個選項,同時開啟多個客戶端,共用一個服務器。

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

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

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

構造方法:

方法:

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

構造方法:

方法:

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

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

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

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

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

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

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

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

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

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

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

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

等待服務器返回消息后,用responseScanner來接收InputStream傳輸的數據,再打印出來。

當前咱們的服務器同一時刻只能給一個客戶端提供服務,這是不科學的。當前啟動服務器后,先后啟動兩個客戶端,客戶端1可以看到正常的上線提示,但是客戶端2沒有任何提醒。當結束客戶端1后,客戶端2馬上顯示上線。

當客戶端連接上服務器之后,代碼執(zhí)行到processConnection這個方法中的while循環(huán)了,此時意味著,只要這個循環(huán)不結束,processConnection方法就結束不了。進一步的也就無法調用到第二次的accept。

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

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

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

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

通過線程池的方法,就能進一步減少消耗。

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

解決辦法就是:IO多路復用,IO多路轉接

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

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

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

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

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

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

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

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

套接字編程的總結第13篇發(fā)送信息:應用層-》傳輸層-》網絡層-》鏈路層-》物理層

接受信息:物理層-》鏈路層-》網絡層-》傳輸層-》應用層

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

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

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

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

DatagramSocket是UDPSocket,用于發(fā)送和接收UDP數據報。

注意:

UDP服務器(Server):采用一個固定端口,方便客戶端(Client)進行通信;使用DatagramSocket(intport),就可以綁定到本機指定的端口,此方法可能有錯誤風險,提示該端口已經被其他進程占用。

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

注意:

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

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

通信結束后,雙方都應該調用close()方法進行資源回收

DatagramPacket是UDPSocket發(fā)送和接收的數據報。

這個類就是定義的報文包:通信過程中的數據抽象

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

注意:

注意:

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

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

UDP客戶端:

UDP服務端:

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

套接字編程的總結第15篇

服務端

客戶端

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

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

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

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

IPv6的sockaddr_in6結構體

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

通用套接口地址結構

**新的通用套接口地址結構:**兼容IPv6地址

考試中的庫例程程序相當于把復習資料里的示例程序更加抽象化了。

包含在中,主要在網絡編程解析時使用。

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

返回值0表示成功,非0表示錯誤。

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

nodename參數:主機名,或者是點分十進制地址串(IPv4),或16進制串(IPv6)

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

hints參數:指向用戶指定的structaddrinfo結構體,只能設定其中ai_family,ai_socktype,ai_protocol和ai_flags四個域,其他域必須為0或者是NULL。其中:

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

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

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

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

res參數:獲取一個指向存儲結果的structaddrinfo結構體,使用完成后調用freeaddrinfo()釋放存儲結果空間

釋放addrinfo結構體動態(tài)內存;

freeaddrinfo(structaddrinfo*ai)

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

intsocket(intdomain,inttype,intprotocol);

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

intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen)

**sockfd參數:**連接文件描述符

addr參數:指定協(xié)議的地址結構體指針

addrlen參數:協(xié)議地址結構體長度

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

intclose(intfd)

char*gai_strerror(interror)

getaddrinfo出錯時返回非零值,gai_strerror根據返回的非零值返回指向對應的出錯信息字符串的指針。

char*strerror(interrnum)

從內部數組中搜索錯誤號errnum,并返回一個指向錯誤消息字符串的指針。

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

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

套接字編程的總結第17篇網絡上的主機通過不同的進程,以編程的方式實現網絡通信,我們稱之為網絡編程。我們只要滿足不同的進程即可,所以即便是同一個主機,只要是不同的進程,基于網絡傳輸數據,也是屬于網絡編程

在一次網絡數據傳輸時,有發(fā)送端、接收端、收發(fā)端三方注意:發(fā)送端和接收端只是相對的,只是一次網絡數據傳輸產生數據流向后的概念。

一般來說,獲取一個網絡資源,涉及到兩次網絡數據傳輸:第一次:請求數據的發(fā)送;第二次:響應數據的發(fā)送;

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

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

size_twrite(intflides,constvoid*buf,size_tnbytes)

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

uint32_tntohl(uint32_tnetlong);

ntohl()將一個無符號長整形數從網絡字節(jié)順序轉換為主機字節(jié)順序,返回一個以主機字節(jié)順序表達的數。

structtm*localtime(consttime_t*timer)

把從1970-1-1零點零分到當前時間系統(tǒng)所偏移的秒數時間轉換為本地時間。

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

char*asctime(conststructtm*timeptr)

把timeptr指向的tm結構體中儲存的時間轉換為字符串。

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

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

read()會把參數fd所指的文件傳送count個字節(jié)到buf指針所指的內存中。若參數count為0,則read()不會有作用并返回0。返回值為實際讀取到的字節(jié)數,如果返回0,表示已到達文件尾或是無可讀取的數據,此外文件讀寫位置會隨讀取到的字節(jié)移動。

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

Socket是站在應用層,做網絡編程很重要的一個概念

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

拓展:

OS原生的提供的系統(tǒng)調用(Linux上的網絡編程):

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

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

eg:X86架構

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

eg:MIPS架構

在網絡通信中并不知道對端主機是大端還是小端因此兩個不同主機字節(jié)的主機在進行通信時會造成數據二義,字節(jié)序對網絡通信造成影響主要針對存儲大于一個字節(jié)的類型(int16_t,int32_t,short,int,long,float,double);

為了避免因為主機字節(jié)序不同導致的數據二義性,因此規(guī)定網絡通信使用統(tǒng)一字節(jié)標準,把這個統(tǒng)一的標準字節(jié)序稱為網絡字節(jié)序:大端字節(jié)序,所以在編寫網絡通信程序中,若是傳輸大于一個字節(jié)類型的數據,就需要考慮字節(jié)序的轉換gong

注意:可使用聯合體使用大小端的判斷

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

uint32_thtonl(uint32_thostlong);//把32位主機字節(jié)序轉換成網絡字節(jié)序

uint16_thtons(uint16_thostshort);

uint32_tntohl(uint32_tnetlong);

uint16_tntohs(uint16_tnetshort);//把16位網絡字節(jié)序轉換成主機字節(jié)序

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

配有代碼詳解圖

TCP服務端代碼入下:

詳解:

單進程版我們一般不用

詳解:

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

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

pid_tfork(void)

返回值:若成功調用一次則返回兩個值,子進程返回0,父進程返回子進程ID;否則,出錯返回-1。

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

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

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

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

參數:

intfputs(constchar*str,FILE*stream)

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

參數:

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

使用bind函數來關聯地址和套接字。

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

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

可以調用getsockname函數來發(fā)現綁定到套接字上的地址。

調用getsockname之前,將alenp設置為一個指向整數的指針,該整數指定緩沖區(qū)sockaddr的長度。返回時,該整數會被設置成返回地址的大小。如果地址和提供的緩沖區(qū)長度不匹配,地址會被自動截斷而不報錯。如果當前沒有地址綁定到該套接字,則其結果是未定義的。

如果套接字已經和對等方連接,可以調用getpeername函數來找到對方的地址。

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

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

在connect中指定的地址是我們想與之通信的服務器地址。如果sockfd沒有綁定到一個地址,connect會給調用者綁定一個默認地址。

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

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

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

服務器調用listen函數來宣告它愿意接受連接請求。

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

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

一旦服務器調用了listen,所用的套接字就能接收連接請求。使用accept函數獲得連接請求并建立連接。

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

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

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

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

如果服務器調用accept,并且當前沒有連接請求,服務器會阻塞直到一個請求到來。另外,服務器可以使用poll或select來等待一個請求的到來。在這種情況下,一個帶有等待連接請求的套接字會以可讀的方式出現。

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

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

最簡單的是send,它和write很像,但是可以指定標志來改變處理傳輸數據的方式。

類似write,使用send時套接字必須已經連接。參數buf和nbytes的含義與write中的一致。

然而,與write不同的是,send支持第4個參數flags。圖16-13總結了這些標志。

即使send成功返回,也并不表示連接的另一端的進程就一定接收了數據。我們所能保證的只是當send成功返回時,數據已經被無錯誤地發(fā)送到網絡驅動程序上。

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

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

通過套接字發(fā)送數據時,還有一個選擇??梢哉{用帶有msghdr結構的sendmsg來指定多重緩沖區(qū)傳輸數據,這和writev函數很相似。

定義了msghdr結構,它至少有以下成員:

函數recv和read相似,但是recv可以指定標志來控制如何接收數據。

當指定MSG_PEEK標志時,可以查看下一個要讀取的數據但不真正取走它。當再次調用read或其中一個recv函數時,會返回剛才查看的數據。

對于SOCK_STREAM套接字,接收的數據可以比預期的少。MSG_WAITALL標志會阻止這種行為,直到所請求的數據全部返回,recv函數才會返回。對于SOCK_DGRAM和SOCK_SEQPACKET套接字,MSG_WAITALL標志沒有改變什么行為,因為這些基于報文的套接字類型一次讀取就返回整個報文。

如果發(fā)送者已經調用shutdown來結束傳輸,或者網絡協(xié)議支持按默認的順序關閉并且發(fā)送端已經關閉,那么當所有的數據接收完畢后,recv會返回0。

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

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

因為可以獲得發(fā)送者的地址,recvfrom通常用于無連接的套接字。否則,recvfrom等同于recv。

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

recvmsg用msghdr結構(在sendmsg中見到過)指定接收數據的輸入緩沖區(qū)??梢栽O置參數flags來改變recvmsg的默認行為。返回時,msghdr結構中的msg_flags字段被設為所接收數據的各種特征。(進入recvmsg時msg_flags被忽略。)recvmsg中返回的各種可能值總結在圖16-15中。

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

可以使用setsockopt函數來設置套接字選項。

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

參數val根據選項的不同指向一個數據結構或者一個整數。一些選項是on/off開關。如果整數非0,則啟用選項。如果整數為0,則禁止選項。參數len指定了val指向的對象的大小。

可以使用getsockopt函數來查看選項的當前值。

參數lenp是一個指向整數的指針。在調用getsockopt之前,設置該整數為復制選項緩沖區(qū)的長度。如果選項的實際長度大于此值,則選項會被截斷。如果實際長度正好小于此值,那么返回時將此值更新為實際長度。

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

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

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

F_GETOWN命令可以用來獲得當前套接字所有權。對于F_SETOWN命令,負值代表進程組ID,正值代表進程ID。因此,調用

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

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

當下一個要讀取的字節(jié)在緊急標志處時,sockatmark返回1。

當帶外數據出現在套接字讀取隊列時,select函數會返回一個文件描述符并且有一個待處理的異常條件??梢栽谄胀〝祿魃辖邮站o急數據,也可以在其中一個recv函數中采用MSG_OOB標志在其他隊列數據之前接收緊急數據。TCP隊列僅用一個字節(jié)的緊急數據。如果在接收當前的緊急數據字節(jié)之前又有新的緊急數據到來,那么已有的字節(jié)會被丟棄。

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

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

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

要完成第二個步驟,有兩個選擇。

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

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

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

寫一個客戶端程序和服務端程序,返回指定主機上當前運行的進程數量。

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

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

隨書練習源碼地址

套接字編程的總結第26篇sockaddr結構的出現

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

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

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

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

對于TCP/IP應用程序,有4個用來在處理器字節(jié)序和網絡字節(jié)序之間實施轉換的函數。

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

一個地址標識一個特定通信域的套接字端點,地址格式與這個特定的通信域相關。為使不同格式地址能夠傳入到套接字函數,地址會被強制轉換成一個通用的地址結構sockaddr:

因特網地址定義在頭文件中。在IPv4因特網域(AF_INET)中,套接字地址用結構sockaddr_in表示:

數據類型in_port_t定義成uint16_t。數據類型in_addr_t定義成uint32_t。這些整數類型在中定義并指定了相應的位數。

與AF_INET域相比較,IPv6因特網域(AF_INET6)套接字地址用結構sockaddr_in6表示:

注意,盡管sockaddr_in與sockaddr_in6結構相差比較大,但它們均被強制轉換成sockaddr結構輸入到套接字例程中。

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

函數inet_ntop將網絡字節(jié)序的二進制地址轉換成文本字符串格式。inet_pton將文本字符串格式轉換成網絡字節(jié)序的二進制地址。參數domain僅支持兩個值:AF_INET和AF_INET6。

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

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

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

如果主機數據庫文件沒有打開,gethostent會打開它。函數gethostent返回文件中的下一個條目。函數sethostent會打開文件,如果文件已經被打開,那么將其回繞。當stayopen參數設置成非0值時,調用gethostent之后,文件將依然是打開的。函數endhostent可以關閉文件。

當gethostent返回時,會得到一個指向hostent結構的指針,該結構可能包含一個靜態(tài)的數據緩沖區(qū),每次調用gethostent,緩沖區(qū)都會被覆蓋。hostent結構至少包含以下成員:

返回的地址采用網絡字節(jié)序。

能夠采用一套相似的接口來獲得網絡名字和網絡編號。

netent結構至少包含以下字段:

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

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

定義的protoent結構至少包含以下成員:

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

溫馨提示

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

評論

0/150

提交評論