1. 程式人生 > >網路程式設計學習筆記(九)套接字的多種可選項

網路程式設計學習筆記(九)套接字的多種可選項

套接字多種可選項

我們之前寫的程式都是建立好套接字後(未經特別操作)直接使用的,此時通過預設的套接字特性進行資料通訊。之前的示例較為簡單,無需特別操作套接字特性,但有時的確需要更改。

協議層 選項名 讀取 設定
SOL_SOCKET SO_SNDBUF
SOL_SOCKET SO_RCVBUF
SOL_SOCKET SO_REUSEADDR
SOL_SOCKET SO_KEEPALIVE
SOL_SOCKET SO_BROADCAST
SOL_SOCKET SO_DONTROUTE
SOL_SOCKET SO_OOBINLINE
SOL_SOCKET SO_ERROR
SOL_SOCKET SO_TYPE
協議層 選項名 讀取 設定
IPPROTO_IP IP_TOS
IPPROTO_IP IP_TTL
IPPROTO_IP IP_MULTICAST_TTL
IPPROTO_IP IP_MULTICAST_LOOP
IPPROTO_IP IP_MULTICAST_IF
協議層 選項名 讀取 設定
IPPROTO_TCP TCP_KEEPALIVE
IPPROTO_TCP TCP_NODELAY
IPPROTO_TCP TCP_MAXSEG

從表中可以看出,套接字可選項是分層的。IPPROTO_IP層可選項是IP協議相關事項,IPPROTO_TCP層可選項是TCP協議相關事項,SOL_SOCKET層是套接字相關的通用可選項。

getsockopt&setsockopt

我們幾乎可以針對表中的所有可選項進行讀取(Get)和設定(Set),可選項的讀取和設定通過如下兩個函式完成。

#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void *optval, 
                                                socklen_t*optlen);
//sock     用於檢視選向套接字檔案描述符
//level    要檢視的可選項的協議層
//optname  要檢視的可選項名
//optval   儲存檢視結果的緩衝地址值
//optlen   向第4個引數optval傳遞的緩衝大小。呼叫函式後,該變數中儲存通過第四個引數返回的可選項資訊的位元組數
//成功時返回0,失敗時返回-1                        

下面介紹更改可選項時呼叫的函式

#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void*optval, socklen_t optlen);
變數對應上面函式,不過這次是用於更改的值。

下面介紹這些函式的呼叫方法。
sock_type.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int tcp_sock, udp_sock;
    int sock_type;
    socklen_t optlen;
    int state;

    optlen = sizeof(sock_type);
    tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
    udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
    printf("SOCK_STREAM: %d \n", SOCK_STREAM);
    printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);

    state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if(state)
        error_handling("getsockopt() error!");
    printf("Socket type one: %d \n",sock_type);

    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if(state)
        error_handling("getsockopt() error!");
    printf("Socket type two: %d \n",sock_type);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

套接字型別只能在建立時決定, 以後不能再更改。

SO_SNDBUF & SO_RCVBUF

前面介紹過,建立套接字將同時生成I/O緩衝。SO_REVBUF是輸入緩衝大小相關可選項,也可以進行更改。通過下列示例讀取建立套接字時預設的I/O緩衝大小。
get_buf.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    int snd_buf, rcv_buf, state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if(state)
        error_handling("getsockopt() error");

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&rcv_buf, &len);
    if(state)
        error_handling("getsockopt() error");

    printf("Input buffer size: %d \n",rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

接下來程式中將更改I/O緩衝大小
set_buf.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>

#define BUF_SIZE 3096
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    int snd_buf = BUF_SIZE, rcv_buf = BUF_SIZE, state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
    if(state)
        error_handling("getsockopt() error1");


    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
    if(state)
        error_handling("getsockopt() error2");

    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if(state)
        error_handling("getsockopt() error3");

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if(state)
        error_handling("getsockopt() error4");

    printf("Input buffer size: %d \n",rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

緩衝大小的設定須謹慎處理,因此不會完全按照我們的要求進行。

SO_REUSEADDR

重點是可選項SO_REUSEADDR及其相關的Time-wait狀態。
Time-wait狀態,四次揮手過程中,先斷開連線的主機經過Time-wait狀態才結束。所以若伺服器先斷開連線,則無法立即重新執行。套接字處在Time-wait過程時,相應埠還是正在使用的狀態。因此bind函式呼叫過程中當然會發生錯誤。
Time-wait也有很大的缺點,如果網路狀態不理想,那麼Time-wait狀態有可能會持續較長一段時間。解決方案就是在套接字的可選項中更改SO_REUSEADDR的狀態。適當調整引數,可將Time-wait狀態下的套接字埠號重新分配給新的套接字。
程式碼實現實際上就是3句話:
SO_REUSEADDR預設值為0(假,此時無法重新分配),我們將其設定為真。

optlen = sizeof(option);
option = TRUE;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*) &option, optlen);

TCP_NODELAY

Nagle演算法

這個演算法是為防止資料包過多而發生網路過載而在1984就誕生了。應用於TCP層,很簡單。
簡單描述就是:只有收到前一資料的ACK訊息時,Nagle演算法才傳送下一資料。
TCP套接字預設使用Nagle演算法交換資料,因此最大限度地進行緩衝,直到收到ACK。
一般情況下,不使用Nagle演算法可以提高傳輸速度。但如果無條件放棄使用Nagle演算法,就會增加過多的網路流量,反而會影響傳輸。

禁用Nagle演算法

禁用的典型情景就是“傳輸大檔案資料”。Nagle演算法使用與否在網路流量上差別不大時,使用Nagle演算法的傳輸速度更慢。禁用方法非常簡單,只需將套接字可選項TCP_NODELAY改為1(真)即可。

int optval = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val,sizeof(opt,val));

基於windows的實現

#include <winsock2.h>
int getsockopt(SOCKET sock, int level, int optname, char *optval, int *optlen);
//可以看到,除了optval型別變成char指標外,與Linux中的getsockopt函式相比並無太大區別。
//成功時返回0,失敗時返回SOCKET_ERROR
#include <winsock2.h>
int setsockopt(SOCKET sock, int level, int optname, 
                                const char * optval, int optlen);
//成功時返回0,失敗時返回SOCKET_ERROR