wordpress 支持html5/網(wǎng)站標(biāo)題算關(guān)鍵詞優(yōu)化嗎
Linux下套接字TCP實現(xiàn)網(wǎng)絡(luò)通信
文章目錄
- Linux下套接字TCP實現(xiàn)網(wǎng)絡(luò)通信
- 1.引言
- 2.具體實現(xiàn)
- 2.1接口介紹
- 1.socket()
- 2.bind()
- 3.listen()
- 4.accept()
- 5.connect()
- 2.2 服務(wù)器端server.hpp
- 2.3服務(wù)端server.cc
- 2.4客戶端client.cc
1.引言
? 套接字(Socket)是計算機網(wǎng)絡(luò)中實現(xiàn)網(wǎng)絡(luò)通信的一種編程接口。它提供了應(yīng)用程序與網(wǎng)絡(luò)通信之間的一座橋梁,因為它允許應(yīng)用程序通過網(wǎng)絡(luò)發(fā)送和接收相應(yīng)的數(shù)據(jù)以實現(xiàn)不同主機之間的通信。
通常套接字由以下兩部分組成:
1.網(wǎng)絡(luò)IP和端口號:IP用來標(biāo)識主機,而端口號可以標(biāo)識到單臺主機的唯一進程。
2.通信協(xié)議:套接字通過規(guī)定通信協(xié)議來制定數(shù)據(jù)傳輸和發(fā)送的規(guī)則。常見的有TCP和UDP等協(xié)議。
TCP是一種面向連接的協(xié)議,提供可靠的、有序的、基于字節(jié)流的數(shù)據(jù)傳輸。
UDP是一種無連接的協(xié)議,提供不可靠的、無序的、基于數(shù)據(jù)報的數(shù)據(jù)傳輸。
? 今天我們來學(xué)習(xí)TCP實現(xiàn)網(wǎng)絡(luò)通信。TCP由于能提供可靠、基于字節(jié)流的數(shù)據(jù)傳輸,使用率與使用場景也比UDP多很多。
我們來看看能實現(xiàn)出什么樣的結(jié)果(聊天室模擬兩個用戶隨機通信):
若不開啟服務(wù)端就只開啟客戶端的話,那么就會像打游戲的某些情況連不上:
當(dāng)服務(wù)端和客戶端都開啟后就可以正常通信了:
這里我們還是通過客戶端給服務(wù)器端發(fā)送消息,通過TCP鏈接實現(xiàn)通信。
那么事不宜遲,我們馬上開始分享實現(xiàn)過程吧!
2.具體實現(xiàn)
2.1接口介紹
1.socket()
? socket函數(shù)是用于創(chuàng)建套接字的函數(shù),創(chuàng)建成功返回文件描述符fd,失敗返回-1;
int socket(int domain, int type, int protocol);
? 參數(shù)說明:
-
domain
:指定套接字的地址族(Address Family)
今天我們選擇:
AF_INET
:IPv4 地址族
-
type
:指定套接字的類型(Socket Type)
今天我們選擇:
SOCK_STREAM
:有連接的字節(jié)流套接字,用于TCP協(xié)議
-
protocol
:可選參數(shù),指定具體的傳輸協(xié)議。常用的有:
? 今天我們選擇:
0
:自動選擇合適的協(xié)議
2.bind()
? 在Linux下,bind()
函數(shù)用于將一個套接字(socket)與特定的IP地址和端口號進行綁定。
*int bind(int sockfd, const struct sockaddr addr,socklen_t addrlen);
參數(shù)說明:
sockfd
:要進行綁定的套接字的文件描述符。addr
:指向一個struct sockaddr
結(jié)構(gòu)體的指針,其中包含要綁定的IP地址和端口號信息。addrlen
:addr
結(jié)構(gòu)體的長度。
在綁定bind的第二個參數(shù)中,我們也需要用到庫中定義好的sockaddr_in結(jié)構(gòu)體來初始化!
具體結(jié)構(gòu)體struct sockaddr_in說明:
結(jié)構(gòu)體中有三個值也需要初始化指定一下:
sin_family
:表示地址族(Address Family),一般為AF_INET
。
sin_port
:表示端口號。它是一個 16 位的整數(shù),使用網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)表示。在使用時,通常需要使用htons()
函數(shù)將主機字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。
sin_addr
:表示 IPv4 地址。它是一個struct in_addr
類型的結(jié)構(gòu)體,用于存儲 32 位的 IPv4 地址。一般服務(wù)端用INADDR_ANY,讓udp_server在啟動時候可以綁定任何ip.
? 客戶端用inet_addr函數(shù)將字符串轉(zhuǎn)化成32位無符號整數(shù)
3.listen()
? listen()函數(shù):
將套接字設(shè)置為監(jiān)聽狀態(tài),等待連接請求。
參數(shù):
- sockfd:套接字的文件描述符。這里我們選擇前面socket創(chuàng)建好的返回值.
- backlog:指定等待連接隊列的最大長度。一般不會太大,我們這里寫32即可。
4.accept()
accept()
函數(shù):接受客戶端的連接請求,創(chuàng)建一個新的套接字用于與客戶端進行通信。
參數(shù):
- sockfd:套接字的文件描述符。這里我們選擇前面socket創(chuàng)建好的返回值.
- addr:指向客戶端地址的結(jié)構(gòu)體指針。創(chuàng)建一個sockaddr的結(jié)構(gòu)體強轉(zhuǎn)一下(struct sockaddr*)即可。
- addrlen:客戶端地址結(jié)構(gòu)體的字節(jié)大小。創(chuàng)建一個socklen_t類型的值用來計算結(jié)構(gòu)體大小。
5.connect()
connect()
函數(shù):發(fā)起與遠(yuǎn)程主機建立TCP連接的請求。
參數(shù):
- sockfd:套接字的文件描述符。這里我們選擇前面socket創(chuàng)建好的返回值.
- addr:指向遠(yuǎn)程主機地址的結(jié)構(gòu)體指針。創(chuàng)建一個sockaddr的結(jié)構(gòu)體強轉(zhuǎn)一下(struct sockaddr*)即可。
- addrlen:遠(yuǎn)程主機地址結(jié)構(gòu)體的字節(jié)大小。創(chuàng)建一個socklen_t類型的值用來計算結(jié)構(gòu)體大小。
2.2 服務(wù)器端server.hpp
在整個服務(wù)器端server.hpp中,我們需要創(chuàng)建tcpServer類,并在類中建立這些成員:監(jiān)聽套接字、端口號等.
在類中我們還需要初始化服務(wù)器InitServer()和啟動服務(wù)器Start()兩個接口。
服務(wù)器端具體實現(xiàn)思路是:我們創(chuàng)建套接字socket()后開始綁定bind(),之后監(jiān)聽listen(),監(jiān)聽成功后我們獲取鏈接accept()即可。
思路簡單,但是實現(xiàn)還需要很多事完成:
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;class tcpServer
{public:tcpServer(func_t func,uint16_t port = defaultport):_func(func),_port(port),_quit(true){}~tcpServer() {}void InitServer(){//1.創(chuàng)建套接字_listensock = socket(AF_INET,SOCK_STREAM,0);if(_listensock < 0){std::cerr << "create socket error" << std::endl;exit(-1);}//2.綁定struct sockaddr_in local;memset(&local,0,sizeof(local)); //清空結(jié)構(gòu)體local.sin_family = AF_INET;local.sin_port = htons(_port); //主機轉(zhuǎn)網(wǎng)絡(luò)local.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(_listensock,(struct sockaddr*)&local,sizeof(local)) < 0){std::cerr << "bind error" << std::endl;exit(-2);}//3.監(jiān)聽(tcp)if(listen(_listensock,backlog) < 0){std::cerr <<" listen error" << std::endl;exit(-3);}}void Start(){_quit = false; //運行時設(shè)置位運行狀態(tài),即不退出的狀態(tài)while(!_quit) //服務(wù)器死循環(huán){struct sockaddr_in client;socklen_t len = sizeof(client);//4.獲取鏈接acceptint sock = accept(_listensock,(struct sockaddr*)&client,&len);if(sock < 0) {std::cerr <<"accept error " <<std::endl;continue;} //攬客的sock失敗后繼續(xù)即可//5.獲取鏈接成功std::cout<< "獲取鏈接成功" << sock << " from " << _listensock << std::endl;service(sock);}}void service(int sock) //服務(wù){(diào)char buffer[1024];while(true){ssize_t s = read(sock,buffer,sizeof(buffer)-1);if(s > 0) //代表成功讀取{buffer[s] = 0;std::string res = _func(buffer); //回調(diào)顯示std::cout<< res <<std::endl;write(sock,res.c_str(),res.size());}else if(s == 0) //代表讀到文件結(jié)尾 在網(wǎng)絡(luò)中就相當(dāng)于對方關(guān)閉鏈接{close(sock);std::cout << "quit" <<std::endl;break;}else //文件讀取失敗{close(sock);std::cerr << " read error" <<std::endl;break;}}}private:uint16_t _port; //端口號int _listensock; //監(jiān)聽套接字bool _quit; //代表服務(wù)器沒有運行的狀態(tài)func_t _func; //回調(diào)包裝器,為了后面輸出后回顯
};
2.3服務(wù)端server.cc
在服務(wù)端的主文件中,我們直接包含上面的頭文件。
我們期望的用法是:./tcp_server port,代表運行可執(zhí)行文件后面需要帶一個參數(shù):端口號
所以我們能夠從用戶中輸入的port,通過main函數(shù)中的**char* argv[]**參數(shù)列表中獲取到。并傳給tcpSercer類中初始化與啟動服務(wù)器即可。
#include "server.hpp"
#include<memory>
using namespace std;static void usage(string proc) //使用手冊,代表運行可執(zhí)行文件后面需要帶一個參數(shù):端口號
{std::cout << "Usage:\n\t" << proc << "port\n" <<std::endl;}std::string echo(const std::string& message)//輸出回顯
{return message;
}//期望用法:./tcp_server port
int main(int argc,char* argv[])
{if(argc != 2) //輸入的不是兩個參數(shù),說明你不會用。輸出使用手冊{usage(argv[0]);exit(-1);}uint16_t port = atoi(argv[1]); //強轉(zhuǎn)成能夠使用的類型unique_ptr<tcpServer> ts(new tcpServer(echo,port));//采用智能指針創(chuàng)建釋放資源ts->InitServer();ts->Start();return 0;
}
2.4客戶端client.cc
在客戶端中我們conncet嘗試鏈接到服務(wù)器端,這里需要做一個重連反饋:正在嘗試重連…
我們期望運行格式:./client serverip serverport,代表運行可執(zhí)行文件后需要兩個參數(shù):IP和端口
我們從用戶輸入的兩個參數(shù)中傳給main,并通過main參數(shù)char* argv[]參數(shù)列表獲取到,之后獲取到直接轉(zhuǎn)化即可。
static void usage(string proc)
{std::cout << "Usage:\n\t" << proc << "serverip serverport\n" <<std::endl;}
//期望使用:./client serverip serverport
int main(int argc,char* argv[])
{if(argc != 3){usage(argv[0]);exit(-2);}string serverip = argv[1]; //獲取到參數(shù)uint16_t port = atoi(argv[2]);//1.創(chuàng)捷套接字int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){std::cerr << " socket error" <<std::endl;exit(-1);}//2.客戶端需要鏈接服務(wù)器 --connectstruct sockaddr_in server; //memset(&server,0,sizeof(server)); //清空結(jié)構(gòu)體server.sin_family = AF_INET;//初始化結(jié)構(gòu)體server.sin_port = htons(port);//server.sin_addr.s_addr = inet_addr(serverip.c_str()); //客戶端inet_aton(serverip.c_str(),&(server.sin_addr));int cnt = 5;while(connect(sock,(struct sockaddr*)&server,sizeof(server)) != 0) //如果綁定失敗{sleep(1);std::cout<<"正在嘗試重連... 重連次數(shù):" <<cnt-- <<std::endl;if(cnt <= 0) break;} if(cnt <= 0){cerr<< "服務(wù)器連接失敗"<<endl;exit(-1);}//3.連接成功while(true) //連接成功后從客戶端直接輸入發(fā)送數(shù)據(jù){string line;char buffer[1024];cout<<"Enter>> "; getline(cin,line);write(sock,line.c_str(),line.size()); //給緩沖區(qū)寫數(shù)據(jù)ssize_t s = read(sock,buffer,sizeof(buffer) -1);if(s > 0)//正常寫{buffer[s] = 0;cout<< " server rcho >>>" <<buffer <<endl;}else if(s == 0) //寫結(jié)束{cerr << "server quit" <<endl;break;}else{ //異常cerr<< " read error " <<endl;break;}}close(sock);//關(guān)閉套接字,管不管都可以return 0;
}
最后運行之后就能獲得我們之前通信的結(jié)果了: