對socket通信的理解

       socket是“插座”的意思,兩個進程之間通過socket來進行通信可以用手機來比喻,一般都是客戶端向服務器發出訪問請求,則客戶端類比爲撥電話的人,而服務器類比爲接電話的人。兩個用戶要對話首先雙方都要有一部手機,相當於雙方用socket()函數創建一個socket套接字一樣,然後這部手機要有一個電話號它纔能有利用價值,也就是將套接字與通信地址進行掛鉤。對於客戶端,也就是準備撥電話的人,他必須要知道他想給誰打也就是對方的電話號是多少,通過connect()函數將它想訪問的通信地址與該套接字進行連接。而接電話的人他要給這部電話一個電話號,其他人才能打過來,所以利用bind()給這部電話綁定一個電話號。這樣就能看出,如果雙方想要成功的建立連接,雙方的通信地址必須是一樣的,就像我要給你打電話,必須撥打的是你的手機號一樣。

那這個手機就有不同了,有的是國際號,有的是家裏的小號,不同的號對電話的要求不太一樣,拿着小靈通就打不到國外了……而再然後,有的打電話就必須對方接到再進行通話,有的可以留言,對方什麼時候有空再聽。前者代表的是協議族,有本地通信和ipv4 ipv6 網絡通信,後者表示連接類型,必須連接是TCP通信類型,可以留言的是UDP通信類型,通過選擇參數選擇合適的電話機。

對這個電話號碼也是有要求的,家裏的小號是短號,國外的可能就多好幾位,對於本地通信和網絡通信有通用的通信地址,不需要我們自己定義,分別是struct  sockaddr_un和struct sockaddr_in,但是在bind和connect過程中爲了方便,我們都把他們強制轉換爲通用的一個地址格式struct sockaddr。有一點要注意就是在網絡通信地址裏的端口號,由於網絡字節和主機字節很多時候是不一樣的,網絡字節是大端網絡,而主機字節很多情況下是小端網絡,也就是假設端口號是1234,那麼在計算機裏存儲是地址由低到高:0x34 0x12,但在網絡上存儲時地址由低到高:0x12 0x34,這個時候利用htons()函數轉變字節順序。

一:本地通信和UDP通信

都不是面向連接的,過程大致爲

服務器:
   
1)創建socket,使用socket函數
   
2)準備通信地址,使用結構體類型
   
3)綁定socket和通信地址,使用bind函數
   
4)進行通信,使用read/write函數
   
5)關閉socket,使用close函數
客戶端:
   
1)創建socket,使用socket函數
   
2)準備通信地址,服務器的地址
   
3)鏈接socket和通信地址,使用connect函數
   
4)進行通信,使用read/write函數
   
5)關閉socket,使用close函數

二:TCP通信

是面向連接的,所以接電話的人先給自己手機綁定一個手機號以後,就一直等着電話響,這就是listen(),等到發現電話響了,馬上把電話接起來,這就是accept(),然後雙方就可以進行通話了。注意這裏accept()函數會返回一個文件描述符,服務器是通過返回的這個描述符來通信而不是像UDP一樣用socket的套接字來通信。

過程如下:

服務器:
1)創建socket,使用socket函數
2)準備通信地址,使用結構體類型
3)綁定socket和通信地址,使用bind函數
4)監聽,使用listen函數
5)響應客戶端的連接請求,使用accept函數
6)進行通信,使用read/write函數
7)關閉socket,使用close函數
客戶端:
1)創建socket,使用socket函數
2)準備通信地址,使用結構體類型
3)連接socket和通信地址,使用connect函數
4)進行通信,使用read/write函數
5)關閉socket,使用close函數


學習中遇到的一些問題和解決方法:

 

一:bind出現Address already in use:

         1:本地通信:unixdomain socket 與網絡socket編程最大的不同在於地址格式不同,用結構體socketaddr_un表示,網絡地址是由ip加端口號決定,而domain socket的地址是一個socket類型的文件在文件系統的路徑,該文件由bind()函數創建並綁定,如果bind時該文件已經存在,則綁定失敗。因此每次把創建的socket文件刪除或者bind一個新的socket文件。

         2:網絡通信:程序第一次可以正確運行並且結束,但第二次開始就會出現錯誤bind:Address already in use,只能用ctrl+c強制結束,這個問題是由TCP 套接字狀態TIME_WAIT 引起,在套接字通過close()正常關閉後,會保留2到4分鐘,該套接字纔會刪除,同時與該套接字綁定的端口和本地地址纔可以被重新綁定。所以如果我們運行過一次程序,用close(sockfd)刪除這個套接字之後,其實要等到幾分鐘之後,纔會真正刪除,這段時間內的端口和本地地址是仍然與該套接字綁定的,如果你立即再執行一遍程序,便會提示:這個地址正在使用中。

         有一個繞過TIME_WAIT的方法就是,給套接字設置一下,讓它可以綁定一個複用的端口就可以了。具體是利用setsockopt函數。

int setsockopt(SOCKET s,int level,intoptname,const char* optval,int optlen)

SOCKET(套接字): 指向一個打開的套接口描述字

level:(級別): 指定選項代碼的類型。

SOL_SOCKET: 基本套接口

IPPROTO_IP: IPv4套接口

IPPROTO_IPV6: IPv6套接口

IPPROTO_TCP: TCP套接口

optname(選項名): 選項名稱

optval(選項值): 是一個指向變量的指針 類型:整形,套接口結構, 其他結構類型:linger{},timeval{ }

optlen(選項長度) :optval 的大小

返回值:標誌打開或關閉某個特徵的二進制選項

 

因此可以加這麼一句:

int reuse = 1;

setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,&reuse, sizeof(reuse));

(SO_REUSERADDR 允許重用本地地址和端口,充許綁定已被使用的地址(或端口號))

 

二:bind: Cannot assign requested address

         首先作爲服務器bind時候,必須綁定自己的ip地址,不能亂寫成別的ip地址,另外在tcp中端口號不能被佔用,可以加上上一題裏那兩句允許複用的程序,就可以綁定佔用的端口。程序測試發現udp裏可以綁定正在使用中的端口(留疑問)。

         然後服務器和客戶端要通信,兩端通信地址必須完全一樣,ip和端口都要一致。

 

 三:connect: Connection refused

客戶端要和服務器連接時,服務器必須是已經打開的,所以要先開服務器再開客戶端。

 

四:read:Bad address

大部分是由於讀寫內存地址錯誤的問題

五:read: Connection reset by peer

客戶端要讀數據時,此時服務器已經關閉了

六:服務器這端在read()數據時,會接收兩次數據

如果write()端的發送數據大小大於read()端的接收數據大小,數據會分成兩批進行接收。因此要保持兩端的數據大小一致或者read端大於write端

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章