Socket 编程 小知识库

来源:互联网 发布:年轻人不愿生了知乎 编辑:程序博客网 时间:2024/06/07 20:32

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选项对应表leveloptnameoptval typeSOL_SOCKETSO_RCVBUF/ SO_SNDBUFint设置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_SOCKETSO_RCVTIMEO / SO_SNDTIMEOstruct 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_SOCKETSO_REUSEADD / SO_REUSEPORTint(逻辑布尔)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_SOCKETSO_BROADCAST  (一般用于UDP.)int(逻辑布尔)希望该socket发送的数据具有广播特性。
int bBroadcast= 1;
setsockopt(sock,SOL_SOCKET,SO_BROADCAST, &bBroadcast, sizeof(BOOL));SOL_SOCKETSO_LINGERstruct 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_SOCKETSO_DONTLINGERint(逻辑布尔)如果要已经处于连接状态的soket在调用close socket后强制关闭,不经历TIME_WAIT的过程。
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,&bDontLinger,sizeof(int));SOL_SOCKETSO_CONDITIONAL_ACCEPTint(逻辑布尔)在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_TCPSO_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_TCPTCP_NODELAY / TCP_CHORKint(逻辑布尔)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_TCPTCP_DEFER_ACCEPTint(单位:秒)推迟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_IPIP_HDRINCLint(逻辑布尔)设置混杂模式。读数据的时候,连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_IPIP_TOSint(逻辑布尔)一般不用。部分系统不支持。IPPROTO_IPIP_TTLint(逻辑布尔)一般不用。部分系统不支持。IPPROTO_IPIP_OPTINOSint(逻辑布尔)一般不用。部分系统不支持。


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.



原创粉丝点击