簡介:網絡分爲5層,軟體開發需要用到的主要是上面兩層:網絡層,和應用層;路由器系統開發的估計需要用到傳輸層;所以如果研究網絡的話,我們需要深入瞭解的是網絡層和應用層;
關鍵字:網絡層,應用層;
關鍵字解釋:網絡傳輸,可以想象成是快遞包裹一樣,快遞有運輸車(車裏有快遞箱,箱子裏面有快遞包裹,包裹表面有寄件單資訊,收件人根據這個來收到),這一層層的裹起來,就是不同層的職責所在,并非是爲了難爲我們這些學習網絡的人;那麽,接下來,解釋一下關鍵字:網絡層是tcp/ip包裹的那一層,我們把原來的要發的data看作是物品的話,那麽網絡層就是寄件單一樣的東西,可以標志誰寄的,誰來收;而應用層是什麽呢?應用層是爲了簡化或者說模式化網絡層而出現的,更加方便包裹的這一層;
實戰舉例:我做的項目都是服務業自動化系統,這裏會涉及到的最多的是socket,而通常socket編程就是針對網絡層的,也就是通訊協議雙方,制定protocol,這個protocol的内容是一些十六進制的編碼,比如:
也就是在data的前面加上一些協議頭,不同的頭表示data所屬的内容是什麽,這樣拆包的那方就知道用什麽方式拆包了,比如我發了一個int過去,協議中説好了,這個是一個4byte的int,那麽對方就可以用int_32來把這個data轉成4byte的int了,就拿到數字了;深挖一下:網絡中的包都是二進制的,所以我們用十六進制來制定協議,因爲拆包的時候1個byte就是1個char;下面逐步講解如何發送socket:
關鍵字:socket,server,client,listen,connect;
建server:首先需要建立一個監聽用的server,server需要的是兩個ip+port,ip就是我們的網絡地址(分爲公網和局域網,往往我們看到的ip是局域網的,由路由器自動分配的,公網ip可以去google直接查,内部通訊可以用局域網ip,但是對外通訊需要用公網ip,一般與其他廠商通訊,都需要公網ip),port是監聽端口,這個是因爲我們電腦中有很多程序,到底哪個程序來處理從外部來的data呢?這個就出現了每個程序可以監聽一個或多個端口(port),端口從1到幾萬都可以,但是低位的(幾十幾百的)往往都是公約端口,這些已經被占了,請不要使用,也就是說,如果我自己寫一個程序來監聽某個端口的話,最好找個端口值設高一些,比如幾千幾萬等等;設定好之後,就監聽這個ip+port就好了;
建一個或者多個client:client端,直接connect到 server端;
收發訊息:read 和 write;一般實際處理的時候,是需要多綫程處理的,因爲主程序還是在跑,儅有資訊來的時候,就拆包,儅需要發資訊的時候,就封包;
下面看一下代碼(c++的,linux系統,vscode,cmake跑的):
server:
打開監聽端口:
void CallCarServer::Init()
{
int listenfd;//萬物皆文件,請不要懷疑,所謂的監聽端口,就是一個文件標識符(最原始的就是個int喲)
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
{
ERR_EXIT("ERROR");
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(CAR_DRIVING_SERVER_NETWORK_TARGET_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
{
ERR_EXIT("SETSOCKOPT");
}
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
ERR_EXIT("ERROR");
}
if (listen(listenfd, SOMAXCONN) < 0)
{
ERR_EXIT("LISTEN");
}
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
char sendbuf[256];
while (true)
{
conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen);
if (conn < 0)
{
ERR_EXIT("accept");
}
printf("ip = %s, port = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
conn_ = conn;
std::thread t_receive(&CallCarServer::thread_accept, this);
t_receive.detach();
}
close(listenfd);
}
多執行緒處理數據:
void CallCarServer::thread_accept()
{
char recvbuf[BUFF_SIZE];
while (true)
{
sleep(1);
memset(recvbuf, 0, sizeof(recvbuf));
int num = read(conn_, recvbuf, sizeof(recvbuf));
if (num <= 0)
{
continue;
}
printf("message length is %d\n", num);
//take the length of message
//int len = Lenth_Message(recvbuf);
//len = num-4;
char *message = new char(num - 4);
for (int i = 0; i < len; i++)
{
message[i] = recvbuf[i + 4];
}
// DealMessage(message);
ReceiveBuf *recvbuf = reinterpret_cast<ReceiveBuf *>(message);
std::cout<< recvbuf->message.iCarID<<std::endl;
delete (message);
//SendCarGo(conn);
}
close(conn_);
}
client端:
int sock;
if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
{
ERR_EXIT("ERROR");
}
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(2978);
servaddr.sin_addr.s_addr = inet_addr("192.168.200.243");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
ERR_EXIT("connect");
}
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
write(sock,sendbuf,strlen(sendbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
close(sock);
上面是基本知識,實戰中,花更多時間的是制定protocol,下篇再講解如何制定protocol;
以及可能大部分人使用的是應用層的network program:比如mqtt,http post等;