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