TCP
和 UDP
TCP
追求的是可靠的傳輸數(shù)據(jù), UDP
追求的則是快速的傳輸數(shù)據(jù)以下例子以
*nix
平臺(tái)的便準(zhǔn)為例,因?yàn)?Windows
平臺(tái)需要考慮額外的加載問題,稍作添加就能在 Windows 平臺(tái)上運(yùn)行
UDP
接收端:
int sock; /* 套接字 */
socklen_t addr_len; /* 發(fā)送端的地址長(zhǎng)度,用于 recvfrom */
char mess[15];
char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
struct sockaddr_in recv_host, send_host;
/* 創(chuàng)建套接字 */
sock = socket(PF_INET, SOCK_DGRAM, 0);
/* 把IP 和 端口號(hào)信息綁定在套接字上 */
memset(&recv_host, 0, sizeof(recv_host));
recv_host.sin_family = AF_INET;
recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 接收任意的IP */
recv_host.sin_port = htons(6000); /* 使用6000 端口號(hào) */
bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host));
/* 進(jìn)入接收信息的狀態(tài) */
recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len);
/* 接收完成,關(guān)閉套接字 */
close(sock);
上述代碼省略了許多必要的 錯(cuò)誤檢查 ,在實(shí)際編寫時(shí)要添加
htonl
和 htons
兩個(gè)函數(shù)的使用涉及到 大端小端問題, 這里不敘述,需要記住的是在網(wǎng)絡(luò)編程時(shí)一定要使用這種函數(shù)將必要信息轉(zhuǎn)為 大端表示法 。(struct sockaddr *)
這個(gè)強(qiáng)制轉(zhuǎn)換是為了參數(shù)的必須,但不會(huì)出錯(cuò),因?yàn)?sizeof(struct sockaddr_in) == sizeof(struct sockaddr)
具體可以查詢相關(guān)信息,之所以這么做是為了方便編寫套接字程序的程序員。發(fā)送端:
int sock;
const char* mess = "Hello Server!";
char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
struct sockaddr_in recv_host;
socklen_t addr_len;
/* 創(chuàng)建套接字 */
sock = socket(PF_INET, SOCK_DGRAM, 0);
/* 綁定 */
memset(&recv_host, 0, sizeof(recv_host));
recv_host.sin_family = AF_INET;
recv_host.sin_addr.s_addr = inet_addr("127.0.0.1");
recv_host.sin_port = htons(6000);
/* 發(fā)送信息 */
/* 在此處,發(fā)送端的IP地址和端口號(hào)等各類信息,隨著這個(gè)函數(shù)的調(diào)用,自動(dòng)綁定在了套接字上 */
sendto(sock, mess, strlen(mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host));
/* 完成,關(guān)閉 */
close(sock);
上述代碼是發(fā)送端。
inet_addr
函數(shù)是用于將字符串格式的 IP地址 轉(zhuǎn)換為 大端表示法的 地址類型,即 s_addr
的類型 in_addr_t
inet_ntoa
用于將 in_addr_t
類型轉(zhuǎn)為字符串,但是使用時(shí)一定要記住及時(shí)拷貝返回值
char addr[16];
recv_host.sin_addr.s_addr = inet_addr("127.0.0.1");
strcpy(addr, inet_ntoa(recv_host.sin_addr.s_addr));UDP
協(xié)議的使用十分簡(jiǎn)潔,幾乎就是 創(chuàng)建套接字->準(zhǔn)備數(shù)據(jù)->裝備套接字->發(fā)送/接收->結(jié)束UDP
隨時(shí)和 不同的主機(jī) 進(jìn)行通信所默認(rèn)的設(shè)置,如果需要和相同主機(jī)一直通信呢?UDP
和同一主機(jī)通信時(shí),可以使用 connect
函數(shù)來進(jìn)行優(yōu)化自身。此時(shí) 假設(shè)兩臺(tái)主機(jī)的實(shí)際功能一致,既接收也發(fā)送發(fā)送端:
/* 前方高度一致,將 bind函數(shù)替換為 */
connect(sock, (struct sockaddr *)&recv_host, sizeof(recv_host); // 將對(duì)方的 IP地址和 端口號(hào)信息 注冊(cè)進(jìn)UDP的套接字中)
while(1) /* 循環(huán)的發(fā)送和接收信息 */
{
size_t read_len = 0;
/* 原先使用的 sendto 函數(shù),先擇改為使用 write 函數(shù), Windows平臺(tái)為 send 函數(shù) */
write(sock, mess, strlen(mess)); /* send(sock, mess, strlen(mess), 0) FOR Windows Platform */
read_len = read(sock, get_mess, GET_MAX-1); /* recv(sock, mess, strlen(mess)-1, 0) FOR Windows Platform */
get_mess[read_len-1] = '\0';
printf("In Client like Host Recvive From Other Host : %s\n", get_mess);
}
/* 后方高度一致 */
接收端:
/* 前方一致, 添加額外的 struct sockaddr_in send_host; 并添加循環(huán),構(gòu)造收發(fā)的現(xiàn)象*/
while(1)
{
size_t read_len = 0;
char sent_mess[15] = "Hello Sender!"; /* 用于發(fā)送的信息 */
sendto(sock, mess, strlen(sent_mess), 0, (struct sockaddr *)&recv_host, sizeof(recv_host));
read_len = recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len)
mess[read_len-1] = '\0';
printf("In Sever like Host Recvive From other Host : %s\n", mess);
}
/* 后方高度一致 */
/*
* 之所以只在接收端使用 connect 的原因,便在于我們模擬的是 客戶端-服務(wù)器 的模型,而服務(wù)器的各項(xiàng)信息是不會(huì)隨意變更的
* 但是 客戶端就不同了,可能由于 ISP(Internet Server Provider) 的原因,你的IP地址不可能總是固定的,所以只能
* 保證 在客戶端 部分注冊(cè)了 服務(wù)器 的各類信息,而不能在 服務(wù)器端 注冊(cè) 客戶端 的信息。
* 當(dāng)然也有例外,例如你就想這個(gè)軟件作為私密軟件,僅供兩個(gè)人使用, 且你有固定的 IP地址,那么你可以兩邊都connect,但是
* 一定要注意,只要有一點(diǎn)信息變動(dòng),這個(gè)軟件就可能無法正常的收發(fā)信息了。
*/
UDP
似乎并沒有 connect
的使用必要,但是實(shí)際上還是有用到的地方。*nix
的 API 來說,sendto
和 write
的區(qū)別十分明顯,便是一個(gè)需要在參數(shù)中提供目標(biāo)主機(jī)的各類信息,而后者則不需要提供。同樣的道理recvfrom
和read
也是如此。以上是 UDP 的一些簡(jiǎn)單說明,入門足矣,并未詳細(xì)敘述某些 函數(shù) 的具體用法,而是用實(shí)際例子來體現(xiàn)。 在 記錄 TCP 之前,還是需要講一個(gè)函數(shù) shutdown
shutdown
與 close(closesocket)
close(sock); // closesocket(sock); FOR Windows PlatForm
就是這么干的,同時(shí)斷開兩個(gè)方向的連接。發(fā)送端:
/* 假設(shè)有一個(gè) TCP 的連接,此為客戶端 */
write(sock, "Thank you", 10);
close(sock); // 寫完直接關(guān)閉通信
接收端:
/* 此為服務(wù)器 */
/* 首先關(guān)閉寫流 */
shutdown(sock_c, SHUT_WR);
read(sock_c, get_mess, GET_MAX);
printf("Message : %s\n", get_mess);
close(sock_c);
close(sock_s); // 關(guān)閉兩個(gè)套接字是因?yàn)?TCP 服務(wù)器端的需要,后續(xù)會(huì)記錄
shutdown
函數(shù)的作用就是 可選擇的關(guān)閉那個(gè)方向的輸出
int shutdown(int sock, int howto);
sock
代表要操作的套接字howto
有幾個(gè)選擇
SHUT_RD
SHUT_WR
SHUT_RDWR
SD_RECEIVE
SD_SEND
SD_BOTH