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端