網路IPC:套接字

来源:http://www.cnblogs.com/Lynn-Zhang/archive/2016/08/03/5716078.html
-Advertisement-
Play Games

網路進程間通信:socket API簡介 不同電腦(通過網路相連)上運行的進程相互通信機制稱為網路進程間通信(network IPC)。 在本地可以通過進程PID來唯一標識一個進程,但是在網路中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網路層的“ip地址”可以唯一標識網路中的主 ...


網路進程間通信:socket API簡介

不同電腦(通過網路相連)上運行的進程相互通信機制稱為網路進程間通信(network IPC)。

在本地可以通過進程PID來唯一標識一個進程,但是在網路中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網路層的“ip地址”可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程式(進程)。這樣利用三元組(ip地址,協議,埠)構成套接字,就可以標識網路的進程了,網路中的進程通信就可以利用這個標誌與其它進程進行交互。

套接字是通信埠的抽象!通過套接字網路IPC介面,進程能夠使用該介面和其他進程通信。

幾個定義:

  1. IP地址:即依照TCP/IP協議分配給本地主機的網路地址,兩個進程要通訊,任一進程首先要知道通訊對方的位置,即對方的IP。
  2. 埠號:用來辨別本地通訊進程,一個本地的進程在通訊時均會占用一個埠號,不同的進程埠號不同,因此在通訊前必須要分配一個沒有被訪問的埠號。
  3. 連接:指兩個進程間的通訊鏈路。
  4. 半相關:網路中用一個三元組可以在全局唯一標誌一個進程:(協議,本地地址,本地埠號)這樣一個三元組,叫做一個半相關,它指定連接的每半部分。
  5. 全相關:一個完整的網間進程通信需要由兩個進程組成,並且只能使用同一種高層協議。也就是說,不可能通信的一端用TCP協議,而另一端用UDP協議。因此一個完整的網間通信需要一個五元組來標識:(協議,本地地址,本地埠號,遠地地址,遠地埠號),這樣一個五元組,叫做一個相關(association),即兩個協議相同的半相關才能組合成一個合適的相關,或完全指定組成一連接。

套接字描述符

套接字是端點的抽象。與應用進程要使用文件描述符訪問文件一樣,訪問套接字也需要用套接字描述符。套接字描述符在UNIX系統中是用文件描述符實現的。

要創建一個套接字,可以調用socket函數。

#include<sys/socket.h>
int socket(int domain, int type, int protocol);

  參數:

作用:socket()用於創建一個socket描述符(socket descriptor),它唯一標識一個socket。


網路位元組序

網路協議指定了位元組序,因此異構電腦系統能夠交換協議信息而不會混淆位元組序。TCP/IP協議棧採用大端位元組序。應用進程交換格式化數據時,位元組序問題就會出現。對於TCP/IP,地址用網路位元組序來表示,所以應用進程有時需要在處理器的位元組序與網路位元組序之間轉換。

#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

這些函數名很好記,h表示host,n表示network, l表示32位長整數,s表示16位短整數

在將一個地址綁定到socket的時候,請先將主機位元組序轉換成為網路位元組序,對主機位元組序不要做任何假定,務必將其轉化為網路位元組序再賦給socket!


將套接字與地址綁定

與客戶端的套接字關聯的地址意義不大,可以讓系統選擇一個預設的地址。然而,對於伺服器,需要給一個接收客戶端請求的套接字綁定一個眾所周知的地址。客戶端應有一種方法用以連接伺服器的地址,最簡單的方法就是為伺服器保留一個地址並且在/etc/services或某個名字服務(name service)中註冊。

  可以用bind函數來搞定這個問題:

#include <sys/types.h>        
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

參數:

第一個參數:bind()函數把一個地址族中的特定地址賦給該sockfd(套接字描述字)。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和埠號組合賦給socket。 

第二個參數:struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同:

地址格式

地址標識了特定通信域中的套接字端點,地址格式與特定的通信域相關。為使不同格式地址能夠被傳入到套接字函數,地址需被強轉為通用的地址結構sockaddr表示。

//頭文件
#include<netinet/in.h>

struct sockaddr 是一個通用地址結構,該結構定義如下: 

struct sockaddr
{
   sa_family_t sa_family;
   char        sa_data[14];
}

IPV4網際網路域:

//ipv4對應的是: 
/* 網路地址 */
struct in_addr 
{
    uint32_t       s_addr;     /* address in network byte order */
};

struct sockaddr_in {
    sa_family_t    sin_family;    /* address family: AF_INET */
    in_port_t      sin_port;      /* port in network byte order */
    struct in_addr sin_addr;      /* internet address */
};

IPv6網際網路域:

//ipv6對應的是: 
struct in6_addr 
{ 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

struct sockaddr_in6 
{ 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

  Unix域對應的是: 

#define UNIX_PATH_MAX    108

struct sockaddr_un 
{ 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

第三個參數:addrlen 對應的是地址的長度

返回值:成功返回0,出錯返回-1

作用:將套接字與埠號綁定,即把一個ip地址和埠號組合賦給socket


點分十進位IP與網路位元組序IP之間的轉換

有時需要列印出能被人而不是電腦所理解的地址格式。我們可以利用函數來進行二進位地址格式與點分十進位格式的相互轉換。但是這些函數僅支持IPv4地址。

 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 //點分十進位IP轉換網路位元組序IP
 int inet_aton(const char *cp, struct in_addr *inp);
 //點分十進位IP轉換網路位元組序IP
 in_addr_t inet_addr(const char *cp);
 //網路位元組序IP 轉化點分十進位IP
 char *inet_ntoa(struct in_addr in);

其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換IPv6的in6_addr,因此函數介面是void* 類型!

#include <arpa/inet.h>
//網路位元組序IP 轉化點分十進位IP
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
//點分十進位IP轉換網路位元組序IP
int inet_pton(int af, const char *src, void *dst);

監聽

如果作為一個伺服器,在調用socket()、bind()之後就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,伺服器端就會接收到這個請求。

伺服器調用 listen 來宣告可以接收連接請求!

#include <sys/types.h>    
#include <sys/socket.h>
 int listen(int sockfd, int backlog);

參數:sockfd為要監聽的socket描述字,backlog為相應socket可以排隊的最大連接個數  

返回值:成功返回0,出錯返回-1

作用:socket函數創建一個套接字時,預設是一個主動套接字,listen函數把一個未調用connect的未連接的套接字轉換成一個被動套接字,指示內核應接收指向該套接字的連接請求。(主動/客戶 -> 被動/伺服器)


連接

如果是面向連接的網路服務,在開始交換數據前,都要在請求服務的進程套接字(客戶端)和提供服務的進程套接字(伺服器)之間建立一個連接,使用connect函數:

#include <sys/types.h>        
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

參數:第一個參數sockfd為客戶端的socket描述字,第二參數為伺服器的socket地址,第三個參數為socket地址的長度。 

返回值:成功返回0,出錯返回-1

作用:客戶端通過調用connect函數來建立與TCP伺服器的連接

註意:在connect中所指定的地址是想與之通信的伺服器地址。如果sockfd沒有綁定到一個地址,connect會給調用者綁定一個預設地址!


使用accept函數獲得連接請求並建立連接

#include <sys/types.h>          
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

參 數 :第一個參數為伺服器的socket描述字,第二個參數為指向struct sockaddr *的指針,用於返回客戶端的協議地址,第三個參數為協議地址的長度 

返回值:如果accpet成功,那麼其返回值是由內核自動生成的一個全新的描述字,該描述符連接到調用connect的客戶端。這個新的套接字描述符和原始的套接字描述符具有相同的套接字類型和地址族。

註 意:傳給accept的原始套接字沒有關聯到這個連接,而是繼續保持可用狀態並接受其它連接請求!

通俗點來說,accept的第一個參數為伺服器的socket描述字,是伺服器開始調用socket()函數生成的,稱為監聽socket描述字;而accept函數返回的是已連接的socket描述字。一個伺服器通常通常僅僅只創建一個監聽socket描述字,它在該伺服器的生命周期內一直存在。內核為每個由伺服器進程接受的客戶連接創建了一個已連接socket描述字,當伺服器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。


數據傳輸

既然套接字端點表示文件描述符,那麼只要建立連接,就可以使用write和read來通過套接字通信了。

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);

write()會把指針buf所指的記憶體寫入count個位元組到參數fd所指的文件內(文件讀寫位置也會隨之移動),如果順利write()會返回實際寫入的位元組數。當有錯誤發生時則返回-1,錯誤代碼存入errno中!

read()會把參數fd所指的文件傳送nbyte個位元組到buf指針所指的記憶體中,成功返回讀取的位元組數,出錯返回-1並設置errno,如果在調read之前已到達文件末尾,則這次read返回0 。

如果想指定多個選項、從多個客戶端接收數據包或發送帶外數據,需要採用6個傳遞數據的套接字函數中的一個。

三個函數用來發送數據:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

  三個函數用來接收數據:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

關閉套接字描述符

 close函數用來關閉文件描述符:

#include <unistd.h>
int close(int fd);

  註意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向伺服器發送終止連接請求。


 地址“重用”

預設條件下,一個套接字不能與一個已在使用中的本地地址捆綁。但有時會需要“重用”地址。因為每一個連接都由本地地址和遠端地址的組合唯一確定,所以只要遠端地址不同,兩個套介面與一個地址捆綁並無大礙。為了通知套介面實現不要因為一個地址已被一個套介面使用就不讓它與另一個套介面捆綁,應用程式可在bind()調用前先設置SO_REUSEADDR選項。請註意僅在bind()調用時該選項才被解釋;故此無需(但也無害)將一個不會共用地址的套接字設置該選項,或者在bind()對這個或其他套介面無影響情況下設置或清除這一選項。

解決這個問題的方法是使用setsockopt()設置socket描述符的 選項SO_REUSEADDR為1,表示允許創建埠號相同但IP地址不同的多個socket描述符。 在server代碼的socket()和bind()調用之間插入如下代碼:

int opt=1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

socket通信基本流程:

  1. TCP伺服器端依次調用socket()、bind()、listen()之後,就會監聽指定的socket地址了。
  2. TCP客戶端依次調用socket()、connect()之後就向TCP伺服器發送了一個連接請求。
  3. TCP伺服器監聽到這個請求之後,就會調用accept()函數取接收請求,這樣連接就建立好了。
  4. 之後就可以開始網路I/O操作了,即類同於普通文件的讀寫I/O操作。


 

代碼示例

建立一個基於TCP的socket API

伺服器:

/*************************************************************************
 > File Name: server.c
 > Author:Lynn-Zhang 
 > Mail: [email protected]
 > Created Time: Fri 29 Jul 2016 12:15:28 PM CST
 ************************************************************************/

#include<stdio.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<pthread.h>

static void usage(const char* proc)
{
    printf("Usage: %s [ip] [port]\n",proc);
}

void *thread_run(void *arg)
{
    printf("create a new thread\n");
    int fd=(int)arg;
    char buf[1024];
    while(1)
    {
        //伺服器端將套接字描述符中到數據讀到buf並列印,再將自己的回覆寫入套接字描述符
        memset(buf,'\0',sizeof(buf));
        ssize_t _s=read(fd,buf,sizeof(buf)-1);
        if(_s>0)
        {
            buf[_s]='\0';
            printf("client:# %s",buf);
            printf("server:$ ");
            fflush(stdout);
            
            //伺服器將回覆寫入fd
            memset(buf,'\0',sizeof(buf));
            ssize_t _in=read(0,buf,sizeof(buf)-1);
            if(_in>=0)
            {
              buf[_in-1]='\0';
              write(fd,buf,strlen(buf));
            }
            printf("please wait ...\n");
        }
        else if(_s==0)
        {
            printf("client close...\n");
            break;
        }
        else
        {
            printf("read error ...\n");
            break;
        }
    }
    return (void*)0;
}

int main(int argc,char *argv[])
{
    //參數必須能構成完整的socket
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    //建立伺服器端socket
    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock<0)
    {
        perror("socket");
        return 1;
    }

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(atoi(argv[2]));
    local.sin_addr.s_addr=inet_addr(argv[1]);
    
    int opt=1;
    if(setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
    {
        perror("setsockopet error\n");
        return -1;
    }

    //將套接字綁定到伺服器端的ip地址和埠號綁定
    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        return 2;
    }
    //建立監聽隊列,等待套接字的連接請求
    listen(listen_sock,5);

    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);
    while(1)
    {
        //獲得連接請求並建立連接
        int client_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
        if(client_sock<0)
        {
            perror("accept faild ...\n");
            return 3;
        }
        printf("get a new link,socket -> %s:%d\n",inet_ntoa(peer.sin_addr));

        pthread_t id;
        pthread_create(&id,NULL,thread_run,(void*)client_sock);

        pthread_detach(id);

//        pid_t id=fork();
//        if(id==0)
//        {//child
//            char buf[1024];
//            while(1)
//            {
//                 //將監聽到的套接子描述符指定文件描述中的數據讀到buf中
//                memset(buf,'\0',sizeof(buf));
//                ssize_t _s=read(client_sock,buf,sizeof(buf)-1);
//                if(_s>0)
//                {
//                    buf[_s-1]='\0'
//                    printf("client:# %s\n",buf); 
//                    printf("server:$ ");
//                    fflush(stdout);
//                    memset(buf,'\0',sizeof(buf));
//                    ssize_t _s=read(0,buf,sizeof(buf)-1);
//                    if(_s>0)
//                    {                    
//                         buf[_s-1]='\0';
//                         write(client_sock,buf,strlen(buf));
//                    }
//                    else
//                    {
//                        printf("Fail !\n");
//                    }
//                }
//                else
//                {
//                      printf("read done...\n");
//                    break;
//                }
//            }
//
//        }
//        else
//        {//father
//            waitpid(-1,NULL,WNOHANG);
//        }
//
    }
    close(listen_sock);
    return 0;
}

 客戶端:

/*************************************************************************
 > File Name: client.c
 > Author:Lynn-Zhang 
 > Mail: [email protected]
 > Created Time: Fri 29 Jul 2016 09:00:01 AM CST
 ************************************************************************/

#include<stdio.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<errno.h>
#include<pthread.h>

static usage(const char* proc)
{
    printf("Usage: %s [ip] [port]\n",proc);
}

int main(int argc,char* argv[])
{
    //傳入的參數是一個完整的socket(ip地址+埠號)
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    //建立一個套接字描述符
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        return 2;
    }
    //IPv4網際網路域(AF_INET)中,套接字地址用sockaddr_in表示
    struct sockaddr_in remote;
    remote.sin_family=AF_INET;   //socket通信域
    remote.sin_port=htons(atoi(argv[2]));   //埠號
    remote.sin_addr.s_addr=inet_addr(argv[1]);  //ip地址
    //建立連接請求
    int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));
    if(ret<0)
    {
        printf("connect failed ... ,errno is :%d,errstring is: %s\n",errno,strerror(errno));
        return 3;
    }
    printf("connect success ...\n");
    char buf[1024];
    while(1)
    {
        //從標準輸入將數據讀入buf中,再寫入sock中
        memset(buf,'\0',sizeof(buf));
        printf("client:# ");
        fflush(stdout);
        ssize_t _s=read(0,buf,sizeof(buf)-1);
        fflush(stdin);
        if(_s<0)
        {
            perror("read\n");
            break;
        }
        buf[_s]='\0';
        write(sock,buf,strlen(buf));
        if(strcmp(buf,"quit")==0)
        {
            printf("quit!\n");
            break;
        }

        _s=read(sock,buf,sizeof(buf));
        if(_s>0)
        {
            buf[_s]='\0';
            printf("server:$ %s\n",buf);
        }
    }
    close(sock);
    printf("sock close");
    return 0;
}

伺服器: 

客戶端:

 

本篇總結若有不足,希望指正 (。⌒∇⌒)

 

部分參考:

吳秦   http://www.cnblogs.com/skynet/

《Unix 環境高級編程》

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這段時間遇到一個問題就是ReportService 中採用了遠程連接的報表偶爾會斷開連接,導致報表導出異常,查閱了很多資料,幾天來就是斷斷續續的終於解決了這個問題,下麵把一些解決的點一一展示出來,便於大家將來遇到同樣問題無從下手。 首先是報錯,接下來我馬上去看日誌,很多人不知道文件的位置,一般預設就 ...
  • 成功安裝了Oracle 11g後,使用sqlplus登錄資料庫時遇到下麵錯誤: [oracle@DB-Server ~]$ sqlplus / as sysdba sqlplus: error while loading shared libraries: /u01/app/lib/libclnts... ...
  • 計算string所占的位元組長度:返回字元串的長度,單位是 計算string所占的字元長度:返回字元串的長度,單位是 eg: //去掉該欄位後面15位字元串 select t.depre_name, substr(t.depre_name, 0, (length(t.depre_name) 16)) ...
  • 一. 創建表的方法 語法:create table 表名( 屬性名數據類型完整約束條件, 屬性名數據類型條完整約束件, 。。。。。。。。。 屬性名數據類型 ); (1)舉例:1 create table example0( 2 id int, 3 name varchar(20), 4 sexboo ...
  • Kafka是目前非常流行的消息隊列中間件,常用於做普通的消息隊列、網站的活性數據分析(PV、流量、點擊量等)、日誌的搜集(對接大數據存儲引擎做離線分析)。 全部內容來自網路,可信度有待考證!如有問題,還請及時指正。 概念介紹 在Kafka中消息隊列分為三種角色: ,即生產者,負責產生日誌數據。 ,存 ...
  • 需求: 一篇文章里有很多評論,每個評論又有很多回覆評論,要求: 頁面將文章展示出來,且文章的主評論按照評論時間分頁展示,回覆評論的評論完全展示在每個主評論下麵,且按照回覆時間排序 最終查詢結果SQL查詢結果如下: Code: 評論編碼,ParentCode:回覆評論編碼,num:主評論序號,lvl: ...
  • 問題描述:在表列里有肉眼不可見字元,導致一些更新或插入失敗。 幾年前第一次碰見這種問題是在讀取考勤機人員信息時碰見的,折騰了一點時間,現在又碰到了還有點新發現就順便一起記錄下。 轉載註明出處:http://www.cnblogs.com/zzry/p/5729404.html 如下圖所示 golds ...
  • 背景 使用Exp命令在oracle 11g 以後不導出空表(rowcount=0),是最近在工作中遇到一個很坑的問題,甚至已經被坑了不止一次,所以這次痛定思痛,準備把這個問題徹底解決。之所以叫新方法,那一定有老方法了,這個方法是一位博友很早就提出了,以下是原文,其實也說明瞭問題的原因 Oracle1 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...