android socket編程

android socket編程

轉載時請註明出處和作者

作者:Xandy
 
註明:以下部分內容來自 Linux下Socket編程

1       關於socket

關於socket百度百科裏有這樣的解釋:通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket正如其英文原意那樣,象一個多孔插座。一臺主機猶如佈滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務。

至此,我們對socket進行了直觀的描述。抽象出來,socket實質上提供了進程通信的端點。進程通信之前,雙方首先必須各自創建一個端點,否則是沒有辦法建立聯繫並相互通信的。正如打電話之前,雙方必須各自擁有一臺電話機一樣。在網間網內部,每一個socket用一個半相關描述:

(協議,本地地址,本地端口)

  一個完整的socket有一個本地唯一的socket號,由操作系統分配。

  最重要的是,socket 是面向客戶/服務器模型而設計的,針對客戶和服務器程序提供不同的socket 系統調用。客戶隨機申請一個socket (相當於一個想打電話的人可以在任何一臺入網電話上撥號呼叫),系統爲之分配一個socket號;服務器擁有全局公認的 socket ,任何客戶都可以向它發出連接請求和信息請求(相當於一個被呼叫的電話擁有一個呼叫方知道的電話號碼)。

socket利用客戶/服務器模式巧妙地解決了進程之間建立通信連接的問題。服務器socket 半相關爲全局所公認非常重要。不妨考慮一下,兩個完全隨機的用戶進程之間如何建立通信?假如通信雙方沒有任何一方的socket 固定,就好比打電話的雙方彼此不知道對方的電話號碼,要通話是不可能的。

網絡的socket數據傳輸是一種特殊的I/O,socket也是一種文件描述符。Socket也具有一個類似於打開文件的函數調用socket(),該函數返 回一個整型的Socket描述符,隨後的連接建立、數據傳輸等操作都是通過該socket實現的。

2       常見socket類型

常用的socket類型有兩種:

·流式socket (SOCK_STREAM)。流式是一種面向連接的socket,針對於面向連接的TCP服務應用。

·數據報式socket(SOCK_DGRAM)。數據 報式socket是一種無連接的socket,對應於無連接的UDP服務應用。

3       Linux下socket編程

3.1 socket建立

socket爲了建立socket連接,程序可以調用socket函數,該函數返回一個類似於文件描述符的句柄。socket函數原型爲:int socket(int domain,int type,int protocol);domain指明所使用的協議族,通常爲PF_INET,(其與addrinfo 裏的 AF_INET在現在看來是相同的。只是歷史上人們曾構想將AF(地址家族address family)與PF(protocol family 協議家族)分開,但實際上這種區分並未真正推廣,所以現在AF_INET和PF_INET具有相同的意義。其中AF_INET是基於IPv4而PF_INET基於IPv6)表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,socket接口還定義了原始socket(SOCK_RAW),允許程序使用低層協議;protocol通常賦值0。socket()調用返回一個整型socket描述符,你可以在後面的調用使用它。socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用socket函數時,socket執行體將建立一個socket,實際上"建立一個socket"意味着爲一個socket數據結構分配存儲空間。socket執行體爲你管理描述符表。兩個網絡程序之間的一個網絡連接包括五種信息:通信協議、本地協議地址、本地主機端口、遠端主機地址和遠端協議端口。socket數據結構中包含這五種信息。

3.2 socket配置

通過socket調用返回一個socket描述符後,在使用socket進行網絡傳輸以前,必須配置該socket。面向連接的socket客戶端通過調用connect函數在socket數據結構中保存本地和遠端信息。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用 bind函數來配置本地信息。

bind函數將socket與本機上的一個端口相關聯,隨後你就可以在該端口監聽服務請求。bind函數原型爲:

int bind(int sockfd,struct sockaddr *my_addr, int addrlen);

sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP地址及端口號等信息的sockaddr類型的指針;addrlen常被設置爲sizeof(struct sockaddr)。

struct sockaddr結構類型是用來保存socket信息的:

struct sockaddr {

unsigned short sa_family; /* 地址族, AF_xxx */

char sa_data[14]; /* 14 字節的協議地址 */

};

sa_family一般爲AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP地址和端口號。

另外還有一種結構類型:

struct sockaddr_in {

   short int sin_family; /* 地址族 */

   unsigned short int sin_port; /* 端口號 */

   struct in_addr sin_addr; /* IP地址 */

   unsigned char sin_zero[8]; /* 填充0 以保持與struct sockaddr同樣大小 */

};

這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()或memset()函數將其置爲零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味着如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向 sockaddr_in的指針轉換爲指向sockaddr的指針;或者相反。

使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被佔用的端口號:

my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的端口號 */

my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */

通過將my_addr.sin_port置爲0,函數會自動爲你選擇一個未佔用的端口來使用。同樣,通過將my_addr.sin_addr.s_addr置爲INADDR_ANY,系統會自動填入本機IP地址。

注意在使用bind函數是需要將sin_port和sin_addr轉換成爲網絡字節優先順序;而sin_addr則不需要轉換。

計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對於在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。

下面是幾個字節順序轉換函數:

·htonl():把32位值從主機字節序轉換成網絡字節序

·htons():把16位值從主機字節序轉換成網絡字節序

·ntohl():把32位值從網絡字節序轉換成主機字節序

·ntohs():把16位值從網絡字節序轉換成主機字節序

Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置爲相應的錯誤號。需要注意的是,在調用bind函數時一般不要將端口號置爲小於1024的值,因爲1到1024是保留端口號,你可以選擇大於1024中的任何一個沒有被佔用的端口號。

3.3 連接建立

面向連接的客戶程序使用connect函數來配置socket並與遠端服務器建立一個TCP連接,其函數原型爲:

int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);

Sockfd 是socket函數返回的socket描述符;serv_addr是包含遠端主機IP地址和端口號的指針;addrlen是遠端地質結構的長度。connect函數在出現錯誤時返回-1,並且設置errno爲相應的錯誤碼。進行客戶端程序設計無須調用bind(),因爲這種情況下只需知道目的機器 的IP地址,而客戶通過哪個端口與服務器建立連接並不需要關心,socket執行體爲你的程序自動選擇一個未被佔用的端口,並通知你的程序數據什麼時候到達端口。

connect函數啓動和遠端主機的直接連接。只有面向連接的客戶程序使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啓動一個連接,它只是被動的在協議端口監聽客戶的請求。

listen函數使socket處於被動的監聽模式,併爲該socket建立一個輸入數據隊列,將到達的服務請求保存在此隊列中,直到程序處理它們。

int listen(int sockfd, int backlog);

sockfd 是socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連接請求將在隊列中等待accept()它們(參考下文)。backlog對隊列中等待服務的請求的數目進行了限制,大多數系統缺省值爲20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯信息。

當出現錯誤時listen函數返回-1,並置相應的errno錯誤碼。

accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列後,服務器就調用accept函數,然後睡眠並等待客戶的連接請求。

int accept(int sockfd, void *addr, int *addrlen);

sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的信息(某臺主機從某個端口發出該請求);addrten通常爲一個指向值爲sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。

首先,當accept函數監視的 socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯繫起來,收到服務請求的初始socket仍可以繼續在以前的 socket上監聽,同時可以在新的socket描述符上進行數據傳輸操作。

3.4 數據傳輸

send()和recv()這兩個函數用於面向連接的socket上進行數據傳輸。

send()函數原型爲:

int send(int sockfd, const void *msg, int len, int flags);

sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;len是以字節爲單位的數據的長度;flags一般情況下置爲0(關於該參數的用法可參照man手冊)。

send()函數返回實際上發送出的字節數,可能會少於你希望發送的數據。在程序中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。

char *msg = "Hello!";

int len, bytes_sent;

……

len = strlen(msg);

bytes_sent = send(sockfd, msg,len,0);

……

recv()函數原型爲:

int recv(int sockfd,void *buf,int len,unsigned int flags);

sockfd是接受數據的socket描述符;buf 是存放接收數據的緩衝區;len是緩衝的長度。Flags也被置爲0。recv()返回實際上接收的字節數,當出現錯誤時,返回-1並置相應的errno值。

sendto()和recvfrom()用於在無連接的數據報socket方式下進行數據傳輸。由於本地socket並沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。

sendto()函數原型爲:

int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);

該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值爲sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。

Recvfrom()函數原型爲:

int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);

from是一個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置爲sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或 當出現錯誤時返回-1,並置相應的errno。

如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動爲之加上目地和源地址信息。

3.5 結束傳輸

當所有的數據操作結束以後,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:

close(sockfd);

你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。

int shutdown(int sockfd,int how);

Sockfd是需要關閉的socket的描述符。參數 how允許爲shutdown操作選擇以下幾種方式:

·0-------不允許繼續接收數據

·1-------不允許繼續發送數據

·2-------不允許繼續發送和接收數據,

·均爲允許則調用close ()

shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。

3.6 socket (TCP)編程實例

3.6.1 服務器端:tcp_server.c

複製代碼
#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main(int argc, char *argv[])

{

       int server_sockfd;//服務器端套接字

       int client_sockfd;//客戶端套接字

       int len;

       struct sockaddr_in my_addr;   //服務器網絡地址結構體

       struct sockaddr_in remote_addr; //客戶端網絡地址結構體

       int sin_size;

       char buf[BUFSIZ];  //數據傳送的緩衝區

       memset(&my_addr,0,sizeof(my_addr)); //數據初始化--清零

       my_addr.sin_family=AF_INET; //設置爲IP通信

       my_addr.sin_addr.s_addr=INADDR_ANY;//服務器IP地址--允許連接到所有本地地址上

       my_addr.sin_port=htons(8000); //服務器端口號

      

       /*創建服務器端套接字--IPv4協議,面向連接通信,TCP協議*/

       if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)

       { 

              perror("socket");

              return 1;

       }

 

        /*將套接字綁定到服務器的網絡地址上*/

       if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)

       {

              perror("bind");

              return 1;

       }

      

       /*監聽連接請求--監聽隊列長度爲5*/

       listen(server_sockfd,5);

      

       sin_size=sizeof(struct sockaddr_in);

      

       /*等待客戶端連接請求到達*/

       if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)

       {

              perror("accept");

              return 1;

       }

       printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));

       len=send(client_sockfd,"Welcome to my server\n",21,0);//發送歡迎信息

      

       /*接收客戶端的數據並將其發送給客戶端--recv返回接收到的字節數,send返回發送的字節數*/

       while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)

       {

              buf[len]='\0';

              printf("%s\n",buf);

              if(send(client_sockfd,buf,len,0)<0)

              {

                     perror("write");

                     return 1;

              }

       }

       close(client_sockfd);

       close(server_sockfd);

        return 0;

}
複製代碼

 

3.6.2 客戶端:tcp_client.c

複製代碼
#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main(int argc, char *argv[])

{

       int client_sockfd;

       int len;

       struct sockaddr_in remote_addr; //服務器端網絡地址結構體

       char buf[BUFSIZ];  //數據傳送的緩衝區

       memset(&remote_addr,0,sizeof(remote_addr)); //數據初始化--清零

       remote_addr.sin_family=AF_INET; //設置爲IP通信

       remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服務器IP地址

       remote_addr.sin_port=htons(8000); //服務器端口號

      

       /*創建客戶端套接字--IPv4協議,面向連接通信,TCP協議*/

       if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)

       {

              perror("socket");

              return 1;

       }

      

       /*將套接字綁定到服務器的網絡地址上*/

       if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)

       {

              perror("connect");

              return 1;

       }

       printf("connected to server\n");

       len=recv(client_sockfd,buf,BUFSIZ,0);//接收服務器端信息

         buf[len]='\0';

       printf("%s",buf); //打印服務器端信息

      

       /*循環的發送接收信息並打印接收信息--recv返回接收到的字節數,send返回發送的字節數*/

       while(1)

       {

              printf("Enter string to send:");

              scanf("%s",buf);

              if(!strcmp(buf,"quit"))

                     break;

              len=send(client_sockfd,buf,strlen(buf),0);

              len=recv(client_sockfd,buf,BUFSIZ,0);

              buf[len]='\0';

              printf("received:%s\n",buf);

       }

       close(client_sockfd);//關閉套接字

    return 0;

}
複製代碼

 

3.7 socket (UDP)編程實例

3.7.1 服務器端:udp_server.c

複製代碼
#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main(int argc, char *argv[])

{

       int server_sockfd;

       int len;

       struct sockaddr_in my_addr;   //服務器網絡地址結構體

         struct sockaddr_in remote_addr; //客戶端網絡地址結構體

       int sin_size;

       char buf[BUFSIZ];  //數據傳送的緩衝區

       memset(&my_addr,0,sizeof(my_addr)); //數據初始化--清零

       my_addr.sin_family=AF_INET; //設置爲IP通信

       my_addr.sin_addr.s_addr=INADDR_ANY;//服務器IP地址--允許連接到所有本地地址上

       my_addr.sin_port=htons(6001); //服務器端口號

      

       /*創建服務器端套接字--IPv4協議,面向無連接通信,UDP協議*/

       if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)

       { 

              perror("socket");

              return 1;

       }

 

        /*將套接字綁定到服務器的網絡地址上*/

       if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)

       {

              perror("bind");

              return 1;

       }

       sin_size=sizeof(struct sockaddr_in);

       printf("waiting for a packet...\n");

      

       /*接收客戶端的數據並將其發送給客戶端--recvfrom是無連接的*/

       if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct  sockaddr *)&remote_addr,&sin_size))<0)

       {

              perror("recvfrom");

              return 1;

       }

       printf("received packet from %s:\n",inet_ntoa(remote_addr.sin_addr));

       buf[len]='/0';

       printf("contents: %s\n",buf);

       close(server_sockfd);

    return 0;

}
複製代碼

 

3.7.2 客戶端:udp_client.c

複製代碼
#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main(int argc, char *argv[])

{

       int client_sockfd;

       int len;

        struct sockaddr_in remote_addr; //服務器端網絡地址結構體

       int sin_size;

       char buf[BUFSIZ];  //數據傳送的緩衝區

       memset(&remote_addr,0,sizeof(remote_addr)); //數據初始化--清零

       remote_addr.sin_family=AF_INET; //設置爲IP通信

       remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服務器IP地址

       remote_addr.sin_port=htons(6001); //服務器端口號

 

         /*創建客戶端套接字--IPv4協議,面向無連接通信,UDP協議*/

       if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)

       { 

              perror("socket");

              return 1;

       }

       strcpy(buf,"This is a test message");

       printf("sending: '%s'\n",buf);

       sin_size=sizeof(struct sockaddr_in);

      

       /*向服務器發送數據包*/

       if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0)

       {

              perror("recvfrom");

              return 1;

       }

       close(client_sockfd);

       return 0;

}
複製代碼

 

4       android下socket編程

在android下,socket編程主要有兩部分:一是本地(native)socket編程;另外一個是java中的socket編程。

4.1 本地(native)socket編程

android的本地方法實現中很多都繼承了linux的特性,所以在socket編程方面,很多都和上面講的linux下socket編程方法類似。值得一提的是android在線程間通信時,除了用binder機制或者framework層與native層間交換數據時用的jni這些常見的方法外,還可以用socket通訊,下面就主要來講講如何通過socket讓native與framework或者app之間進行通訊!

先提一下研究socket在線程間通訊的背景。目前正在做一個GPIO口的LED測試項目,原理比較簡單,就是每一個空着的GPIO口上都掛了一個LED燈,用來檢測GPIO口是否通路,內核中的驅動也比較簡單,不再提及,我們主要來看看HAL中是如何來處理與底層和framework或者app之間的關係的!

爲了繞開常規封裝HAL層SO庫,然後通過jni或者native java調用SO庫這種繁瑣的過程,我用HAL封裝成了一個可執行文件,然後在init.rc裏將這個可執行文件封裝成service,最後用socket關鍵字創建一個Uinx域的名爲/dev/socket/《service name》 的套接字,並傳遞它的文件描述符給已啓動的進程的方式啓動socket server端。

       先來看看這個可執行文件中的main函數:

複製代碼
int socketMain(int fd)

{

    int door_sock = -1;

 

       // Socket to listen on for incomming app connections

       if ((door_sock = android_get_control_socket(GPIOLED_SOCKET)) < 0)

       {

              LOGE("Obtaining file descriptor socket '%s' failed: %s",

                           GPIOLED_SOCKET, strerror(errno));

              exit(1);

       }

 

       //listen :backlog = have connected teams + will connect teams

       //隊列數的總和,在這裏只允許一個連接

       if (listen(door_sock, 1) < 0)

       {

              LOGE("Unable to listen on fd '%d' for socket '%s': %s",

                           door_sock, GPIOLED_SOCKET, strerror(errno));

              exit(1);

       }

 

       while(1)

       {

        fd_set read_fds;

        struct timeval to;

        int rc = 0;

 

              to.tv_sec = (60 * 60);

              to.tv_usec = 0;

             

              FD_ZERO(&read_fds);

              FD_SET(door_sock, &read_fds);

             

              if (gpioSock != -1)

                     FD_SET(gpioSock, &read_fds);

             

              if ((rc = select(door_sock + 1, &read_fds, NULL, NULL, &to)) < 0)

              {

                     LOGE("select() failed (%s)", strerror(errno));

                     sleep(1);

                     continue;

              }

             

              if (!rc)

                     continue;

             

              if (FD_ISSET(door_sock, &read_fds))

              {

                     struct sockaddr addr;

                     socklen_t alen;

             

                     alen = sizeof(addr);

             

                     if (gpioSock != -1)

                     {

                            LOGE("Dropping duplicate app connection");

                            int tmp = accept(door_sock, &addr, &alen);

                            close(tmp);

                            continue;

                     }

             

                     if ((gpioSock = accept(door_sock, &addr, &alen)) < 0)

                            LOGE("Unable to accept app connection (%s)",strerror(errno));

                    

                     LOGI("Accepted connection from app");

              }

             

              if (FD_ISSET(gpioSock, &read_fds))

              {

                     LOGI("process cmd from app");

                     if ((rc = processAppCommand(fd,gpioSock)) < 0)

                     {

                            if (rc == -ECONNRESET)

                            {

                                   LOGE("app disconnected");

                                   close(gpioSock);

                                   gpioSock = -1;

                            }

                            else

                                   LOGE("Error processing app command (%s)",strerror(errno));

                     }

                     else

                            sendMsgToApp(gpioSock);

              }

       }

}
複製代碼

 

這裏主要要說明的是如下這幾個函數:

·static inline int android_get_control_socket(const char *name)

它的解釋如下:

android_get_control_socket - simple helper function to get the file descriptor of our init-managed Unix domain socket. `name' is the name of the socket, as given in init.rc. Returns -1 on error.

這裏需要說明的是傳入的name是在init.rc裏面通過socket關鍵字修飾的字符串,在我的這個工程裏,init.rc裏有如下定義:

複製代碼
#Xandy add socket for gpio test

service gpiotest /system/bin/logwrapper /system/bin/gpiotest -s

              user root

              socket gpiotest stream 666
複製代碼

 

其中/system/bin/gpiotest這個文件就是前面提到的可執行文件!

·#include<sys/socket.h>

int listen(int sockfd, int backlog)

返回:0──成功, -1──失敗

摘要:listen函數使用主動連接套接口變爲被連接套接口,使得一個進程可以接受其它進程的請求,從而成爲一個服務器進程。在TCP服務器編程中listen函數把進程變爲一個服務器,並指定相應的套接字變爲被動連接。listen函數在一般在調用bind之後-調用accept之前調用。

參數sockfd

被listen函數作用的套接字,sockfd之前由socket函數返回。在被socket函數返回的套接字fd之時,它是一個主動連接的套接字,也就是此時系統假設用戶會對這個套接字調用connect函數,期待它主動與其它進程連接,然後在服務器編程中,用戶希望這個套接字可以接受外來的連接請求,也就是被動等待用戶來連接。由於系統默認時認爲一個套接字是主動連接的,所以需要通過某種方式來告訴系統,用戶進程通過系統調用listen來完成這件事。

參數backlog

這個參數涉及到一些網絡的細節。在進程正理一個一個連接請求的時候,可能還存在其它的連接請求。因爲TCP連接是一個過程,所以可能存在一種半連接的狀態,有時由於同時嘗試連接的用戶過多,使得服務器進程無法快速地完成連接請求。如果這個情況出現了,服務器進程希望內核如何處理呢?內核會在自己的進程空間裏維護一個隊列以跟蹤這些完成的連接但服務器進程還沒有接手處理或正在進行的連接,這樣的一個隊列內核不可能讓其任意大,所以必須有一個大小的上限。這個backlog告訴內核使用這個數值作爲上限。

毫無疑問,服務器進程不能隨便指定一個數值,內核有一個許可的範圍。這個範圍是實現相關的。很難有某種統一,一般這個值會小30以內。

當調用listen之後,服務器進程就可以調用accept來接受一個外來的請求。

·#include<sys/socket.h>

int accept(int sockfd, struct sockaddr* addr, socklen_t* len)

返回:非負描述字——成功, -1——失敗

摘要:對於服務器編程中最重要的一步等待並接受客戶的連接,那麼這一步在編程中如何完成,accept函數就是完成這一步的。它從內核中取出已經建立的客戶連接,然後把這個已經建立的連接返回給用戶程序,此時用戶程序就可以與自己的客戶進行點到點的通信了。

accept默認會阻塞進程,直到有一個客戶連接建立後返回,它返回的是一個新可用的套接字,這個套接字是連接套接字。此時我們需要區分兩種套接字,一種套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數之後,一個套接字會從主動連接的套接字變身爲一個監聽套接字;而accept返回是一個連接套接字,它代表着一個網絡已經存在的點點連接。自然要問的是:爲什麼要有兩種套接字?原因很簡單,如果使用一個描述字的話,那麼它的功能太多,使得使用很不直觀,同時在內核確實產生了一個這樣的新的描述字。

參數sockfd

參數sockfd就是上面解釋中的監聽套接字,這個套接字用來監聽一個端口,當有一個客戶與服務器連接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。

參數addr

這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,用戶應該知道這一個什麼樣的地址結構。如果對客戶的地址不感興趣,那麼可以把這個值設置爲NULL。

參數len

如同大家所認爲的,它也是結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的字節個數。同樣的,它也可以被設置爲NULL。

如果accept成功返回,則服務器與客戶已經正確建立連接了,此時服務器通過accept返回的套接字來完成與客戶的通信。

       至此,如果連接沒有問題,就可以用linux下的read/write來寫socket進行讀寫了!

4.2 java socket編程

關於java中的socket編程,這裏不再過多說明,網上類似的文章和資料已經夠多了,這裏主要提一下,用上面的方法建立的socket server端,如果java中的client端要對它進行正常的讀寫時,涉及的主要代碼如下:

複製代碼
LocalSocket ls =null;

LocalSocketAddress lsa;

ls = new LocalSocket();

lsa = new LocalSocketAddress(SOCKET_NAME,LocalSocketAddress.Namespace.RESERVED);

ls.connect(l);
複製代碼

要注意的是上面代碼中的SOCKET_NAME正是在前面init.rc裏面

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章