Socket 編程 小知識庫


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);

socket選項對應表
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.



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