引言
TCP/IP
指傳輸控制協(xié)議/網(wǎng)際協(xié)議(Transmission Control Protocol / Internet Protocol)
。[1]
在TCP/IP協(xié)議簇中主要包含以下內(nèi)容:
- TCP (傳輸控制協(xié)議) - 應(yīng)用程序之間通信
- UDP (用戶數(shù)據(jù)報(bào)協(xié)議) - 應(yīng)用程序之間的簡單通信
- IP (網(wǎng)際協(xié)議) - 計(jì)算機(jī)之間的通信
- ICMP (因特網(wǎng)消息控制協(xié)議) - 針對(duì)錯(cuò)誤和狀態(tài)
- DHCP (動(dòng)態(tài)主機(jī)配置協(xié)議) - 針對(duì)動(dòng)態(tài)尋址
TCP/IP 定義了電子設(shè)備(比如計(jì)算機(jī))如何連入因特網(wǎng),以及數(shù)據(jù)如何在它們之間傳輸?shù)臉?biāo)準(zhǔn)。
1、TCP
TCP 用于應(yīng)用程序之間的通信。
TCP使用固定的連接,會(huì)建立一個(gè)全雙工的通信。
會(huì)占用兩個(gè)計(jì)算機(jī)之間通信線路,直到被一方或者雙方關(guān)閉。
2、IP
IP是計(jì)算機(jī)之間的通信。
IP是無連接的通信協(xié)議,不會(huì)占用兩個(gè)正在通信計(jì)算機(jī)之間的通信線路。
因此IP降低對(duì)網(wǎng)絡(luò)線路的需求,每條線可以同時(shí)滿足不同計(jì)算機(jī)之間的通信需要。
2.1 IP路由器
IP包從一臺(tái)計(jì)算機(jī)被發(fā)送,它會(huì)到達(dá)一個(gè)IP路由器。
IP路由器會(huì)路由這個(gè)IP包到它的目的地,直接地或者通過其它路由器。
相同通信,一個(gè)包經(jīng)過的路徑可能和其它包不同,而路由器會(huì)根據(jù)通信量
、網(wǎng)絡(luò)中錯(cuò)誤
或其它參數(shù)
進(jìn)行正確尋址。
3、TCP/IP
TCP/IP是不同通信協(xié)議的大集合。
[1]
TCP(應(yīng)用程序與應(yīng)用程序之間建立的全雙工通信協(xié)議),IP(計(jì)算機(jī)與計(jì)算機(jī)之間建立的通信協(xié)議)。
- TCP負(fù)責(zé)將數(shù)據(jù)分割裝入IP包,然后在到達(dá)的時(shí)候重新組合它們。
- IP則在中間負(fù)責(zé)將包發(fā)送至接受者。
如下圖表示:
IP地址:計(jì)算機(jī)的“門牌”號(hào),有了IP地址才可以接入因特網(wǎng)。IP包就像快遞,得知道IP地址才能發(fā)送到對(duì)應(yīng)的計(jì)算機(jī)。
TCP/IP使用4組數(shù)字為計(jì)算機(jī)編址,每個(gè)計(jì)算機(jī)有唯一的4組數(shù)字地址。每組數(shù)字必須在0~255之間
,并用點(diǎn)號(hào)分割開,這里使用的是ipv4協(xié)議
。
為什么TCP/IP每個(gè)地址是使用0~255之間數(shù)字?
因?yàn)橐?guī)定TCP/IP協(xié)議使用32個(gè)bit編址,在計(jì)算機(jī)中 8bit=1byte=1B,所以使用四個(gè)字節(jié)來編址。
然后8bit = 8 位,因此每組地址的范圍就是
0000 0000 ~ 1111 1111,范圍是0 ~ 2^8 - 1,也就是0 ~ 255。
TCP/IP簡化了OSI的七層模型為四層模型。
- 優(yōu)化結(jié)構(gòu)。
- 每層獨(dú)立但又因?yàn)樯蠈訁f(xié)議使用下層協(xié)議服務(wù),之間又存在聯(lián)系。
4、TCP/IP協(xié)議C++11實(shí)現(xiàn)
Linux下網(wǎng)絡(luò)編程主要分為四個(gè)步驟[2]。
- 1、調(diào)用 socket 函數(shù)創(chuàng)建套接字。
- 2 、調(diào)用 bind 函數(shù)分配IP地址和端口號(hào)。
- 3、調(diào)用 listen 函數(shù)轉(zhuǎn)換為可接受請(qǐng)求狀態(tài)。
- 4、調(diào)用 accept 函數(shù)受理套接字請(qǐng)求。
套接字
是通信中兩個(gè)網(wǎng)路應(yīng)用程序進(jìn)行通信時(shí),各自連接中的端點(diǎn)[3]。是通信的基石。
套接字socket表示方法是(ip地址:端口號(hào))或(ip地址,端口號(hào))。每一個(gè)傳輸層連接唯一地被通信兩端的兩個(gè)端點(diǎn)(即兩個(gè)套接字)所確定。例如:如果IP地址是210.37.145.1,而端口號(hào)是23,那么得到套接字就是(210.37.145.1:23)[4]
上面引用[2]里描述的整個(gè)流程是這樣的,比喻為打電話。
首先需要安裝電話機(jī),因此使用套接字socket,然后分配端口號(hào),也就是電話號(hào)碼,使用bind函數(shù)給創(chuàng)建號(hào)的套接字分配ip地址和端口信息。有了電話和電話號(hào)碼之后,需要架設(shè)電話線,使用listen函數(shù)讓這臺(tái)電話可用,這時(shí)其他人可以打電話到這臺(tái)電話機(jī),電話機(jī)響的時(shí)候需要接聽,最后使用accept函數(shù)來保持監(jiān)聽。
那么我們就簡單來實(shí)現(xiàn)一個(gè)hello world在windows下的傳輸。
首先我們需要建立一個(gè)服務(wù)端項(xiàng)目,它能夠在收到信息時(shí)進(jìn)行回傳hello world。
- 服務(wù)端的職能:
建立socket
,聲明自身的端口號(hào)和地址并綁定到socket,使用listen
打開監(jiān)聽,然后不斷用accept
去查看是否有連接,如果有,捕獲socket,并通過recv獲取消息
的內(nèi)容,通信完成后調(diào)用closeSocket關(guān)閉這個(gè)對(duì)應(yīng)accept到的socket
,如果不再需要等待任何客戶端連接,那么用closeSocket關(guān)閉掉自身的socket。 - 客戶端職能:建立socket套接字,通過ip地址和端口號(hào)確定目標(biāo)服務(wù)器。使用
connect
連接服務(wù)器,使用send
函數(shù)發(fā)送消息,等待服務(wù)器處理,通信完成后調(diào)用closeSocket
關(guān)閉socket。
參考這位朋友的博客[5]實(shí)現(xiàn)了基本的服務(wù)端架設(shè),服務(wù)端代碼和注釋轉(zhuǎn)載于(初學(xué)者的福音)windows下實(shí)現(xiàn)socket通信(TCP/IP)代碼詳解——服務(wù)端篇。
/*****************************************************************************************************************************
* 1、加載套接字庫,創(chuàng)建套接字(WSAStartup()/socket());
* 2、綁定套接字到一個(gè)IP地址和一個(gè)端口上(bind());
* 3、將套接字設(shè)置為監(jiān)聽模式等待連接請(qǐng)求;
* 4、請(qǐng)求到來之后,接受連接請(qǐng)求,返回一個(gè)新的對(duì)應(yīng)于此次連接的套接字(accept());
* 5、用返回的套接字和客戶端進(jìn)行通信(send()/recv());
* 6、返回,等待另一個(gè)連接請(qǐng)求
* 7、關(guān)閉套接字,關(guān)閉加載的套接字庫(closesocket()/WSACleanup());
*****************************************************************************************************************************/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main()
{
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;//WSADATA結(jié)構(gòu)體變量的地址值
//int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
//成功時(shí)會(huì)返回0,失敗時(shí)返回非零的錯(cuò)誤代碼值
if (WSAStartup(sockVersion, &wsaData) != 0)
{
cout << "WSAStartup() error!" << endl;
return 0;
}
//創(chuàng)建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
cout << "socket error !" << endl;
return 0;
}
//綁定IP和端口
sockaddr_in sin;//ipv4的指定方法是使用struct sockaddr_in類型的變量
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);//設(shè)置端口。htons將主機(jī)的unsigned short int轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序
sin.sin_addr.S_un.S_addr = INADDR_ANY;//IP地址設(shè)置成INADDR_ANY,讓系統(tǒng)自動(dòng)獲取本機(jī)的IP地址
//bind函數(shù)把一個(gè)地址族中的特定地址賦給scket。
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//開始監(jiān)聽
if (listen(slisten, 5) == SOCKET_ERROR)
{
cout << "listen error !" << endl;
return -1;
}
//循環(huán)接收數(shù)據(jù)
SOCKET sclient;
sockaddr_in remoteAddr;//sockaddr_in常用于socket定義和賦值,sockaddr用于函數(shù)參數(shù)
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
cout << "等待連接。。。" << endl;
sclient = accept(slisten, (sockaddr*)&remoteAddr, &nAddrlen);
if (sclient == INVALID_SOCKET)
{
cout << "accept error !" << endl;
continue;
}
cout << "接收到一個(gè)連接:" << inet_ntoa(remoteAddr.sin_addr) << endl;
//接收數(shù)據(jù)
int ret = recv(sclient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = 0x00;
cout << revData << endl;
}
//發(fā)送數(shù)據(jù)
const char* sendData = "你好,TCP客戶端!\n";
send(sclient, sendData, strlen(sendData), 0);
closesocket(sclient);
}
closesocket(slisten);
WSACleanup();
system("pause");
//return 0;
}
客戶端實(shí)現(xiàn),客戶端代碼來源于windows環(huán)境下用c++實(shí)現(xiàn)socket編程:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WINSOCK2.H>
#include<STDIO.H>
#include<iostream>
#include<cstring>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
while (true) {
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ //連接失敗
printf("connect error !");
closesocket(sclient);
return 0;
}
string data;
cin >> data;
const char* sendData;
sendData = data.c_str(); //string轉(zhuǎn)const char*
//char * sendData = "你好,TCP服務(wù)端,我是客戶端\n";
send(sclient, sendData, strlen(sendData), 0);
//send()用來將數(shù)據(jù)由指定的socket傳給對(duì)方主機(jī)
//int send(int s, const void * msg, int len, unsigned int flags)
//s為已建立好連接的socket,msg指向數(shù)據(jù)內(nèi)容,len則為數(shù)據(jù)長度,參數(shù)flags一般設(shè)0
//成功則返回實(shí)際傳送出去的字符數(shù),失敗返回-1,錯(cuò)誤原因存于error
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0) {
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
}
WSACleanup();
return 0;
}
先運(yùn)行服務(wù)端,再運(yùn)行客戶端,然后輸入內(nèi)容,便可以通過TCP/IP傳輸。
最終結(jié)果:
后續(xù)解析待更新。。。文章來源:http://www.zghlxwxcb.cn/news/detail-781817.html
參考文獻(xiàn)
[4] 潘偉編著,計(jì)算機(jī)網(wǎng)絡(luò) 理論與實(shí)驗(yàn),廈門大學(xué)出版社,2013.12,第145頁文章來源地址http://www.zghlxwxcb.cn/news/detail-781817.html
到了這里,關(guān)于C++11實(shí)現(xiàn)計(jì)算機(jī)網(wǎng)絡(luò)中的TCP/IP連接(Windows端)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!