網路程式設計學習筆記(九)套接字的多種可選項
套接字多種可選項
我們之前寫的程式都是建立好套接字後(未經特別操作)直接使用的,此時通過預設的套接字特性進行資料通訊。之前的示例較為簡單,無需特別操作套接字特性,但有時的確需要更改。
協議層 | 選項名 | 讀取 | 設定 |
---|---|---|---|
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