Linux手把手教你實現(xiàn)udp服務(wù)器的詳細(xì)過程
前言
上一篇文章中我們講到了很多的網(wǎng)絡(luò)名詞以及相關(guān)知識,下面我們就直接進(jìn)入udp服務(wù)器的實現(xiàn)。
一、udp服務(wù)器的實現(xiàn)
首先我們需要創(chuàng)建五個文件(文件名可以自己命名也可以和我一樣),分別是makefile,udpclient.cc,udpclient.hpp,udpserver.cc,udpserver.hpp,下面我們先進(jìn)行makefile的編寫,在makefile中我們要一次創(chuàng)建兩個可執(zhí)行程序:
cc=g++ .PHONY:all all:udpClient udpServer udpClient:udpClient.cc $(cc) -o $@ $^ -std=c++11 udpServer:udpServer.cc $(cc) -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f udpClient udpServer
我們通過all就可以創(chuàng)建多個可執(zhí)行程序了,對于cc這個變量我們設(shè)置為g++,以后如果想換其他的編譯器就可以直接替換了。
在udpserver.hpp這個文件中我們先寫出整體框架:
namespace Server { class udpServer { public: udpServer() { } void InitServer() { } void start() { } ~udpServer() { } private: //服務(wù)器一定要有自己的服務(wù)端口號(注意端口號是16位的) uint16_t _port; //端口號 //實際上一款服務(wù)器不建議指明一個IP string _ip; //ip }; }
那么我們現(xiàn)在服務(wù)器的ip填多少呢?實際上我們只是完成測試,所以ip就填0.0.0.0就好了,這樣的話任意的ip都能訪問我們的服務(wù)器,所以我們定義一個static變量來保存ip:
static const string defaultIp = "0.0.0.0";
有了ip和端口號后,我們就可以用構(gòu)造函數(shù)初始化了:
udpServer(const uint16_t& port,const string ip = defaultIp) :_port(port) ,_ip(ip) { }
我們的服務(wù)器未來要啟動的話就必須先初始化然后再啟動,所以我們寫了init和start接口,那么該如何初始化呢?實際上不管是udp還是tcp,我們初始化都是需要套接字的,下面我們看看套接字的接口:
如何理解套接字呢,我們都知道linux一切皆文件,所以未來的網(wǎng)絡(luò)通信一定是在同一個文件中只要和網(wǎng)卡設(shè)備關(guān)聯(lián)起來就實現(xiàn)了網(wǎng)絡(luò)通信,所以套接字的目的實際上是創(chuàng)建一個文件,可以看到我們的套接字有三個參數(shù),第一個參數(shù)的解釋是域,實際上就是讓我們選擇是進(jìn)行網(wǎng)絡(luò)通信還是本地通信,這里我們一般選擇AF_INET選項,代表使用IPV4協(xié)議的網(wǎng)絡(luò)通信。第二個參數(shù)是type,表面套接字要向我們提供服務(wù)的類型,怎么理解呢,如下圖:
我們現(xiàn)在所寫的UDP服務(wù)器的特點是不可靠傳輸無連接,而這正是與SOCK_DGRAM這個選項所匹配的,我們查看這個選項的解釋可以看到:DGRAM適用于不可靠傳輸,連接少
我們下一篇要實現(xiàn)的TCP服務(wù)器,就會用到SOCK_STREAM這個選項,因為這個選項的解釋是面向流式服務(wù),而我們TCP的特點就是面向字節(jié)流。
第三個參數(shù)我們一般缺省為0,因為這個參數(shù)代表我們未來要采用什么協(xié)議,如果我們寫為0,那么這個接口會根據(jù)我們填的前兩個參數(shù)來幫我們確定第三個參數(shù)是選擇TCP協(xié)議還是UDP協(xié)議。
這個接口的返回值相信大家也看到了,沒錯!一旦創(chuàng)建套接字成功,那么就會給我們返回一個文件描述符,如果失敗則會給我們返回-1并且提供錯誤碼。
了解了socket這個接口,那么我們下一步就是增加一個私有變量來接收socket返回的文件描述符(注意:這個文件描述符會被后面的接口多次用到):
然后我們在構(gòu)造函數(shù)中將這個文件描述符初始化為-1:
udpServer(const uint16_t& port,const string ip = defaultIp) :_port(port) ,_ip(ip) ,_sockfd(-1) { }
然后我們初始化第一步:使用套接字
void InitServer() { //UDP第一步:創(chuàng)建了一個套接字 _sockfd = socket(AF_INET,SOCK_DGRAM,0); if (_sockfd==-1) { cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl; exit(SOCKET_ERROR); } cout<<"server socket success: "<<" : "<<_sockfd<<endl; }
如果套接字創(chuàng)建失敗,就算沒有給我們的文件描述符返回-1,由于我們初始化的時候就初始化為-1,所以還是會報錯,注意:一旦連套接字都沒創(chuàng)建成功,那么就沒有繼續(xù)的必要了直接退出即可,這里我們直接用枚舉列出所有的退出碼然后在退出的時候使用:
enum { SOCKET_ERROR = 2 };
創(chuàng)建成功我們就直接打印一下文件描述符即可。
下面進(jìn)入初始化第二步:綁定端口和ip
首先第一個參數(shù)就是我們使用socket接口給我們返回的文件描述符,第二個參數(shù)是什么呢?大家看到這個參數(shù)名struct sockaddr*是否感到熟悉呢?沒錯就是我們上一篇講到的sockaddr結(jié)構(gòu):
注意我們用的IPV4協(xié)議要用sockaddr_in這個結(jié)構(gòu),但是接口參數(shù)是sockaddr*這個結(jié)構(gòu),所以我們用的時候要做一下強(qiáng)制類型轉(zhuǎn)換??梢钥吹轿覀兊倪@個結(jié)構(gòu)有4個位置需要我們填充,第一個AF_INET代表協(xié)議家族,第二個是端口號,第三個是IP地址,第四個是這個結(jié)構(gòu)體的大小。
第三個參數(shù)是這個結(jié)構(gòu)體的長度。
對于bind這個接口,如果成功則返回0,如果失敗則返回-1.
由于bind的第二個參數(shù)是結(jié)構(gòu)體指針,所以我們需要先創(chuàng)建一個新的結(jié)構(gòu)體,然后對這個結(jié)構(gòu)體進(jìn)行填充,填充后傳入?yún)?shù):
struct sockaddr_in local; //在棧(用戶)上定義了一個結(jié)構(gòu)體變量 bzero(&local,sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); //給別人發(fā)消息要將port和ip發(fā)送給對方 htons主機(jī)轉(zhuǎn)網(wǎng)絡(luò)序列(port是short類型) local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1.string->uint32_t 2.主機(jī)轉(zhuǎn)網(wǎng)絡(luò),ip是四字節(jié)htonl
bzero這個接口可以將我們的結(jié)構(gòu)體里面的內(nèi)容初始化為0,然后我們進(jìn)行填充首先協(xié)議家族填寫AF_INET這里是固定寫法,然后就是填寫端口號和ip地址,對于端口號,在結(jié)構(gòu)體中的類型是16字節(jié)的short短整型,而htons這個接口可以將主機(jī)字節(jié)序轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序(還記得我們上一篇講的內(nèi)容嗎?網(wǎng)絡(luò)中所有字節(jié)序必須是大端存儲,而主機(jī)中有可能大端有可能小端,所以hton這個接口就是將任意的主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的接口),htons后面的s代表要轉(zhuǎn)化為16字節(jié)的,如果你的port是32字節(jié)的,那么你就需要用htonl轉(zhuǎn)換為long類型。
對于ip的填充,首先結(jié)構(gòu)體中的ip的類型是32位的,而我們剛剛在類內(nèi)定義的是一個字符串,所以我們需要先將字符串轉(zhuǎn)換為32位整形,然后再將這個32位整形由主機(jī)字節(jié)序轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序,所以正常的步驟是:1.string->uint32_t 2.htonl(uint32_t) 但是現(xiàn)在我們有一個很好用的接口,這個接口是inet_addr,下面我們看看這個接口:
我們可以看到inet_addr的參數(shù)是一個const char*類型,這是什么呢?實際上這個類型就是我們ip常用的點分十進(jìn)制類型,這個函數(shù)的返回值是in_addr_t,也就是說這個函數(shù)可以直接將點分十進(jìn)制類型轉(zhuǎn)化為我們結(jié)構(gòu)體中所需要的ip類型。
我們將這個結(jié)構(gòu)體填充完畢后,下面就直接綁定端口號和ip:
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local)); if (n==-1) { cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl; exit(BIND_ERR); }
前面我們說過,bind的參數(shù)與我們ipv4協(xié)議使用的結(jié)構(gòu)體類型不一樣需要強(qiáng)制轉(zhuǎn)化。當(dāng)我們綁定失敗,我們就打印錯誤信息,然后加一個bind接口的錯誤碼用于返回:
enum { SOCKET_ERROR = 2 ,BIND_ERR };
綁定結(jié)束后我們的服務(wù)器初始化接口就結(jié)束了,下面我們進(jìn)入服務(wù)器啟動的接口,在這里我們要注意,服務(wù)器啟動的本質(zhì)就是一個死循環(huán),就比如我們的手機(jī)系統(tǒng),如果不是主動的退出,我們的手機(jī)是不會關(guān)機(jī)的。
對于udp服務(wù)器的啟動,我們先大概的思考一下:./udpserver ip port也就是說需要三個參數(shù),所以我們可以先設(shè)計一下udpserver.cc:
首先對于不懂如何啟動服務(wù)器的用戶我們需要加一個使用手冊,保證用戶可以正常啟動服務(wù)器:
static void Usage(string proc) { cout<<"Usage:\n\t"<<proc<<" local_ip local_port\n\n"; } int main(int argc,char* argv[]) { if (argc!=3) { Usage(argv[0]); exit(USAGE_ERR); } uint16_t port = atoi(argv[2]); string ip = argv[1]; unique_ptr<udpServer> usvr(new udpServer(port,ip)); usvr->InitServer(); usvr->start(); return 0; }
對于main函數(shù)的參數(shù)我們之前已經(jīng)講過,argc代表你傳了幾個參數(shù),argv這個數(shù)組對應(yīng)的下標(biāo)就是我們的參數(shù)。我們的目的是:./udpserver ip port這樣使用,所以一共有三個參數(shù),如果用戶沒有傳3個參數(shù),那么我們就直接提示如何使用并且退出程序,這里我們也可以弄一個錯誤碼寫到枚舉中:
enum { USAGE_ERR = 1 ,SOCKET_ERROR ,BIND_ERR };
如果用戶輸入成功,那么我們先獲取用戶輸入的端口號,因為用戶輸入的是字符串,所以需要將字符串轉(zhuǎn)化為整形,我們用uint16_t的類型來接收端口號,因為我們的server類中的ip是string的,所以可以直接用string變量獲取ip地址。然后我們用一個智能指針來管理服務(wù)器,在服務(wù)器中使用端口號和ip構(gòu)造服務(wù)器,然后對服務(wù)器進(jìn)行初始化和啟動即可。
下面我們講解一個在綁定前填充結(jié)構(gòu)體中ip地址的問題:實際上我們在正在做項目的時候,是不會直接像下面這樣指明一個IP的:
真實的寫法應(yīng)該是下面這樣:
local.sin_addr.s_addr = INADDR_ANY; //任意地址綁定才是服務(wù)器的真實寫法
什么意思呢?實際上就是當(dāng)我們將服務(wù)器的IP設(shè)為ANY(本質(zhì)其實是0),就代表未來發(fā)給我的數(shù)據(jù)只要是綁定了我的端口那么就能與我通信,這樣就不會漏掉沒有我IP地址的服務(wù)器給我發(fā)的消息了。還記得我們剛開始寫的IP是什么嗎?沒錯就是全0,也就是說我們現(xiàn)在寫的這個服務(wù)器是不需要我們具體的IP只需要通過端口號就可以啟動臺服務(wù)器,并且未來客戶端訪問我們的服務(wù)器的時候是不需要指明IP的,任意一個IP+特定的端口號都能訪問我們這臺服務(wù)器。既然不需要IP,下面我們就修改一下代碼:
static void Usage(string proc) { cout<<"Usage:\n\t"<<proc<<" local_port\n\n"; } int main(int argc,char* argv[]) { if (argc!=2) { Usage(argv[0]); exit(USAGE_ERR); } uint16_t port = atoi(argv[1]); unique_ptr<udpServer> usvr(new udpServer(handerMessage,port)); usvr->InitServer(); usvr->start(); return 0; }
所以實際上一個服務(wù)器的IP不重要,只要我們有端口號就能啟動這臺服務(wù)器,并且客戶端用任意的IP和我們服務(wù)器特定的端口號就可以和我們的服務(wù)器通信。
下面我們編寫start接口的代碼,一旦啟動我們就要接受數(shù)據(jù),所以我們先認(rèn)識一個接口:
這個接口的第一個參數(shù)是我們創(chuàng)建套接字返回的文件描述符,意思就是我們從哪個套接字里讀數(shù)據(jù)。第二個參數(shù)是一個緩沖區(qū),第三個參數(shù)是這個緩沖區(qū)的長度,2和3這兩個參數(shù)代表的是你讀到的數(shù)據(jù)要放在哪個緩沖區(qū)里,第四個參數(shù)是讀取方式,這里我們默認(rèn)填0代表阻塞式讀取,也就是說客戶端不給我們服務(wù)端發(fā)消息時,我們就一直等待客戶端發(fā)消息,這就叫阻塞式讀取。第五個參數(shù)和第六個參數(shù)非常重要,這兩個參數(shù)是輸出型參數(shù),也就是說未來客戶端給我們發(fā)消息時,會將數(shù)據(jù)放到緩沖區(qū)中,然后會將客戶端的端口號和IP放到struct sockaddr*這個結(jié)構(gòu)體當(dāng)中,第六個參數(shù)就是這個結(jié)構(gòu)體的長度,我們可以理解為:我們只需要創(chuàng)建一個空的結(jié)構(gòu)體,然后客戶端發(fā)消息后這個接口就會將客戶端的端口號和IP放到我們自己創(chuàng)建的結(jié)構(gòu)體中。
對于這個接口的返回值,如果成功則會給我們返回讀到數(shù)據(jù)的字節(jié)數(shù),如果失敗返回-1.
static const int gnum = 1024; void start() { //服務(wù)器的本質(zhì)實際上就是一個死循環(huán) char buffer[gnum]; for (;;) { struct sockaddr_in peer; socklen_t len = sizeof(peer); //必填 ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(structsockaddr*)&peer,&len); //成功返回字節(jié)數(shù) } }
我們在使用recvfrom接口的時候,對于緩沖區(qū)是不用考慮\0的存在的,所以長度是1024-1.然后我們的結(jié)構(gòu)體類型在參數(shù)中需要做強(qiáng)制類型轉(zhuǎn)換,理由與上面同理。下面我們思考讀到數(shù)據(jù)該干什么?我們的目的是實現(xiàn)一個udp服務(wù)器用來進(jìn)行簡單的聊天,聊天的時候要顯示出客戶端的ip和端口號,所以我們這樣設(shè)計:
void start() { //服務(wù)器的本質(zhì)實際上就是一個死循環(huán) char buffer[gnum]; for (;;) { struct sockaddr_in peer; socklen_t len = sizeof(peer); //必填 ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len); //成功返回字節(jié)數(shù) //1.數(shù)據(jù)是什么? 2.誰發(fā)的 if (s>0) { buffer[s] = 0; string clientip = inet_ntoa(peer.sin_addr); //1.網(wǎng)絡(luò)序列 2.int->點分十進(jìn)制IP uint16_t clientport = ntohs(peer.sin_port); string message = buffer; cout<<clientip<<"["<<clientport<<"]#"<<message<<endl; } } }
如果讀取數(shù)據(jù)成功,我們先將緩沖區(qū)中最后一個位置填上\0,這樣我們就可以用string來接收這個緩沖區(qū)中的字符了,然后我們獲取用戶的IP,由于結(jié)構(gòu)體中的類型是網(wǎng)絡(luò)的,所以我們需要將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)回主機(jī)字節(jié)序,而這里有一個接口與我們那會用的inet_addr正好相反,那就是inet_ntoa接口:
這個接口可以為完成兩步:1.ntol(struct in_addr) 2.ntol(struct in_addr)->char*
ntol就是hton相反的轉(zhuǎn)換接口。
獲取到string類型的ip后,我們再接收端口號,同樣需要轉(zhuǎn)換,然后我們就打印用戶端ip[端口號]+用戶端發(fā)的消息即可。這樣服務(wù)端的代碼就實現(xiàn)完成了。
下面我們開始完成客戶端代碼:
首先客戶端必須要有的是服務(wù)端的IP和服務(wù)端的port,所以我們先寫一個框架:
namespace Client { class udpClient { public: udpClient(const string& serverip,const uint16_t &serverport) :_serverip(serverip) ,_serverport(serverport) { } void InitClient() { } void run() { } ~udpClient() { } private: string _serverip; uint16_t _serverport; }; }
前面我們說過,對于服務(wù)器而言,ip地址是不重要的,只需要端口號就可以啟動服務(wù)器,因為一般服務(wù)器的IP都是全0,代表任意IP都可以訪問,所以我們的客戶端只需要隨便填一個IP加上特殊的端口號就可以通信了,那么客戶端內(nèi)部ip和port肯定是必須要有的,明白了這個知識我們就先實現(xiàn)一下client.cc的框架:
#include "udpClient.hpp" #include <memory> using namespace Client; static void Usage(string proc) { cout<<"Usage:\n\t"<<proc<<" server_ip server_port\n\n"; } //./udpClient server_ip server_port int main(int argc, char* argv[]) { if (argc!=3) { Usage(argv[0]); exit(1); } string serverip = argv[1]; uint16_t serverport = atoi(argv[2]); unique_ptr<udpClient> ucli(new udpClient(serverip,serverport)); ucli->InitClient(); ucli->run(); return 0; }
這里的原理和我們服務(wù)器寫的一模一樣,我們就直接編寫客戶端代碼:
首先我們客戶端的初始化一定也是需要創(chuàng)建套接字的,既然要創(chuàng)建套接字就必須要有一個變量接收套接字返回的文件描述符:
udpClient(const string& serverip,const uint16_t &serverport) :_sockfd(-1) ,_serverip(serverip) ,_serverport(serverport) { }
然后我們編寫初始化函數(shù):
void InitClient() { // 1.創(chuàng)建socket _sockfd = socket(AF_INET,SOCK_DGRAM,0); if (_sockfd==-1) { cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl; exit(2); } cout<<"client socket success: "<<" : "<<_sockfd<<endl; //2. client要不要bind(必須要),client要不要明確的bind(不需要,不需要程序員自己bind(由OS自動形成端口綁定)) }
我們客戶端的代碼很簡單,相比服務(wù)端客戶端是不需要明確的去bind的,這是因為服務(wù)端必須要有指定的不能隨意改變的端口,這樣我們的客戶端才能找到服務(wù)端,就像110一樣,110這個電話是不能隨意更改的,但是對于用戶端自己來講,我自己是什么樣的端口不重要,我只需要通過服務(wù)端的端口訪問服務(wù)端。所以我們一定要注意:客戶端需要bind,但是不需要程序員明確的bind,這里我們自己不bind,操作系統(tǒng)察覺到我們沒有綁定后會自動幫我們綁定,并且每次綁定的端口號都是隨機(jī)的。
下面我們編寫客戶端運行的函數(shù),客戶端運行很簡單,我們只需要讓客戶端輸入數(shù)據(jù),這樣的話我們服務(wù)端就可以接受到數(shù)據(jù),因為我們的目的就是簡單的網(wǎng)絡(luò)通信。
對于客戶端發(fā)消息,我們需要認(rèn)識一個接口:
第一個參數(shù)是我們創(chuàng)建套接字返回的文件描述符,第二個參數(shù)和第三個參數(shù)是一起的,buf是我們發(fā)送的數(shù)據(jù)所在的緩沖區(qū),第三個參數(shù)是緩沖區(qū)的長度,第四個參數(shù)是發(fā)送方式,我們還是默認(rèn)填0表示阻塞發(fā)送,有數(shù)據(jù)就發(fā),沒數(shù)據(jù)就等。第五個參數(shù)和第六個參數(shù)同樣是輸入型參數(shù),我們客戶端需要提前創(chuàng)建一個結(jié)構(gòu)體,向里面填充我們客戶端的ip和端口號,然后通過sendto接口發(fā)送到服務(wù)端,然后服務(wù)端就會接收到我們的數(shù)據(jù)和ip和端口號。
void run() { struct sockaddr_in server; memset(&server,0,sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(_serverip.c_str()); server.sin_port = htons(_serverport); string message; while (!_quit) { cout<<"Please Enter# "; cin>>message; sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server)); } }
這里我們的客戶端要持續(xù)的輸入所以設(shè)為死循環(huán),quit是我們新增的一個成員變量:
以上就是我們客戶端的代碼了,實際上客戶端的代碼非常簡單,下面我們運行起來:
運行起來后我們可以看到是沒問題的,這里也解釋了為什么說客戶端端口不需要程序員綁定,我們可以看到每次客戶端重新登錄在服務(wù)端顯示的端口號都是不一樣的,因為這是操作系統(tǒng)自動指定的端口號,而我們的服務(wù)端的端口號是唯一的,我們客戶端必須輸入服務(wù)端正確的端口號才能訪問服務(wù)端,當(dāng)然小伙伴們也一樣將服務(wù)端的可執(zhí)行程序直接發(fā)給你們的小伙伴,然后讓他們直接通過任意ip+ 你的服務(wù)器端口號來和你進(jìn)行聊天,下面是多人通過網(wǎng)絡(luò)聊天的界面:
總結(jié)
以上就是我們udp服務(wù)器的所有內(nèi)容了,下一篇文章我們將會把這個服務(wù)器改造稱為英漢互譯,大型聊天室等好玩的工具。
到此這篇關(guān)于Linux手把手教你實現(xiàn)udp服務(wù)器的文章就介紹到這了,更多相關(guān)Linux udp服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
圖文詳解Ubuntu搭建Ftp服務(wù)器的方法(包成功)
今天小編就為大家分享一篇關(guān)于圖文詳解Ubuntu搭建Ftp服務(wù)器的方法(包成功),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03Ubuntu下安裝nvidia顯卡驅(qū)動(安裝方式簡單)
這篇文章主要介紹了Ubuntu下安裝nvidia顯卡驅(qū)動,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Linux之進(jìn)程狀態(tài)&&進(jìn)程優(yōu)先級詳解
文章介紹了操作系統(tǒng)中進(jìn)程的狀態(tài),包括運行狀態(tài)、阻塞狀態(tài)和掛起狀態(tài),并詳細(xì)解釋了Linux下進(jìn)程的具體狀態(tài)及其管理,此外,文章還討論了進(jìn)程的優(yōu)先級、查看和修改進(jìn)程優(yōu)先級的方法,以及并發(fā)相關(guān)的概念和函數(shù)的返回值2025-02-02Linux下實現(xiàn)UTF-8和GB2312互相轉(zhuǎn)換的方法
下面小編就為大家?guī)硪黄狶inux下實現(xiàn)UTF-8和GB2312互相轉(zhuǎn)換的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,祝大家游戲愉快哦2016-12-12如何在Linux服務(wù)上管理Redis的啟動、重啟和關(guān)閉
Redis是一個高性能的開源鍵值對存儲數(shù)據(jù)庫,廣泛用于緩存、會話管理和實時數(shù)據(jù)處理等場景,本文將詳細(xì)介紹如何在Linux系統(tǒng)上啟動、重啟和關(guān)閉Redis服務(wù),并提供相關(guān)的配置和故障排除技巧,需要的朋友可以參考下2024-05-05Linux中大內(nèi)存頁Oracle數(shù)據(jù)庫優(yōu)化的方法
這篇文章主要給大家介紹了關(guān)于Linux中大內(nèi)存頁Oracle數(shù)據(jù)庫優(yōu)化的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11