Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲

Socket

編輯:關於Android編程

關鍵詞

IP 地址 (IP Address)
IPv4(10進制): xxx.xxx.xxx.xxx IPv6(16進制): xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx 端口 (Port)
Web: 80 FTP: 21 SMTP: 25 協議 (Protocol)
TCP UDP 傳輸方式
SOCK_STREAM - TCP 准確傳輸 SOCK_DGRAM - UDP 高效但有一定幾率丟失 特殊 IP 地址
127.0.0.1 表示本機地址

Socket 交互流程

創建准備階段
服務器或客戶端: 使用 gethostbyname() 來通過域名獲取 IP 地址和端口 服務器: 創建 Socket 服務器: 使用 bind() 將套接字與特定的 IP 地址和端口綁定 客戶端: 創建 Socket 連接階段(UDP 不需要建立連接)
服務器: 使用 listen() 進入監聽狀態 服務器: 使用 accpet() 接收客服端的請求,返回客戶端 Socket。(如無請求,會阻塞程序進行等待) 客戶端: 使用 connect() 建立連接,並獲得服務器 Socket。 數據交互階段
服務器或客戶端: 使用 wirte() 對對方 Socket 進行數據寫入。 服務器或客戶端: 使用 read() 對對方 Socket 進行數據讀取。 UDP 情況下使用 sendto() 發送數據。 UDP 情況下使用 recvfrom() 接收數據。 關閉階段
服務器或客戶端: 使用 close() 關閉套接字 服務器或客戶端: 使用 shutdow() 關閉連接(但不會關閉套接字)

Socket 常用函數

socket 創建

int socket(int af, int type, int protocol)
返回 Socket 描述符 af: 地址類型
AF_INET: ipv4 地址 AF_INET6: ipv6 地址 type: 傳輸方式
SOCK_STREAM: 面向連接 (TCP) SOCK_DERAM: 無連接 (UDP) protocol: 協議
IPPROTO_TCP IPPTOTO_UDP
// 示例
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

bind() 綁定地址

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
返回值: 0 正常 / -1 錯誤 sock: Socket 文件描述符 addr: sockaddr 結構體變量指針(一般用 sockaddr_in / sockaddr_in6 強轉) addrlent: arrd 結構體大小(一般用 sizeof() 計算得出)
// 示例
//將創建的套接字與IP地址 127.0.0.1、端口 1234 綁定:
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//創建sockaddr_in結構體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));            //每個字節都用0填充
serv_addr.sin_family = AF_INET;                      //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
serv_addr.sin_port = htons(1234);                    //端口

//將套接字和IP、端口綁定 使用 sockaddr_in 強轉成 sockaddr
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

connect() 客戶端建立連接

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
返回值: 正常返回 0 sock: Socket serv_addr: addrlen:

listen() 服務器端進入監聽狀態

int listen(int sock, int backlog);
返回值: 正常返回 0 / 否則 -1 sock: backlog: 最大請求隊列長度
如果請求的時候隊列滿了,客戶端會收到 ECONNREFUSED。 監聽狀態下並不對客戶端做出響應,也不會堵塞線程

accept() 服務器在監聽狀態下獲取客戶端請求

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
返回值: 新的套接字,表示客戶端的套接字 sock: 服務器端的 Socket addr: 新建的用來接收地址信息的結構體指針 addrlen: 接收地址信息的結構體大小

accpet 會阻塞當前線程直到有新的請求到來。

write() 寫數據到緩沖區

ssize_t write(int fd, const void *buf, size_t nbytes);
返回值: 成功則返回字節數,否則返回 -1 fd: Socket buf: 寫入數據的緩沖區地址指針 nbytes: 寫入數據的字節數

數據只是寫入到緩沖區,但是什麼時候發送不由程序員控制。

read() 從緩沖區中讀取數據

ssize_t read(int fd, void *buf, size_t nbytes);
返回值: 成功則返回字節數,文件尾則返回 0,失敗返回 -1 fd: Socket buf: 用來接收數據的緩沖區地址指針 nbytes: 要讀取數據的字節數

只是讀取緩沖區數據

shutdown() 關閉連接

int shutdown(int sock, int howto);
返回值: 成功 0 / 失敗 -1 sock: Socket howto: 斷開方式
SHUT_RD: 斷開輸入流。無法接收數據了。 SHUT_WR: 斷開輸出流。無法發送數據了。 SHUT_RDWR: 同事斷開 I/O 流。相當於調用了 RD 和 WR

close() 關閉套接字

int close(int fd);
返回值: 成功 0 / 失敗 -1 fd: Socket

Socket 其他

TCP

簡單的在服務器端建立 Socket 並開始監聽,然後在客戶端進行連接並接收數據。

服務器端
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(){
    //創建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //將套接字和IP、端口綁定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //進入監聽狀態,等待用戶發起請求
    listen(serv_sock, 20);

    //接收客戶端請求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    //向客戶端發送數據
    char str[] = "Hello World!";
    write(clnt_sock, str, sizeof(str));

    //關閉套接字
    close(clnt_sock);
    close(serv_sock);
    return 0;
}
客戶端
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    //創建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    //向服務器(特定的IP和端口)發起請求
    struct sockaddr_in serv_addr;

    memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //讀取服務器傳回的數據
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);

    printf("Message form server: %s\n", buffer);

    //關閉套接字
    close(sock);
    return 0;
}

UDP

創建一個 UDP 連接的 Socket,服務器不斷的接收客戶端的消息,然後返回回去。

服務器
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 100

int main(){
    // 創建套接字
    int serv_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 綁定套接字
    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = PF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動獲取 ip 地址
    serv_addr.sin_port = htons(1234);
    bind(serv_sock, (sockaddr *)&serv_addr, sizeof(sockaddr));

    // 接收客戶端請求
    sockaddr clin_addr;
    socklen_t clin_size = sizeof(sockaddr);
    char buffer[BUF_SIZE];

    while (1) {
        int str_len = recvfrom(serv_sock, buffer, BUF_SIZE, 0, &clin_addr, &clin_size);
        printf("Message form clinet: %s\n", buffer);
        sendto(serv_sock, buffer, str_len, 0, &clin_addr, clin_size);
    }

    close(serv_sock);
    return 0;
}
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 100

int main() {

    // 創建套接字
    int clin_sock = socket(PF_INET, SOCK_DGRAM, 0);

    // 服務器地址
    sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = PF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(1234);

    //不斷獲取用戶輸入並發送給服務器,然後接受服務器數據
    sockaddr fromAddr;
    socklen_t addrLen = sizeof(fromAddr);
    while(1){
        char buffer[BUF_SIZE] = {0};
        printf("Input a string: ");
        gets(buffer);
        sendto(clin_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        int strLen = recvfrom(clin_sock, buffer, BUF_SIZE, 0, &fromAddr, &addrLen);
        buffer[strLen] = 0;
        printf("Message form server: %s\n", buffer);
    }

    close(clin_sock);
    return 0;
}

緩沖區

I/O 緩沖區在每個 TCP Socket 中單獨存在 I/O 緩沖區在創建 Socket 時自動生成。 關閉 Socket 也會繼續輸出緩沖區中遺留的數據 關閉 Socket 會丟失輸入緩沖區中的數據。 默認緩沖區大小是 8K,可以通過 getsockopt() 函數獲取
// 示例
unsigned optVal;
int optLen = sizeof(int);
getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
printf("Buffer length: %d\n", optVal);

TCP Socket 堵塞模式

write()
檢查緩沖區,如果緩沖區的可用空間長度小於要發送的數據,那麼 write() 會被阻塞,直到緩沖區中的數據被發送到目標機器,騰出足夠的空間,才繼續寫入數據。 如果 TCP 協議正在向網絡發送數據,那麼輸出緩沖區會被鎖定,不允許寫入,直到數據發送完畢緩沖區解鎖,才被喚醒。 如果要寫入的數據大於緩沖區的最大長度,那麼將分批寫入。 直到所有數據被寫入緩沖區 write() 才能返回。 read()
檢查緩沖區,如果緩沖區中有數據,那麼就讀取,否則函數會被阻塞,直到網絡上有數據到來。 如果要讀取的數據長度小於緩沖區中的數據長度,那麼就不能一次性將緩沖區中的所有數據讀出,剩余數據將不斷積壓,直到再次讀取。 直到讀取到數據後 read() 函數才會返回,否則就一直被阻塞。

Socket 常用數據結構

include

sockaddr - 通用 Ip 地址結構體

struct sockaddr {
    __uint8_t   sa_len;     
    sa_family_t sa_family;  
    char        sa_data[14];    
};
sa_len: 結構體總長度 sa_family: 地址族
AF_INET: ipv4 地址 AF_INET6: ipv6 地址 char: IP 地址和端口號

sockaddr_in - Ipv4 地址結構

struct sockaddr_in {
    __uint8_t   sin_len;
    sa_family_t sin_family;
    in_port_t   sin_port;
    struct  in_addr sin_addr;
    char        sin_zero[8];
};
sin_len: 結構體長度 sin_family: 地址族(一般是 AF_INET) sin_port: 16位端口號,需要用 htons() 進行轉換 sin_addr: in_addr 類型的結構體,包含一個 32 位的 ip 地址,定義在

sockaddr_in6 - Ipv6 地址結構

struct sockaddr_in6 {
    __uint8_t   sin6_len;   
    sa_family_t sin6_family;
    in_port_t   sin6_port;  
    __uint32_t  sin6_flowinfo;  
    struct in6_addr sin6_addr;  
    __uint32_t  sin6_scope_id;  
};
sin6_len: 結構體長度 sin6_family: 地址族(一般是 AF_INET6) sin6_port: 16 位端口號,需要用 htons() 進行轉換 sin6_flowinfo: Ipv6 流信息 sin6_addr: Ipv6 地址 sin6_scope_id: 接口范圍 id

in_addr - Ipv4 地址

struct in_addr {
    in_addr_t s_addr;
};

in6_addr - Ipv6 地址

struct in6_addr {
    union {
        __uint8_t   __u6_addr8[16];
        __uint16_t  __u6_addr16[8];
        __uint32_t  __u6_addr32[4];
    } __u6_addr;            /* 128-bit IP6 address */
};
``
## hostent - 通過域名獲取的 ip 地址結構

include

* h_name:官方域名
* h_aliases:別名, 可以通過多個域名訪問同一主機
* h_addrtype:地址族, IPv4 對應 AF_INET, IPv6 對應 AF_INET6
* h_length:保存IP地址長度. IPv4 的長度為4個字節,IPv6 的長度為16個字節
* h_addr_list:以整數形式保存域名對應的IP地址, 可能會分配多個IP地址
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved