準備工作
網絡字節序
由於每一臺主機的大小端都有可能是不一樣的,我們在主機內部進行通信的時候,不會有大小端不同的問題,而在進行跨主機進行通信的時候,就會出現兩臺主機的字節序不同,爲了統一標準,在網絡中通信的數據,統一採用大端的存儲方式。如果機器是大端,在發送的時候,不做任何修改,如果主機是小端的字節序,則在發送的時候,需要先將數據轉化爲大端,然後再發送。
爲了便於我們直接通過調用一些已經寫好的接口直接能完成網絡字節序的裝換,系統爲我們提供了以下的一些接口
uint32_t htonl(uint32_t hostlong); //主機轉網絡
uint16_t htons(uint16_t hostshort); //主機轉網絡
uint32_t ntohl(uint32_t hostlong); //網絡轉主機
uint16_t ntohs(uint16_t hostshort); //網絡轉主機
以上的,就是網絡字節序的轉化
IP + PORT
在網絡通信的過程中,IP唯一標識網絡中的一臺主機,而PORT,也就是端口號,唯一標識一臺主機上的唯一的一個進行網絡通信的進程。通過IP尋找主機,再通過端口號,尋找該主機上的一個進程,就保證了傳輸的準確性。
複雜的網絡環境,其實可以看做是一臺超級巨大的主機,只不過這臺主機進行進程間通信的時候,是通過網絡環境進行的。
socket的一些接口以及這些接口中數據類型的詳細信息
常見的API接口
int socket(int domain,int type,int protocol);
//這個接口的作用是創建一個socket文件描述符,然後將其返回
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//這個接口的作用是 將你先前創建的socket文件描述符與你後來創建的sockaddr_in類型的變量進行綁定
//(先將端口號和ip在sockaddr_in類型的變量中填充,再進行綁定),創建好網絡通信的環境。
// 該接口的作用就是,利用本地的一個socket類型的文件描述符,和一個sockaddr_in類型(裏面含有ip和端口號)的變量,
//實現對文件描述符的讀寫操作,等同於網絡間的收發信息。
UDP傳輸協議
UDP是什麼
UDP協議是網絡中兩個主機進行通信的時候,所採用的的一種非連接的通信協議,核心數非連接。(非連接的意思是:接受方與發送方並不建立連接,接收方不看給他發送信息的發送者是誰,只是一味地接收,發送方也不看接收方是誰,只是一味地發送) 。面向的是數據報。不可靠的一種傳輸方式。
如何進行搭建
1.文件描述符創建
剛纔說了,網絡間通信可以看做是進程間進行通信的一種特殊形式,既然是進程間通信,就必須有一個公共區域,也就是一個文件,對這個文件進行操作的話,就必須有文件描述符。我們第一步的操作就是,創建文件描述符,並檢驗他的正確性。
int sockfd = socket(AF_INET,SOCK_DGRAM,0);\
// 用socket調用,創建用於網絡間通信的文件描述符。
//socket函數的第一個參數是 通信協議類型 AF_INET是ipv4協議家族。
//第二個參數是通信方式,因爲這裏是UDP協議的通信方式,所以是SOCK_DGRAM,無連接的通信方式。
//最後一個參數是接收方通信協議地選擇,我們這裏用0,表示自動選擇。
if(sockfd < 0)
{
exit(2);
}
2.將文件描述符進行綁定
我們進行網絡間通信的最重要的是先要找到對方,而我們找到對方的方式就是通過IP+PORT進行尋找的,我們已經創建好了文件描述符,接下來就是要對文件描述符與IP+PORT進行綁定的過程
sockaddr_in 類型和sockaddr類型,在物理存儲上是一樣的,只是表示方式不同,我們在使用的時候,需要將sockaddr_in類型強制類型轉換爲sockaddr類型的。
bind函數如果成功,返回值就是0,如果不成功,就返回錯誤碼。
string ip;
short port;
struct sockaddr_in local;
bzeor(&local,sizeof(local));
//對結構體進行清空
local.sin_family = AF_INET;
//填充協議家族
local.sin_addr.s_addr = inet_addr(ip);
//填充IP地址,需要將字符串類型轉化爲十進制,
//用inet_addr接口,可以直接完成轉換
local.sin_port = htons(port);
//填充端口號,也是需要轉換,將short類型主機轉網絡,
//也就是開始說的網絡字節序的轉換。
if( bind(sockfd, (struct sockaddr*)&local, sizeof(local) ) == 0)
{
// true
// ...
}
else
{
// false
exit(3);
}
3.開始網絡間通信
網絡間通信,至少有兩個角色,一個是服務器,一個是客戶端。既然如此,就需要服務器和客戶端完成各自的任務。
服務器:服務器需要接收客戶端發來的請求,並作出迴應。
客戶端:想服務器提出請求,並接收服務器的返回
服務器框架(這裏只完成了服務器的接收操作,並沒有進行服務器的發送操作,服務器的發送,和客戶端的發送是一樣的,調用的是同一個接口,所以不做多的解釋)
char buf[1024];
struct sockaddr_in peer;
for(;;)
{
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
//recvfrom函數的參數,從左到右,依次是:接收的文件描述符,
//將接收到的數據放進哪個緩衝區,緩衝區最大能接受的大小(這裏留了一個自己,用來存貯 '\0' ),
//用來存儲發送方的網絡套接字,存儲發送方的網絡套機字的長度。
if(size > 0)
{
buf[size] = 0;
std::string client_ip = inet_ntoa(peer.sin_addr);
// 將接收來的網絡套接字的IP分離出來 ,
//將十進制裝換爲點分十進制的字符串
int client_port = ntohs(peer.sin_port);
// 將接收來的網絡套接字的PORT分離出來,網絡轉主機,
//類型是short類型
std::cout << "client ip #" << client_ip << " client port # " << client_port << std::endl;
std::cout << "client msg # :" << buf << std::endl;
}
else{
std::cout << "recvfrom error" << std::endl;
exit(4);
}
}
客戶端框架(客戶端也是隻有發送操作,接收操作參考服務器的接收操作)
std::string msg;
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(peer_ip.c_str());
server.sin_port = htons(peer_port);
while(1)
{
socklen_t len = sizeof(server);
std::cout << "請輸入信息 #" ;
std::cin >> msg;
ssize_t s = sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&server,len);
//發送接口,參數依次是: 發送到哪個文件,發送什麼,期望發送的大小,
//以什麼樣的方式進行發送(這裏0,表示阻塞式發送),接收方的套接字,
//接收方的套接字長度
}
總結代碼
以上,就是基本的接口的框架,如何將這些接口進行組合,並實現真正的網絡通信環境呢?我準備瞭如下的一些代碼,分爲四個部分,兩個服務器代碼文件,兩個客戶端代碼文件
這裏的代碼,在使用的時候,是通過命令函參數進行IP和PORT的傳入的
給server的是服務器啓動時,服務器的IP和PORT,用於服務器的初始化。client 啓動時,給的是服務器的IP和PORT,用於客戶端的通信目標。
傳輸方法
server.cpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<strings.h>
#include<string>
class server
{
private:
short port;
std::string ip;
int sockfd;
public:
server(std::string _ip = "127.0.0.1",short _port = 8080) : port(_port),ip(_ip)
{
}
void InitServer()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr<< "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip.c_str());
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) == 0)
{
std::cout << "run fun" << std::endl;
}
else{
std::cerr << "bind error" << std::endl;
exit(3);
}
}
void run()
{
char buf[1024] = {0};
struct sockaddr_in peer;
for(;;)
{
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(size > 0)
{
buf[size] = 0;
std::string client_ip = inet_ntoa(peer.sin_addr);
int client_port = ntohs(peer.sin_port);
std::cout << "client ip #" << client_ip << " client port # " << client_port << std::endl;
std::cout << "client msg # :" << buf << std::endl;
}
else{
std::cout << "recvfrom error" << std::endl;
exit(4);
}
}
}
~server()
{
if(sockfd >= 0)
close(sockfd);
}
};
server.cc
#include"server.hpp"
void usage()
{
std::cout << "請輸入ip + port" << std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage();
exit(2);
}
server *sp = new server(argv[1],atoi(argv[2]));
sp->InitServer();
sp->run();
delete sp;
return 0;
}
client.cpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<strings.h>
#include<string>
class client{
private:
short peer_port;
std::string peer_ip;
int sockfd;
public:
client(std::string _ip = "127.0.0.1",short _port = 8080):peer_ip(_ip) , peer_port(_port)
{}
void InitClient()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cout << "socket error" << std::endl;
exit(2);
}
}
void run()
{
std::string msg;
struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr(peer_ip.c_str());
server.sin_port = htons(peer_port);
while(1)
{
socklen_t len = sizeof(server);
std::cout << "請輸入信息 #" ;
std::cin >> msg;
ssize_t s = sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&server,len);
}
}
~client()
{
if(sockfd > 0)
{
close(sockfd);
}
}
};
client.cc
#include"client.hpp"
void usage()
{
std::cout << "請輸入正確的發送對象,格式爲 ip + port";
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage();
exit(3);
}
client* sp = new client(argv[1],atoi(argv[2]));
sp->InitClient();
sp->run();
delete sp;
return 0;
}