1. 阻塞描述符(有文件描述符和Socket描述符)。調用 函數(read/write/recv/recvfrom/recvmsg)返回的時間。非阻塞是不管有沒有數據,馬上返回。阻塞是要等到有數據的時候再返回。Linux/Unix/Windows默認都是阻塞的。
(1)在Unix類系統設置
設置非阻塞:
int flags = fcntl(sockfd,F_GETFL) |
O_NONBLOCK;
fcntl(sockfd,F_SETFL,flags);
設置阻塞:
int flags = fcntl(sockfd,F_GETFL) & (~
O_NONBLOCK);
fcntl(sockfd,F_SETFL,flags);
(2)在Windows中設置
設置非阻塞
unsigned long isBlocked = 1;
ioctlsocket(s, FIONBIO, (unsigned long*)&isBlocked);
設置阻塞
unsigned long isBlocked = 0;
ioctlsocket(s, FIONBIO, (unsigned long*)&isBlocked);
2. Socket常用選項配置。
int setsockopt (int s, int level, int optname, const void *optval, socklen_t optlen);
level | optname | optval type |
---|---|---|
SOL_SOCKET | SO_RCVBUF/ SO_SNDBUF | int |
設置socket接受發送數據緩衝區大小,單位是字節。如果不希望系統用發送緩衝區, 即在調用send/sendto之後,直接發送數據,可設置size = 0; int size = 32 * 1024; // 32KB. setsockopt(sockfd, SOL_SOCKET,SO_RCVBUF, &size, sizeof(size)); setsockopt(sockfd, SOL_SOCKET,SO_SNDBUF, &size, sizeof(size)); |
||
SOL_SOCKET | SO_RCVTIMEO / SO_SNDTIMEO | struct timeval |
設置socket接收發送數據超時. struct timeval tm = {0}; tm.tv_sec = N; // N seconds. tm.tv_usec = M_us; //N us. 1000000us = 1s setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)); |
||
SOL_SOCKET | SO_REUSEADD / SO_REUSEPORT | int(邏輯布爾) |
SO_REUSEADDR可以用在以下四種情況下。(摘自《Unix網絡編程》卷一,即UNPv1) (1)、當有一個有相同本地地址和端口的socket1處於TIME_WAIT狀態時,而你啓動的程序的socket2要佔用該地址和端口,你的程序就要用到該選項。 (2)、SO_REUSEADDR允許同一port上啓動同一服務器的多個實例(多個進程)。但每個實例綁定的IP地址是不能相同的。在有多塊網卡或用IP Alias技術的機器可以測試這種情況。 (3)、SO_REUSEADDR允許單個進程綁定相同的端口到多個socket上,但每個socket綁定的ip地址不同。這和2很相似,區別請看UNPv1。 (4)、SO_REUSEADDR允許完全相同的地址和端口的重複綁定。但這隻用於UDP的多播,不用於TCP。 只考慮AF_INET的情況(同一端口指ip地址與端口號都相同) 1.freebsd支持SO_REUSEPORT和SO_REUSEADDR選項,而linux只支持SO_REUSEADDR選項。 2.freebsd下,使用SO_REUSEPORT選項,兩個tcp的socket可以綁定同一個端口;同樣,使用SO_REUSEPORT選項,兩個udp的socket可以綁定同一個端口。 3.linux下,兩個tcp的socket不能綁定同一個端口;而如果使用SO_REUSEADDR選項,兩個udp的socket可以綁定同一個端口。 4.freebsd下,兩個tcp的socket綁定同一端口,只有第一個socket獲得數據。 5.freebsd下,兩個udp的socket綁定同一端口,如果數據包的目的地址是單播地址,則只有第一個socket獲得數據,而如果數據包的目的地址是多播地址,則兩個socket同時獲得相同的數據。 6.linux下,兩個udp的socket綁定同一端口,如果數據包的目的地址是單播地址,則只有最後一個socket獲得數據,而如果數據包的目的地址是多播地址,則兩個socket同時獲得相同的數據。 有些系統如Linux,沒有定義SO_REUSEPORT,可自行加上。 #define SO_REUSEPORT 15 ) int usable = 1; setsockopt(sockfd, SOL_SOCKET ,SO_REUSEADDR, &usable , sizeof(usable )); setsockopt(sockfd, SOL_SOCKET ,SO_REUSEPORT, &usable , sizeof(usable )); |
||
SOL_SOCKET | SO_BROADCAST (一般用於UDP.) | int(邏輯布爾) |
希望該socket發送的數據具有廣播特性。 int bBroadcast= 1; setsockopt(sock,SOL_SOCKET,SO_BROADCAST, &bBroadcast, sizeof(BOOL)); |
||
SOL_SOCKET | SO_LINGER | struct linger { u_short l_onoff; //linger開關 u_short l_linger; //等待時間,單位:秒}; |
延緩面向連接的socket的close操作。默認,close立即返回,但是當發送緩衝區中還有一部分數據的時候,系統將會嘗試將數據發送給對端。SO_LINGER可以改變close的行爲,即處於連接狀態的soket在調用close socket後強制關閉,不經歷TIME_WAIT(TCP)的過程。這個選項需要謹慎使用,尤其是強制式關閉,會丟失服務器發給客戶端的最後一部分數據。 struct linger llinger = {1, 5}; //1:打開linger選項,停留時間爲5秒 setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&llinger,sizeof(llinger)); |
||
SOL_SOCKET | SO_DONTLINGER | int(邏輯布爾) |
如果要已經處於連接狀態的soket在調用close socket後強制關閉,不經歷TIME_WAIT的過程。 setsockopt(s,SOL_SOCKET,SO_DONTLINGER,&bDontLinger,sizeof(int)); |
||
SOL_SOCKET | SO_CONDITIONAL_ACCEPT | int(邏輯布爾) |
在client連接服務器過程中,如果處於非阻塞模式下的socket在connect()的過程中可以設置connect()延時,直到accpet()被呼叫(本函數設置只有在非阻塞的過程中有顯著的作用,在阻塞的函數調用中作用不大) int bConditionalAccept=1; setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,&bConditionalAccept,sizeof(int )); |
||
SOL_SOCKET IPPROTO_TCP IPPROTO_TCP IPPROTO_TCP |
SO_KEEPALIVE TCP_KEEPCNT TCP_KEEPIDLE TCP_KEEPINTVL |
int(bool) int int(單位:500ms) int(單位:500ms) |
如果一方已經關閉或異常終止連接,而另一方卻不知道,我們將這樣的TCP連接稱爲半打開的。TCP通過保活定時器(KeepAlive)來檢測半打開連接。在高併發的網絡服務器中,經常會出現漏掉socket的情況,對應的結果有一種情況就是出現大量的CLOSE_WAIT狀態的連接。這個時候,可以通過設置KEEPALIVE選項來解決這個問題。設置SO_KEEPALIVE選項來開啓KEEPALIVE,然後通過TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT設置keepalive的開始時間、間隔、次數。
也可通過/proc/sys/net/ipv4/tcp_keepalive_time、tcp_keepalive_intvl和tcp_keepalive_probes修改,只是是整個系統都會隨之改變。 TCP_KEEPCNT: 關閉一個非活躍連接之前進行探測的最大次數。默認爲 8 次 TCP_KEEPINTVL:兩個探測的時間間隔,默認值爲 150 即 75 秒。 TCP_KEEPIDLE:對一個連接進行有效性探測之前運行的最大非活躍時間間隔,默認值爲 14400(即 2 個小時) 。 int keepalive = 1; //開啓keepalive setsockopt(incomingsock,SOL_SOCKET, SO_KEEPALIVE,&keepalive, sizeof(keepalive)); int max_idle_time = 28800; //當socket處於IDLE時,4小時內要有真實數據通信. setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE,&start_time ,sizeof(int)); int interval = 300; //間隔時間爲150秒, 每150秒心跳一次。 setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &interval , sizeof(interval)); int probes= 8; //最大探測次數爲8 setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &probes, sizeof(probes)); |
||
IPPROTO_TCP | TCP_NODELAY / TCP_CHORK | int(邏輯布爾) |
TCP_NODELAY 不使用Nagle算法,不會將小包進行拼接成大包再進行發送,直接將小包發送出去,會使得小包時候用戶體驗非常好。 當在傳送大量數據的時候,爲了提高TCP發送效率,可以設置TCP_CORK,CORK顧名思義,就是"塞子"的意思,它會盡量在每次發送最大的數據量。當設置了TCP_CORK後,會有阻塞200ms,當阻塞時間過後,數據就會自動傳送。也是禁用了Nagle化。 #<netinet/tcp.h> int enable = 1; setsockopt(s,IPPROTO_TCP,TCP_NODELAY, &enable,sizeof(enable)); setsockopt(s,IPPROTO_TCP,TCP_CHORK, &enable,sizeof(enable)); |
||
IPPROTO_TCP | TCP_DEFER_ACCEPT | int(單位:秒) |
推遲accept,實際上是當接收到第一個數據之後,纔會創建連接。kernel 在到達設置的秒數以後還沒有收到數據,不會繼續喚醒進程,而是直接丟棄連接。如果服務器設置TCP_DEFER_ACCEPT選項後,服務器受到一個CONNECT請求後,三次握手之後,新的socket狀態依然爲SYN_RECV,而不是ESTABLISHED,操作系統不會Accept。 由於設置TCP_DEFER_ACCEPT選項之後,三次握手後狀態沒有達到ESTABLISHED,而是SYN_RECV。這個時候,如果客戶端一直沒有發送"數據"報文,服務器將重傳SYN/ACK報文,重傳次數受net.ipv4.tcp_synack_retries參數控制,達到重傳次數之後,纔會再次進行setsockopt中設置的超時值,因此會出現SYN_RECV生存時間比設置值大一些的情況。 int max_seconds = 5; setsockopt(s, TCP_DEFER_ACCEPT, &max_seconds , sizeof(max_seconds )); |
||
IPPROTO_IP | IP_HDRINCL | int(邏輯布爾) |
設置混雜模式。讀數據的時候,連IP頭一起在緩衝區裏面,sendto的時候,要自己設置IP頭。一般用於 SOCK_RAW. char buffer[PCKT_LEN]; struct ipheader *iphdr = (struct ipheader *) buffer; struct udpheader *udp = (struct udpheader *) (buffer + sizeof(struct ipheader)); int flag = 1; setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int)); recvfrom(sockfd, buffer, &len, 0, &addr, &addr_eln); iphdr->.....; sendto(sockfd, buffer, &len, 0, &addr, addr_eln); |
||
IPPROTO_IP | IP_TOS | int(邏輯布爾) |
一般不用。部分系統不支持。 | ||
IPPROTO_IP | IP_TTL | int(邏輯布爾) |
一般不用。部分系統不支持。 | ||
IPPROTO_IP | IP_OPTINOS | int(邏輯布爾) |
一般不用。部分系統不支持。 |
3. 判斷對方 TCP socket / UDP socket bound destination address是否關閉。
if(recv(sockfd, buf, 100) == 0) {
......
}
4. linux/epoll 函數.
ET / LT 工作模式:
ET模式僅當狀態發生變化的時候才獲得通知,這裏所謂的狀態的變化並不包括緩衝區中還有未處理的數據,也就是說,如果要採用ET模式,需要一直read/write直到出錯爲止,很多人反映爲什麼採用ET模式只接收了一部分數據就再也得不到通知了,大多因爲這樣;而LT模式是只要有數據沒有處理就會一直通知下去的.
#include <sys/epoll.h>
int epoll_create(int maxfdnum);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
int close(int epfd);
epfd: 是epoll_create返回的描述符。
op: EPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL
fd: 要操作的描述符(這裏就是socket 號)。
event: struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
maxevents: 必須小於等於maxfdnum。是監聽的最大事件數。
events: 其實類型是struct epoll_event events[].
IN, 要監聽的事件集合。
OUT, 發生事件的集合。 返回值是 集合的個數。 當返回 0 爲超時,表示沒有感興趣的事件發生。
timeout: 超時時間,單位是毫秒。 -1爲無限的不確定的等待。可以理解爲阻塞的意思。
int count = epoll_wait(epfd, event, 12, -1);
int index = 0;
for(index = 0; index < count; index++) {
event[index].fd; //這個就是socket 套接字。
.........
}
5. 增加最大描述符數。我們都知道,通常一個終端下最多只能有1024個描述符,還有0,1,2默認被佔用,加上設備/文件,有時候還真的不夠用。怎麼增加呢?
軟限制/硬限制: 軟限制是指內核所能支持的資源上限。硬限制只是作爲軟限制的上限。軟限制不能超過硬限制。
命令設置: ulimit -n
ulimit -n 10000
關閉當前終端配置將自動失效。
編程設置:getrlimit,setrlimit/ RLIMIT_NOFILE
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlp);
int setrlimit(int resource, const struct rlimit *rlp);
int fun(int max) {
struct rlimit limit = {0};
getrlimit(RLIMIT_NOFILE, &limit );
limit.rlim_cur =
limit.rlim_max;
limit.rlim_max = max;
setrlimit(RLIMIT_NOFILE, &limit );
return 0;
}
另外,還有RLIMIT_CPU / RLIMIT_STACK / RLIMIT_NPROC.