精华内容
下载资源
问答
  • linux TCP

    万次阅读 2011-05-16 09:52:00
    LinuxTCP网络服务器实现源代码 2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博 站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航...

    Linux下TCP网络服务器实现源代码
    2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博
    站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航=IDC123.COM

    大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务。这里附上源代码来个小结吧。

    首先,循环网络服务器编程实现的步骤是这样的:

    这种服务器模型是典型循环服务,如果不加上多进程/线程技术,此种服务吞吐量有限,大家都可以看到,如果前一个连接服务数据没有收发完毕后面的连接没办法处理。所以一般有多进程技术,对一个新连接启用一个新进程去处理,而监听socket继续监听。

    /************关于本文档********************************************
    *filename:Linux下各类TCP网络服务器的实现源代码
    *purpose:记录Linux下各类tcp服务程序源代码
    *wroteby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0422:00:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *********************************************************************/

    一个循环TCP服务源代码(因为用fork进行多进程服务了,所以这种服务现实中也有用)如下:

    /*----------------------源代码开始--------------------------------------------*/
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<sys/socket.h>
    #include<sys/wait.h>
    /*********************************************************************
    *filename:cycletcpserver.c
    *purpose:循环tcp服务端程序
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0422:00:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:Google.com
    *********************************************************************/
    intmain(intargc,char**argv)
    {
    intsockfd,new_fd;/*监听socket:sock_fd,数据传输socket:new_fd*/
    structsockaddr_inmy_addr;/*本机地址信息*/
    structsockaddr_intheir_addr;/*客户地址信息*/
    unsignedintsin_size,myport,lisnum;

    if(argv[1])myport=atoi(argv[1]);
    elsemyport=7838;

    if(argv[2])lisnum=atoi(argv[2]);
    elselisnum=2;

    if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1){
    perror("socket");
    exit(1);
    }
    my_addr.sin_family=PF_INET;
    my_addr.sin_port=htons(myport);
    my_addr.sin_addr.s_addr=INADDR_ANY;
    bzero(&(my_addr.sin_zero),0);
    if(bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr))==-1){
    perror("bind");
    exit(1);
    }

    if(listen(sockfd,lisnum)==-1){
    perror("listen");
    exit(1);
    }
    while(1){
    sin_size=sizeof(structsockaddr_in);
    if((new_fd=accept(sockfd,(structsockaddr*)&their_addr,&sin_size))==-1){
    perror("accept");
    continue;
    }
    printf("server:gotconnectionfrom%s/n",inet_ntoa(their_addr.sin_addr));
    if(!fork()){/*子进程代码段*/
    if(send(new_fd,"Hello,world!/n",14,0)==-1){
    perror("send");
    close(new_fd);
    exit(0);
    }
    }
    close(new_fd);/*父进程不再需要该socket*/
    waitpid(-1,NULL,WNOHANG);/*等待子进程结束,清除子进程所占用资源*/
    }
    }
    /*----------------------源代码结束--------------------------------------------*/

    一个测试客户端代码如下:

    /*----------------------源代码开始--------------------------------------------*/
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<netdb.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<sys/socket.h>
    #defineMAXDATASIZE100/*每次最大数据传输量*/
    /*********************************************************************
    *filename:cycletcpclient.c
    *purpose:循环tcp客户端程序
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0422:20:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:Google.com
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *********************************************************************/

    intmain(intargc,char*argv[])
    {
    intsockfd,numbytes;
    charbuf[MAXDATASIZE];
    structhostent*he;
    structsockaddr_intheir_addr;
    unsignedintmyport;

    if(argv[2])myport=atoi(argv[2]);
    elsemyport=7838;

    if(argc!=3){
    fprintf(stderr,"usage:%shostnameport/n",argv[0]);
    exit(1);
    }
    if((he=gethostbyname(argv[1]))==NULL){
    herror("gethostbyname");
    exit(1);
    }
    if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1){
    perror("socket");
    exit(1);
    }
    their_addr.sin_family=PF_INET;
    their_addr.sin_port=htons(myport);
    their_addr.sin_addr=*((structin_addr*)he->h_addr);
    bzero(&(their_addr.sin_zero),0);
    if(connect(sockfd,(structsockaddr*)&their_addr,sizeof(structsockaddr))==-1){
    perror("connect");
    exit(1);
    }
    if((numbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1){
    perror("recv");
    exit(1);
    }
    buf[numbytes]=0;
    printf("Received:%s/n",buf);
    close(sockfd);
    return0;
    }
    /*----------------------源代码结束--------------------------------------------*/

    用gcccycletcpserver.c-otcpserver和gcccycletcpclient.c-otcpclient分别编译上述代码后运行情况如下:
    服务端运行显示:

    administrator@ubuzlf:/data/example/c$./tcpserver
    server:gotconnectionfrom127.0.0.1
    server:gotconnectionfrom127.0.0.1
    server:gotconnectionfrom127.0.0.1

    客户端运行显示:

    administrator@ubuzlf:/data/example/c$./tcpclient127.0.0.17838
    Received:Hello,world!

    administrator@ubuzlf:/data/example/c$./tcpclient127.0.0.17838
    Received:Hello,world!

    administrator@ubuzlf:/data/example/c$./tcpclient127.0.0.17838
    Received:Hello,world!
    Linux下TCP网络服务器实现源代码(2)
    2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博
    站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航=IDC123.COM



    不得不说的一个概念性问题:阻塞与非阻塞
    在阻塞服务中,当服务器运行到accept语句而没有客户连接服务请求到来,那么会发生什么情况?这时服务器就会停止在accept语句上等待连接服务请求的到来;同样,当程序运行到接收数据语句recv时,如果没有数据可以读取,则程序同样会停止在接收语句上。这种情况称为阻塞(blocking)。
    但如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接;否则就继续做其他事情,则可以通过将socket设置为非阻塞方式来实现:非阻塞socket在没有客户在等待时就使accept调用立即返回。
    通过设置socket为非阻塞方式,可以实现“轮询”若干socket。当企图从一个没有数据等待处理的非阻塞socket读入数据时,函数将立即返回,并且返回值置为-1,并且errno置为EWOULDBLOCK。但是这种“轮询”会使CPU处于忙等待方式,从而降低性能。考虑到这种情况,假设你希望服务器监听连接服务请求的同时从已经建立的连接读取数据,你也许会想到用一个accept语句和多个recv()语句,但是由于accept及recv都是会阻塞的,所以这个想法显然不会成功。
    调用非阻塞的socket会大大地浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。

    其次,并发服务器,在上述cycletcpserver.c中,由于使用了fork技术也可以称之为并发服务器,但这种服务器并不是真正意义上的IO多路复用的并发服务器,并且由于没有处理阻塞问题,实际应用有各种各样的问题。

    一个典型IO多路复用的单进程并发服务器流程如下:
    /*IO多路复用并发服务流程图*/

    下面是一个演示IO多路复用的源程序,是一个端口转发程序,但它的用处相当大,实际应用中的各类代理软件或端口映射软件都是基于这样的代码的,比如Windows下的WinGate、WinProxy等都是在此基础上实现的。源代码如下:

    /*----------------------源代码开始--------------------------------------------*/
    #include<stdlib.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/time.h>
    #include<sys/types.h>
    #include<string.h>
    #include<signal.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<errno.h>

    staticintforward_port;

    #undefmax
    #definemax(x,y)((x)>(y)?(x):(y))

    /*************************关于本文档************************************
    *filename:tcpforwardport.c
    *purpose:演示了select的用法,这是一个极好的代理软件核心,专门作端口映射用
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0519:00:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *********************************************************************/

    staticintlisten_socket(intlisten_port){
    structsockaddr_ina;
    ints;
    intyes;
    if((s=socket(AF_INET,SOCK_STREAM,0))<0){
    perror("socket");
    return-1;
    }
    yes=1;
    if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes))<
    0){
    perror("setsockopt");
    close(s);
    return-1;
    }
    memset(&a,0,sizeof(a));
    a.sin_port=htons(listen_port);
    a.sin_family=AF_INET;
    if(bind(s,(structsockaddr*)&a,sizeof(a))<0){
    perror("bind");
    close(s);
    return-1;
    }
    printf("acceptingconnectionsonport%d/n",(int)listen_port);
    listen(s,10);
    returns;
    }

    staticintconnect_socket(intconnect_port,char*address){
    structsockaddr_ina;
    ints;
    if((s=socket(AF_INET,SOCK_STREAM,0))<0){
    perror("socket");
    close(s);
    return-1;
    }

    memset(&a,0,sizeof(a));
    a.sin_port=htons(connect_port);
    a.sin_family=AF_INET;

    if(!inet_aton(address,(structin_addr*)&a.sin_addr.s_addr)){
    perror("badIPaddressformat");
    close(s);
    return-1;
    }

    if(connect(s,(structsockaddr*)&a,sizeof(a))<0){
    perror("connect()");
    shutdown(s,SHUT_RDWR);
    close(s);
    return-1;
    }
    returns;
    }

    #defineSHUT_FD1{/
    if(fd1>=0){/
    shutdown(fd1,SHUT_RDWR);/
    close(fd1);/
    fd1=-1;/
    }/
    }

    #defineSHUT_FD2{/
    if(fd2>=0){/
    shutdown(fd2,SHUT_RDWR);/
    close(fd2);/
    fd2=-1;/
    }/
    }

    #defineBUF_SIZE1024

    intmain(intargc,char**argv){
    inth;
    intfd1=-1,fd2=-1;
    charbuf1[BUF_SIZE],buf2[BUF_SIZE];
    intbuf1_avail,buf1_written;
    intbuf2_avail,buf2_written;

    if(argc!=4){
    fprintf(stderr,"Usage/n/tfwd/n");
    exit(1);
    }

    signal(SIGPIPE,SIG_IGN);

    forward_port=atoi(argv[2]);

    /*建立监听socket*/
    h=listen_socket(atoi(argv[1]));
    if(h<0)exit(1);

    for(;;){
    intr,nfds=0;
    fd_setrd,wr,er;
    FD_ZERO(&rd);
    FD_ZERO(&wr);
    FD_ZERO(&er);
    FD_SET(h,&rd);

    /*把监听socket和可读socket三个一起放入select的可读句柄列表里*/
    nfds=max(nfds,h);
    if(fd1>0&&buf1_avail<BUF_SIZE){
    FD_SET(fd1,&rd);
    nfds=max(nfds,fd1);
    }
    if(fd2>0&&buf2_avail<BUF_SIZE){
    FD_SET(fd2,&rd);
    nfds=max(nfds,fd2);
    }

    /*把可写socket两个一起放入select的可写句柄列表里*/
    if(fd1>0&&buf2_avail-buf2_written>0){
    FD_SET(fd1,&wr);
    nfds=max(nfds,fd1);
    }
    if(fd2>0&&buf1_avail-buf1_written>0){
    FD_SET(fd2,&wr);
    nfds=max(nfds,fd2);
    }

    /*把有异常数据的socket两个一起放入select的异常句柄列表里*/
    if(fd1>0){
    FD_SET(fd1,&er);
    nfds=max(nfds,fd1);
    }
    if(fd2>0){
    FD_SET(fd2,&er);
    nfds=max(nfds,fd2);
    }

    /*开始select*/
    r=select(nfds+1,&rd,&wr,&er,NULL);

    if(r==-1&&errno==EINTR)continue;
    if(r<0){
    perror("select()");
    exit(1);
    }

    /*处理新连接*/
    if(FD_ISSET(h,&rd)){
    unsignedintl;
    structsockaddr_inclient_address;
    memset(&client_address,0,l=sizeof(client_address));
    r=accept(h,(structsockaddr*)&client_address,&l);
    if(r<0){
    perror("accept()");
    }else{
    /*关闭原有连接,把新连接作为fd1,同时连接新的目标fd2*/
    SHUT_FD1;
    SHUT_FD2;
    buf1_avail=buf1_written=0;
    buf2_avail=buf2_written=0;
    fd1=r;
    fd2=connect_socket(forward_port,argv[3]);
    if(fd2<0){
    SHUT_FD1;
    }else
    printf("connectfrom%s/n",inet_ntoa(client_address.sin_addr));
    }
    }

    /*NB:readoobdatabeforenormalreads*/
    if(fd1>0)
    if(FD_ISSET(fd1,&er)){
    charc;
    errno=0;
    r=recv(fd1,&c,1,MSG_OOB);
    if(r<1){
    SHUT_FD1;
    }else
    send(fd2,&c,1,MSG_OOB);
    }

    if(fd2>0)
    if(FD_ISSET(fd2,&er)){
    charc;
    errno=0;
    r=recv(fd2,&c,1,MSG_OOB);
    if(r<1){
    SHUT_FD1;
    }else
    send(fd1,&c,1,MSG_OOB);
    }

    /*NB:readdatafromfd1*/
    if(fd1>0)
    if(FD_ISSET(fd1,&rd)){
    r=read(fd1,buf1+buf1_avail,BUF_SIZE-buf1_avail);
    if(r<1){
    SHUT_FD1;
    }else
    buf1_avail+=r;
    }

    /*NB:readdatafromfd2*/
    if(fd2>0)
    if(FD_ISSET(fd2,&rd)){
    r=read(fd2,buf2+buf2_avail,BUF_SIZE-buf2_avail);
    if(r<1){
    SHUT_FD2;
    }else
    buf2_avail+=r;
    }

    /*NB:writedatatofd1*/
    if(fd1>0)
    if(FD_ISSET(fd1,&wr)){
    r=write(fd1,buf2+buf2_written,buf2_avail-buf2_written);
    if(r<1){
    SHUT_FD1;
    }else
    buf2_written+=r;
    }

    /*NB:writedatatofd1*/
    if(fd2>0)
    if(FD_ISSET(fd2,&wr)){
    r=write(fd2,buf1+buf1_written,buf1_avail-buf1_written);
    if(r<1){
    SHUT_FD2;
    }else
    buf1_written+=r;
    }

    /*checkifwritedatahascaughtreaddata*/
    if(buf1_written==buf1_avail)buf1_written=buf1_avail=0;
    if(buf2_written==buf2_avail)buf2_written=buf2_avail=0;

    /*onesidehasclosedtheconnection,keepwritingtotheothersideuntilempty*/
    if(fd1<0&&buf1_avail-buf1_written==0){
    SHUT_FD2;
    }
    if(fd2<0&&buf2_avail-buf2_written==0){
    SHUT_FD1;
    }
    }
    return0;
    }
    /*----------------------源代码结束--------------------------------------------*/

    Linux下TCP网络服务器实现源代码(3)
    2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博
    站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航=IDC123.COM



    用gcctcpforwardport.c-oMyProxy编译此程序后运行效果如下:

    当有用户访问本机的8000端口时,MyProxy程序将把此请求转发到172.16.100.218主机的80端口,即实现了一个http代理。

    关于select函数:
    其函数原型为:
    intselect(intn,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);
    此函数的功能是由内核检测在timeout时间内,是否有readfds,writefds,exceptfds三个句柄集(filedescriptors)里的某个句柄(filedescriptor)的状态符合寻求,即readfds句柄集里有句柄可读或writefds句柄集里有可写或exceptfds句柄集里有例外发生,任何一个有变化函数就立即返回,返回值为timeout发生状态变化的句柄个数。
    n是所有readfds,writefds,exceptfds三个句柄集(filedescriptors)里编号最大值加1。比如:要检测两个socket句柄fd1和fd2在timeout时间内是否分别可读和可写就可以这样:
    先把两个句柄集(filedescriptors)清零:
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    然后把fd1加入读检测集:
    FD_SET(fd1,&readfds);
    然后把fd2加入写检测集:
    FD_SET(fd2,&writefds);
    再给timeout设置值,timeout是这样的一个结构:
    structtimeval{
    longtv_sec;/*seconds*/
    longtv_usec;/*microseconds*/
    };
    你可以这样赋值:
    timeout.tv_sec=1;
    timeout.tv_uec=0;
    表示检测在1秒钟内是否有句柄状态发生变化。
    如果有句柄发生变化,就可以用FD_ISSET检测各个句柄,比如:
    FD_ISSET(fd1,&readfds);//检测是否fd1变成可读的了
    FD_ISSET(fd2,&writefds);//检测是否fd2变成可写的了
    示意程序代码如下:
    /*----------------------示意代码开始--------------------------------------------*/
    fd1=socket();//创建一个socket
    fd2=socket();//创建一个socket
    while(1){
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(fd1,&readfds);
    FD_SET(fd2,&writefds);
    timeout.tv_sec=1;
    timeout.tv_uec=0;
    ret=select(fd1>fd2?(fd1+1):(fd2+1),&readfds,&writefds,NULL,&timeout);
    if(ret<0){printf("系统错误,select出错,错误代码:%d,错误信息:%s",errno,strerror(errno));}
    elseif(ret==0){printf("select超时返回,没有任何句柄状态发生变化!");}
    //有句柄状态发生了变化
    if(FD_ISSET(fd1,&readfds)){
    fd1有数据可读;
    fd1里的数据被读出来;
    }
    if(FD_ISSET(fd2,&writefds)){
    fd2可写;
    fd2里发送数据给对方;
    }
    }
    /*----------------------示意代码结束--------------------------------------------*/

    经常用到的几个自定义函数:
    1、开启监听的函数

    /*----------------------源代码代码开始--------------------------------------------*/
    int
    OpenSCPServer(intport,inttotal,intsendbuflen,intrecvbuflen,intblockORnot,intreuseORnot){
    /*************************关于本函数************************************
    *function_name:OpenSCPServer
    *参数说明:port整数型监听端口号,total整数型监听个数,sendbuflen整数型发送缓冲区大小
    *recvbuflen整数型接收缓冲区大小,blockORnot整数型是否阻塞,reuseORnot整数型是否端口重用
    *purpose:用来建立一个tcp服务端socket
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0520:00:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含GetCurrentTime.h头文件
    *********************************************************************/
    intsockfd=0,ret=0,opt=0,flags=1;
    structsockaddr_inladdr;

    ret=sockfd=socket(PF_INET,SOCK_STREAM,0);
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServersocket()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    return-1;
    }

    ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuseORnot,sizeof(int));
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServersetsockopt()reuseerror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    return-2;
    }

    ret=setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&recvbuflen,sizeof(int));
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServersetsockopt()recvbuferror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    return-3;
    }

    ret=setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&sendbuflen,sizeof(int));
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServersetsockopt()sendbuferror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    return-4;
    }

    ioctl(sockfd,FIONBIO,&blockORnot);/*blockornot*/

    laddr.sin_family=PF_INET;
    laddr.sin_port=htons(port);
    laddr.sin_addr.s_addr=INADDR_ANY;
    bzero(&(laddr.sin_zero),8);

    ret=bind(sockfd,(structsockaddr*)&laddr,sizeof(structsockaddr));
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServerbind()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    close(sockfd);
    return-5;
    }
    ret=listen(sockfd,total);
    if(ret<0){
    sprintf(errorMessage,"OpenTCPServerlisten()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
    close(sockfd);
    return-6;
    }
    sprintf(errorMessage,"OpenTCPServeropenedonport.%d(%d)OK,socket(%d),buf(%d:%d)!%s",port,total,sockfd,sendbuflen,recvbuflen,GetCurrentTime(0,0));
    returnsockfd;
    }
    /*----------------------源代码代码结束--------------------------------------------*/

    2、连接服务器的函数

    /*----------------------源代码代码开始--------------------------------------------*/
    int
    ConnectSCPServer(char*serverip,intserverport,intblockORnot){
    /*************************关于本函数************************************
    *function_name:ConnectSCPServer
    *参数说明:serverip服务器IP地址或主机名,serverport服务器端口,blockORnot整数型是否阻塞
    *purpose:用来建立一个tcp客户端socket
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0520:40:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    *********************************************************************/
    intserversock=0,ret=0;
    unsignedlongaddr;
    structsockaddr_insin;
    structhostent*he;

    if((he=gethostbyname(serverip))==0){
    sprintf(errorMessage,"ConnectSCPServerIPaddress'%s'error!return:-1%s",serverip,GetCurrentTime(0,0));
    return-1;
    }

    serversock=socket(PF_INET,SOCK_STREAM,0);
    if(serversock==-1){
    sprintf(errorMessage,"ConnectSCPServersocket()error!return:-2,errno=%d,errortext:'%s'%s",errno,strerror(errno),GetCurrentTime(0,0));
    return-2;
    }

    ioctl(serversock,FIONBIO,&blockORnot);//blockornot

    memset((char*)&sin,0,sizeof(structsockaddr_in));
    sin.sin_family=PF_INET;
    sin.sin_port=htons(serverport);
    sin.sin_addr=*((structin_addr*)he->h_addr);

    ret=connect(serversock,(structsockaddr*)&sin,sizeof(sin));

    if(ret==-1){
    sprintf(errorMessage,"ConnectSCPServerconnect()error!return:-3,errno=%d,errortext:'%s'%s",errno,strerror(errno),GetCurrentTime(0,0));
    close(serversock);
    return-3;
    }

    returnserversock;
    }
    /*----------------------源代码代码结束--------------------------------------------*/

    3、发送数据函数Send
    /*----------------------源代码代码开始--------------------------------------------*/
    int
    Send(intsock,char*buf,size_tsize,intflag,inttimeout){
    /*************************关于本函数************************************
    *function_name:Send
    *参数说明:sock整数型socket,buf待发送的内容,size要发送的大小,flag发送选项,timeout超时时间值
    *purpose:用来通过一个socket在指定时间内发送数据
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0520:58:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    *********************************************************************/
    inti=0,ret=0,intretry=0;

    structtimevaltival;
    fd_setwritefds;
    intmaxfds=0;

    tival.tv_sec=timeout;
    tival.tv_usec=0;

    FD_ZERO(&writefds);

    if(sock>0){
    FD_SET(sock,&writefds);
    maxfds=((sock>maxfds)?sock:maxfds);
    }
    else{
    sprintf(errorMessage,"Sendsocket:%derror!return:-2%s",sock,GetCurrentTime(0,0));
    return-2;
    }

    ret=select(maxfds+1,NULL,&writefds,NULL,&tival);
    if(ret<=0){
    if(ret<0)sprintf(errorMessage,"Sendsocket:%dselect()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
    elsesprintf(errorMessage,"Sendsocket:%dselecttimeout(%d)!%s",sock,timeout,GetCurrentTime(0,0));
    close(sock);
    return-3;
    }
    if(!(FD_ISSET(sock,&writefds))){
    sprintf(errorMessage,"Sendsocket:%dnotinwritefds!%s",sock,GetCurrentTime(0,0));
    close(sock);
    return-4;
    }

    while(i<size){
    ret=send(sock,buf+i,size-i,flag);
    if(ret<=0){
    sprintf(errorMessage,"Sendsocket:%dsend()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));

    if(EINTR==errno)
    if(intretry<10){intretry++;continue;}
    elsesprintf(errorMessage,"Sendsocket:%dsend()error!EINTR10times!%s",sock,GetCurrentTime(0,0));

    close(sock);
    return-1;
    }
    elsei+=ret;
    }
    sprintf(errorMessage,"Sendsocket:%dsend()OK!%d/%dbytessent!%s",sock,i,size,GetCurrentTime(0,0));
    returni;
    }
    /*----------------------源代码代码结束--------------------------------------------*/

    4、接收数据函数Recv

    /*----------------------源代码代码开始--------------------------------------------*/
    int
    Recv(intsock,char*buf,size_tsize,intflag,inttimeout){
    /*************************关于本函数************************************
    *function_name:Recv
    *参数说明:sock整数型socket,buf接收数据的缓冲区,size要接收数据的大小,flag接收选项,timeout超时时间值
    *purpose:用来从一个socket在指定时间内读取数据
    *tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
    *datetime:2006-07-0521:10:00
    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    *但请遵循GPL
    *Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    *Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    *********************************************************************/
    inti=0,ret=0,intretry=0;

    structtimevaltival;
    fd_setreadfds;
    intmaxfds=0;

    tival.tv_sec=timeout;
    tival.tv_usec=0;

    FD_ZERO(&readfds);

    if(sock>0){
    FD_SET(sock,&readfds);
    maxfds=((sock>maxfds)?sock:maxfds);
    }
    else{
    sprintf(errorMessage,"Recvsocket:%derror!return:-2%s",sock,GetCurrentTime(0,0));
    return-2;
    }

    ret=select(maxfds+1,&readfds,NULL,NULL,&tival);
    if(ret<=0){
    if(ret<0)sprintf(errorMessage,"Recvsocket:%dselect()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
    elsesprintf(errorMessage,"Recvsocket:%dselecttimeout(%d)!%s",sock,timeout,GetCurrentTime(0,0));
    close(sock);
    return-3;
    }
    if(!(FD_ISSET(sock,&readfds))){
    sprintf(errorMessage,"Recvsocket:%dnotinreadfds!%s",sock,GetCurrentTime(0,0));
    close(sock);
    return-4;
    }
    while(i<size){
    ret=recv(sock,buf+i,size-i,flag);
    if(ret<=0){
    sprintf(errorMessage,"Recvsocket:%drecv()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
    if(errno==EINTR)
    if(intretry<10){intretry++;continue;}
    elsesprintf(errorMessage,"Recvsocket:%drecv()error!EINTR10times!%s",sock,GetCurrentTime(0,0));
    close(sock);
    return-1;
    }
    elsei+=ret;
    }
    sprintf(errorMessage,"Recvsocket:%drecv()OK!%d/%dbytesreceived!%s",sock,i,size,GetCurrentTime(0,0));
    returni;
    }

    最后需要说明的是:我这里讲到的源程序并不能实际地作为一个产品程序来用,实际情况下可能会有其它许多工作要做,比如可能要建立共享队列来存放socket里读到的消息,也可能把发送消息先进行排队然后再调用Send函数。还有,如果不是全数字,在发送前一定要htonl转换为网络字节序,同理接收到后一定要先ntohl由网络字节序转换为主机字节序,否则对方发送过来的0x00000001在你这里可能是0x00010000,因为高低位顺序不同。


    Linux下TCP网络服务器实现源代码(4)
    2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博
    站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航=IDC123.COM



    进入2.6内核时代,select应该进垃圾堆了
    高并发服务器用select效率极低,特别是使用非阻塞IO时更是慢得一蹋糊涂
    改用epoll会大大改善
    我一个程序监听从8000到18000共计1万个端口,启动1万个LISTEN
    用epoll来阻塞,系统非常轻松,完全没有惊群现象

    epoll用法比select简单

    初始化:创建epoll描述字;向epoll描述字添加需要响应的套接字,初始化过程只要一次即可

    使用:等待epoll事件发生,提取事件的套接字进行相应的读写操作


    staticints_epfd;//epoll描述字

    {//初始化epoll
    structepoll_eventev;

    //设置epoll
    s_epfd=epoll_create(65535);

    {//这个过程可以循环以便加入多个LISTEN套接字进入epoll事件集合
    //服务器监听创建
    rc=listen();//listen参数这里省略

    //加入epoll事件集合
    ev.events=EPOLLIN;
    ev.data.fd=rc;
    if(epoll_ctl(s_epfd,EPOLL_CTL_ADD,rc,&ev)<0){
    fprintf(stderr,"epollsetinsertionerror:fd=%d",rc);
    return(-1);
    }
    }
    }

    {//epoll事件处理
    inti,nfds,sock_new;
    structepoll_eventevents[16384];
    for(;;){
    //等待epoll事件
    nfds=epoll_wait(s_epfd,events,16384,-1);
    //处理epoll事件
    for(i=0;i<nfds;i++){
    //events.data.fd是epoll事件中弹出的套接字
    //接收连接
    sock_new=accept(events.data.fd);//accept其它参数这里省略了
    if(0>sock_new){
    fprintf(stderr,"接收客户端连接失败/n");
    continue;
    }
    }
    }
    }

    1、为什么select是落后的?

    首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,搜索内核源代码得到:

    include/linux/posix_types.h:#define__FD_SETSIZE1024

    也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。

    其次,内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即select要检测的句柄数越多就会越费时。

    当然,在前文中我并没有提及poll方法,事实上用select的朋友一定也试过poll,我个人觉得select和poll大同小异,个人偏好于用select而已。



    /************关于本文档********************************************

    *filename:Linux2.6内核中提高网络I/O性能的新方法epoll

    *purpose:补充“Linux下各类TCP网络服务器的实现源代码”一文的不足之处

    *wroteby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)

    Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言

    *datetime:2006-07-0622:30:00

    *Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

    *但请遵循GPL

    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

    *********************************************************************/



    2、2.6内核中提高I/O性能的新方法epoll



    epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2),epoll_ctl(2),epoll_wait(2)。

    当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4)isanewAPIintroducedinLinuxkernel2.5.44)



    以下文章转自滕昱的WebLoghttp://mechgouki.spaces.msn.com/blog/PersonalSpace.aspx
    [QUOTE]

    /*********************************引用开始******************************/


    [版权声明]:此文档遵循GNU自由文档许可证(GNUFreeDocumentationLicense).任何人可以自由复制,分发,修改,不过如果方便,请注明出处和作者:)



    (1)导言:



    首先,我强烈建议大家阅读RichardStevens著作《TCP/IPIllustractedVolume1,2,3》和《UNIXNetworkProgrammingVolume1,2》。虽然他离开我们大家已经5年多了,但是他的书依然是进入网络编程的最直接的道路。其中的3卷的《TCP/IPIllustracted》卷1是必读-如果你不了解tcp协议各个选项的详细定义,你就失去了优化程序重要的一个手段。卷2,3可以选读一下。比如卷2讲解的是4.4BSD内核TCP/IP协议栈实现----这个版本的协议栈几乎影响了现在所有的主流os,但是因为年代久远,内容不一定那么vogue.在这里我多推荐一本《TheLinuxNetworkingArchitecture--DesignandImplementationofNetworkProtocolsintheLinuxKernel》,以2.4内核讲解LinuxTCP/IP实现,相当不错.作为一个现实世界中的实现,很多时候你必须作很多权衡,这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多大,sk_buff最小也有272的字节.



    其实对于socket应用层程序来说,《UNIXNetworkProgrammingVolume1》意义更大一点.2003年的时候,这本书出了最新的第3版本,不过主要还是修订第2版本。其中第6章《I/OMultiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和AsynchronousI/O模型.从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了,最后的结果是IO有个线程池,你应用也需要一个线程池......很多文档其实已经指出了这带来的线程context-switch带来的代价。



    在linux平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布,网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对DirectIO的支持(就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当帮助)。



    所以,剩下的select模型基本上就是我们在linux上面的唯一选择,其实,如果加上no-blocksocket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtimesignal等等。最终,DavideLibenzi开发的epoll进入2.6内核成为正式的解决方案

    Linux下TCP网络服务器实现源代码(5)
    2008-09-25 17:40:02 来源:互联网 【大 中 小】 评论:0 条 我要投稿 收藏本文 分享至微博
    站长交易(http://jy.chinaz.com)帮站长赚钱 虚拟主机评测+IDC导航=IDC123.COM




    (2)epoll的优点



    <1>支持一个进程打开大数目的socket描述符(FD)



    select最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat/proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。



    <2>IO效率不随FD数目增加而线性下降



    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idleconnections模拟WAN环境,epoll的效率就远在select/poll之上了。



    <3>使用mmap加速内核与用户空间的消息传递。



    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工mmap这一步的。



    <4>内核微调



    这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小---通过echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。



    (3)epoll的使用



    令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。



    LT(leveltriggered)是缺省的工作方式,并且同时支持block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.



    ET(edge-triggered)是高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(onlyonce),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。



    epoll只有epoll_create,epoll_ctl,epoll_wait3个系统调用,具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html,

    在http://www.kegel.com/rn/也有一个完整的例子,大家一看就知道如何使用了



    (4)Leader/follower模式线程pool实现,以及和epoll的配合



    .....未完成,主要是要避免过多的epoll_ctl调用,以及尝试使用EPOLLONESHOT加速......



    (5)benchmark



    .......未完成

    /*********************************引用结束******************************/

    [/QUOTE]

    3、epoll的使用方法

    这是epoll的man手册提供的一个例子,这段代码假设一个非阻塞的socket监听listener被建立并且一个epoll句柄kdpfd已经提前用epoll_create建立了:

    [CODE]
    structepoll_eventev,*events;



    for(;;){

    nfds=epoll_wait(kdpfd,events,maxevents,-1);/*waitforanI/Oevent.Allnoteshereaddedbyzhoulifa(http://zhoulifa.bokee.com)on2006-7-622:10:00*/



    for(n=0;n<nfds;++n){

    if(events[n].data.fd==listener){/*iflistensockethasanI/O,acceptthenewconnect*/

    client=accept(listener,(structsockaddr*)&local,

    &addrlen);

    if(client<0){

    perror("accept");

    continue;

    }

    setnonblocking(client);

    ev.events=EPOLLIN|EPOLLET;/*EPOLLIN-availableforread*/

    ev.data.fd=client;

    if(epoll_ctl(kdpfd,EPOLL_CTL_ADD,client,&ev)<0){/*addthenewsocketintotheepollfiledescriptors*/

    fprintf(stderr,"epollsetinsertionerror:fd=%d/n",

    client);

    return-1;

    }

    }

    else

    do_use_fd(events[n].data.fd);/*readfromasocketwhichhasdatacome*/

    }

    }

    [/CODE]
    4、epoll使用方法示意代码

    以下代码由chinaunix.net上BBS用户safedead(http://bbs.chinaunix.net/viewpro.php?uid=407631)提供:



    [CODE]
    staticints_epfd;//epoll描述字



    {//初始化epoll

    structepoll_eventev;



    //设置epoll

    s_epfd=epoll_create(65535);



    {//这个过程可以循环以便加入多个LISTEN套接字进入epoll事件集合

    //服务器监听创建

    rc=listen();//listen参数这里省略



    //加入epoll事件集合

    ev.events=EPOLLIN;

    ev.data.fd=rc;

    if(epoll_ctl(s_epfd,EPOLL_CTL_ADD,rc,&ev)<0){

    fprintf(stderr,"epollsetinsertionerror:fd=%d",rc);

    return(-1);

    }

    }

    }



    {//epoll事件处理

    inti,nfds,sock_new;

    structepoll_eventevents[16384];

    for(;;){

    //等待epoll事件

    nfds=epoll_wait(s_epfd,events,16384,-1);

    //处理epoll事件

    for(i=0;i<nfds;i++){

    //events.data.fd是epoll事件中弹出的套接字

    //接收连接

    sock_new=accept(events.data.fd);//accept其它参数这里省略了

    if(0>sock_new){

    fprintf(stderr,"接收客户端连接失败/n");

    continue;

    }

    }

    }

    }


    对照safedead和前面的一份代码,我想大家一定是明白了的。

    展开全文
  • Linux TCP拥塞控制接口

    2019-01-27 10:39:51
    Linux TCP拥塞控制接口 本文分析所采用的Linux版本为4.18 Linux系统中的TCP拥塞控制采用面向对象的设计思想,提供拥塞控制接口用于实现不同的拥塞控制策略,主要实现文件在tcp_cong.c中。 基本数据结构 tcp_cong...

    Linux TCP拥塞控制接口

    本文分析所采用的Linux版本为4.18

    Linux系统中的TCP拥塞控制采用面向对象的设计思想,提供拥塞控制接口用于实现不同的拥塞控制策略,主要实现文件在tcp_cong.c中。

    基本数据结构

    tcp_cong_list为拥塞支持的拥塞控制策略(算法实现)的列表,列表中的每一表项对应一种拥塞控制策略的具体实现。
    tcp_cong_list_lock为控制并发访问tcp_cong_list的自旋锁。

    tcp_congestion_ops结构为拥塞控制的接口,暴露出可以修改实现的接口函数。

    struct tcp_congestion_ops {
      struct list_head   list;
      u32  key;
      u32 flags;
      
      /* 初始化私有数据(可选) */
      void (*init)(struct sock *sk);
      /* 清除私有数据(可选) */
      void (*release)(struct sock *sk);
      
      /* 返回慢启动阈值(必须的) */
      u32 (*ssthresh)(struct sock *sk);
      /* 计算新的cwnd值(必须的) */
      void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);
      /* 改变ca_state前调用(可选) */
      void (*set_state)(struct sock *sk, u8 new_state);
      /* 当cwnd事件发生时调用(可选) */
      void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);
      /* ack到达时调用(可选) */
      void (*in_ack_event)(struct sock *sk, u32 flags);
      /* 丢包后新的cwnd值(必须的) */
      void (*undo_cwnd)(struct sock *sk);
      /* 数据包ack计数的钩子(可选的) */
      void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);
      /* 覆盖sysctl_tcp_min_tso_segs */
      u32 (*min_tso_segs)(struct sock *sk);
      /* 返回tcp_sndbuf_expand中使用的multiplier(可选的)*/
      u32 (*sndbuf_expand)(struct sock *sk);
      /* 在所有ca_state处理之后,当数据包交付以更新cwnd和
        pacing rate时调用(可选的)*/
      void (*cong_control)(struct sock *sk, const struct rate_sample *rs);
      /* 获取inet_diag的信息(可选的)*/
      size_t (*get_info)(struct sock *sk, u32 ext, int *attr,
          union tcp_cc_info *info);
      char  name[TCP_CA_NAME_MAX];
      struct module *owner;
    }
    

    当需要定义新的拥塞控制策略时,只需要修改相应的接口函数即可。
    以下代码给出tcp_reno控制策略的接口实现。

    struct tcp_congestion_ops tcp_reno = {
      .flags   = TCP_CONG_NON_RESTRICTED,
      .name    = "reno",
      .owner   = THIS_MODULE,
      .ssthresh = tcp_reno_ssthresh,
      .cong_avoid = tcp_reno_cong_avoid,
      .undo_cwnd = tcp_reno_undo_cwnd,
    };
    

    基本操作

    tcp_ca_find用于在列表中查找指定名称的拥塞控制算法。该函数使用简单的线性查找,当表项较多时会导致性能下降。

    static struct tcp_congestion_ops *tcp_ca_find(const char *name)
    {
      struct tcp_congestion_ops *e;
      /* 遍历列表项 */
      list_for_each_entry_rcu(e, &tcp_cong_list, list) {
        if(strcmp(e->name, name) == 0)
          return e;
      }
      return NULL;
    }
    

    tcp_ca_find_autoload函数用于查找具有指定名称的拥塞控制算法并自动载入,调用时要保持rcu锁定状态。
    首先在列表中查找指定的拥塞控制算法,之后存在两种情况:

    • 查找到指定的拥塞控制算法,直接返回。
    • 为查找到指定的拥塞控制算法,且内核支持模块动态加载,且具有相应的权限,则尝试加载指定的拥塞控制算法。
    static struct tcp_congestion_ops *tcp_ca_find_autoload(struct net *net, const char *name)
    {
      struct tcp_congestion_ops *ca = tcp_ca_find(name);
    /* 支持内核模块时,加载该模块 */
    #ifdef CONFIG_MODULES
      // 该算法尚未载入并且具有加载权限
      if(!ca && capable(CAP_NET_ADMIN) {
        rcu_read_unlock();
        request_module("tcp_%s", name);
        rcu_read_lock();
        ca = tcp_ca_find(name);
      }
    #endif
      return ca;
    }
    

    tcp_ca_find_key用于在列表中查找指定键值(key)的拥塞控制算法。该函数使用简单的线性查找,当表项较多时会导致性能下降。

    static struct tcp_congestion_ops *tcp_ca_find_key(u32 key)
    {
      struct tcp_congestion_ops *e;
      /* 遍历列表项 */
      list_for_each_entry_rcu(e, &tcp_cong_list, list) {
        if(e->key == key)
          return e;
      }
      return NULL;
    }
    

    tcp_register_congestion_control用于注册新的拥塞控制算法,即将新的拥塞控制算法添加到列表中。

    int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
    {
      int ret = 0;
      /* 所有的拥塞控制算法都必须实现这些操作 */
      if(!ca->ssthresh || !ca->undo_cwnd ||
        !(ca->cong_avoid || ca->cong_control)) {
        pr_err("%s does not implement required ops\n", ca->name);
        return -EINVAL;
      }
    
      ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));
      
      spin_lock(&tcp_cong_list_lock);
      if(ca->key == TCP_CA_UNSPEC || tcp_ca_find_key(ca->key)) {
        pr_notice("%s already registered or non-unique key\n", ca->name);
        return -EEXIST;
      } else {
        list_add_tail_rcu(&ca->list, &tcp_cong_list);
        pr_debug("%s registered\n", ca->name);
      }
      spin_unlock(&tcp_cong_list_lock);
    
      return ret;
    }
    EXPORT_SYMBOL_GPL(tcp_register_congestion_control);
    

    与tcp_register_congestion_control函数相反,tcp_unregister_congestion_control函数在列表中删除指定的拥塞控制算法。该函数由模块的remove函数调用。模块的引用计数器保证只有在没有套接字使用该算法是才被删除。

    void tcp_unregister_congestion_control(struct tcp_congestion_ops *ca)
    {
      spin_lock(&tcp_cong_list_lock);
      list_del_rcu(&ca->list);
      spin_unlock(&tcp_cong_list_lock);
    
      /* 在模块完全删除前等待所有的读操作完成。*/
      /* 此时,try_module_get将失败,这是因为模块处于"going"状态
        (没有引用), 并且module_exit()正在被调用。*/
      synchronize_rcu();
    }
    EXPORT_SYMBOL_GPL(tcp_unregister_congestion_control);
    

    tcp_ca_get_key_by_name用于通过名称获取指定拥塞控制算法的键值(key)。ecn_ca为返回值。

    u32 tcp_ca_get_key_by_name(struct net *net, const char *name, bool *ecn_ca)
    {
      const struct tcp_congestion_ops *ca;
      u32 key = TCP_CA_UNSPEC;
      
      might_sleep();
      
      rcu_read_lock();
      ca = tcp_ca_find_autoload(net, name);
      if (ca) {
        key = ca->key;
        *ecn_ca = ca->flags & TCP_CONG_NEEDS_ECN;
      }
      rcu_read_unlock();
      return key;
    }
    EXPORT_SYMBOL_GPL(tcp_ca_get_key_by_name);
    

    tcp_ca_get_name_by_key用于通过键值获取指定拥塞控制算法的名称。

    char *tcp_ca_get_key_by_name(u32 key, char *buffer) 
    {
      const struct tcp_congestion_ops *ca;
      char *ret = NULL;
      
      rcu_read_lock();
      ca = tcp_ca_find_key(key);
      if(ca)
        ret = strncpy(buffer, ca->name, TCP_CA_NAME_MAX);
      rcu_read_unlock();
      return ret;
    }
    EXPORT_SYMBOL_GPL(tcp_ca_get_name_by_key);
    

    tcp_assign_congestion_control函数用于设置指定sock的拥塞控制算法。
    首先,获取系统中设置的拥塞控制算法。如果该算法尚未加载,则将拥塞控制算法设置为tcp_reno;否则,使用系统中设置的拥塞控制算法。

    void tcp_assign_congestion_control(struct sock *sk)
    {
      struct net *net = sock_net(sk);
      struct inet_connection_sock *icsk = inet_csk(sk);
      const struct tcp_congestion_ops *ca;
      
      rcu_read_lock();
      ca = rcu_dereference(net->ipv4.tcp_congestion_control);
      if(unlikely(!try_module_get(ca->owner()))
        ca = &tcp_reno;
      icsk->icsk_ca_ops = ca;
      rcu_read_unlock();
    
      memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv));
      if(ca->flags & TCP_CONG_NEEDS_ECN)
        INET_ECN_xmit(sk);
      else
        INET_ECN_dontxmit(sk);
    }
    

    tcp_init_congestion_control函数用于初始化指定sock的拥塞控制算法。

    void tcp_init_congestion_control(struct sock *sk)
    {
      const struct inet_connection_sock *icsk = inet_csk(sk);
      
      tcp_sk(sk)->prior_ssthresh = 0;
      if(icsk->icsk_ca_ops->init)
        icsk->icsk_ca_ops->init(sk);
      if(tcp_ca_needs_ecn(sk))
        INET_ECN_xmit(sk);
      else
        INET_ECN_dontxmit(sk);
    } 
    

    tcp_reinit_congestion_control函数用于重新初始化指定sock的拥塞控制算法。

    static void tcp_reinit_congestion_control(struct sock *sk, const struct tcp_congestion_ops *ca)
    {
      const struct inet_connection_sock *icsk = inet_csk(sk);
      
      tcp_cleanup_congestion_control(sk);
      icsk->icsk_ca_ops = ca;
      icsk->icsk_ca_setsockopt = 1;
      memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk->icsk_ca_priv));
      
      if(sk->sk_state != TCP_CLOSE)
        tcp_init_congestion_control(sk);
    }
    

    tcp_cleanup_congestion_control用于清除指定sock的拥塞控制算法。

    void tcp_cleanup_congestion_control(struct sock *sk)
    {
      struct inet_connection_sock *icsk = inet_csk(sk);
    
      if(icsk->icsk_ca_ops->release)
        icsk->icsk_ca_ops->release(sk);
      module_put(icsk->icsk_ca_ops->owner);
    }
    

    tcp_set_default_congestion_control用于设置默认的拥塞控制算法。sysctl调用该函数。

    int tcp_set_default_congestion_control(struct net *net, const char *name)
    {
      struct tcp_congestion_ops *ca;
      const struct tcp_congestion_ops *prev;
      int ret;
    
      rcu_read_lock();
      ca = tcp_ca_find_autoload(net, name);
      if(!ca) {
        ret = -ENOENT;
      } else if(!try_module_get(ca->owner)) {
        ret = -EBUSY;
      } else {
        prev = xchg(&net->ipv4.tcp_congestion_control, ca);
        if(prev)
          module_put(prev->module);
        ca->flags |= TCP_CONG_NON_RESTRICTED;
        ret = 0;
      }
      rcu_read_unlock();
    
      return ret;
    }
    

    tcp_congestion_default函数用于系统启动时通过kernel配置信息设置默认值。

    static int __init tcp_congestion_default(void)
    {
      return tcp_set_default_congestion_control(&init_net, CONFIG_DEFAULT_TCP_CONG);
    }
    late_initcall(tcp_congestion_default);
    

    tcp_get_available_congestion_control用于获取可用的拥塞控制算法

    void tcp_get_available_congestion_control(char *buf, size_t maxlen)
    {
      struct tcp_congestion_ops *ca;
      size_t offs = 0;
       
      rcu_read_lock();
      list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
        offs += snprintf(buf+offs, maxlen - offs, "%s%s", 
                offs == 0 ? "" : " ", ca->name);
      }
      rcu_read_unlock();
    }
    

    tcp_get_default_congestion_control用于获取当前的默认拥塞控制算法。

    void tcp_get_default_congestion_control(struct net *net, char *name)
    {
      const struct tcp_congestion_ops *ca;
      
      rcu_read_lock();
      ca = rcu_dereference(net->ipv4.tcp_congestion_control);
      strncpy(name, ca->name, TCP_CA_NAME, MAX);
      rcu_read_unlock();
    }
    

    tcp_get_allowed_congestion_control用于获取non-restricted的拥塞控制算法。

    void tcp_get_allowed_congestion_control(char *buf, size_t maxlen)
    {
      struct tcp_congestion_ops *ca;
      size_t offs = 0;
    
      *buf = '\0';
      rcu_read_lock();
      list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
        if(!(ca->flags & TCP_CONG_NON_RESTRICTED))
          continue;
        offs += snprintf(buf + offs, maxlen - offs, "%s%s",
              offs == 0 ? "" : " ", ca->name);
      }
      rcu_read_unlock();
    }
    

    tcp_set_allowed_congestion_control用于改变non-restricted拥塞控制算法列表。val为以空格分隔的拥塞控制算法名称列表。

    int tcp_set_allowed_congestion_control(char *val)
    {
      struct tcp_congestion_ops *ca;
      char *saved_clone, *clone, *name;
      int ret = 0;
      
      saved_clone = clone = kstrdup(val, CFP_USER);
      if(!clone)
        return -ENOMEM;
      
      spin_lock(&tcp_cong_list_lock);
      /* pass 1: 检查bad表项 */
      while ((name = strsep(&clone, " ")) && *name) {
        ca = tcp_ca_find(name);
        if(!ca) {
          ret = -ENOENT;
          goto out;
        }
      }
      
      /* pass 2: 清除旧值 */
      list_for_each_entry_rcu(ca, &tcp_cong_list, list)
        ca->flags &= ~TCP_CONG_NON_RESTRICTED;
    
      /* pass 3: 标记 */
      while ((name = strsep(&clone, " ")) && *name) {
        ca = tcp_ca_find(name);
        WARN_ON(!ca);
        if(ca)
          ca->flags |= TCP_CONG_NON_RESTRICTED;
      }
    out:
      spin_unlock(&tcp_cong_list_lock);
      kfree(saved_clone);
    
      return ret;
    }
    

    tcp_set_congestion_control用于设置指定sock的拥塞控制算法。如果load的值为false,调用者负责调用tcp_init_congestion_control或者tcp_reinit_congestion_control(如果当前的拥塞控制算法已经初始化)。

    int tcp_set_congestion_control(struct sock *sk, const char *name, bool laod, bool reinit)
    {
      struct inet_connection_sock *icsk = inet_csk(sk);
      const struct tcp_congestion_ops *ca;
      int err = 0;
      
      if(icsk->icsk_ca_dst_locked)
        return -EPERM;
      
      rcu_read_lock();
      if(!load)
        ca = tcp_ca_find(name);
      else
        ca = tcp_ca_find_autoload(sock_net(sk), name);
    
      /* 如果当前值和设置值相同,不需要修改 */
      if (ca == icsk->icsk_ca_ops) {
        icsk->icsk_ca_setsockopt = 1;
        goto out;
      }
      
      if(!ca) {
        err = -ENOENT;
      } else if(!load) {
        const struct tcp_congestion_ops *old_ca = icsk->icsk_ca_ops;
        
        if(try_module_get(ca->owner)) {
          if(reinit) {
            tcp_reinit_congestion_control(sk, ca);
          } else {
            icsk->icsk_ca_ops = ca;
            module_put(old_ca->owner);
          }
        } else {
          err = -EBUSY;
        }
      } else if(!((ca->flags & TCP_CONG_NON_RESTRICTED) || 
            ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))) {
          err = -EPERM;
      } else if(!try_module_get(ca->owner)) {
        err = -EBUSY;
      } else {
        tcp_reinit_congestion_control(sk, ca);
      }
    out:
      rcu_read_unlock();
      return err;
    }
    

    当拥塞窗口的大小不大于慢启动阈值时,使用慢启动过程。tcp_slow_start函数基于RFC2581,且适当地处理stretch ACKs。本函数没有实现RFC3456的Appropriate Byte Counting (ABC)。

    u32 tcp_slow_start(struct tcp_sock *tp, u32 acked) 
    {
      u32 cwnd = min(tp->snd_cwnd + acked, tp->snd->ssthresh);
      
      acked -= cwnd - tp->snd_cwnd;
      tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);
      
      return acked;
    }
    EXPORT_SYMBOL_GPL(tcp_slow_start);
    

    tcp_cong_avoid_ai函数实现拥塞避免阶段的加法增加。理论上,对每个acked的数据包,更新tp->snd_cwnd += 1/tp->snd_cwnd(或w)。

    void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)
    {
      /* 如果在较高的w值累计信用,则温和地应用 */
      if(tcp->snd_cwnd_cnt >= w) {
        tp->snd_cwnd_cnt = 0;
        tp->snd_cwnd++;
      }
      
      tp->snd_cwnd_cnt += acked;
      if(tp->snd_cwnd_cnt >= w) {
        u32 delta = tp->snd_cwnd_cnt / w;
        tp->snd_cwnd_cnt -= delta * w;
        tp->snd_cwnd += delta;
      }
      tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_cwnd_clamp);
    }
    EXPORT_SYMBOL_GPL(tcp_cong_avoid_ai);
    

    导出的函数列表

    • tcp_register_congestion_control
    • tcp_unregister_congestion_control
    • tcp_ca_get_key_by_name
    • tcp_ca_get_name_by_key
    • tcp_slow_start
    • tcp_cong_avoid_ai

    RFC2581

    RFC2581描述了4中拥塞控制算法:慢启动、拥塞避免、快速重传和快速恢复。

    慢启动和拥塞避免

    发送端控制向网络中发送的数据量。为了实现这些算法,每个TCP连接状态增加两个变量:拥塞窗口(cwnd)为发送端在接收到ACK前可以发送到网络中的数据量;发送端通告窗口(rwnd)主机端数据量限制。cwnd和rwnd的较小值决定了数据传输量。
    另外,还需要状态变量慢启动阈值(ssthresh),用于确定使用慢启动算法还是拥塞避免算法。
    在向未知状态的网络中发送数据时,需要TCP缓慢地探测网络以确定可用的容量。慢启动算法用于这一目的。
    ssthresh的初始值可以任意大(例如,一些实现使用通告窗口大小),但在响应拥塞时减少。当cwnd < ssthresh时,使用慢启动算法,当cwnd > ssthresh时使用拥塞避免算法。当cwnd和ssthresh的值相等时,发送端可以使用慢启动或者拥塞避免。

    慢启动期间,对每个收到的新数据的ACK,TCP最多将cwnd的值增加SMSS字节。当cwnd值超过ssthresh或观察到拥塞时,慢启动过程终止。

    拥塞避免期间,每RTT时间cwnd的值增加1个全尺寸数据段大小。拥塞避免阶段持续到检测到拥塞。cwnd+= SMSS*SMSS/cwnd,该式是对上述更新1个全尺寸数据段大小的一种可接受逼近,且在每个非重复ACK时更新。另一种可接受的增加cwnd值的方法是计算新数据ACK确认地字节数(缺点是需要维护附加的状态变量)。当确认地字节数量达到cwnd时,cwnd的值增加SMSS字节。

    快速重传/快速恢复

    当乱序数据包到达时,TCP接收端需要立即发送重复ACK,用于通告发送端接收到乱序数据包和期望的序列号。
    发送端使用快速重传算法检测和恢复丢包,基于重复ACK。快速重传算法使用3个重复ACK(4个相同的ACK,且期间没有其他ACK)作为数据包丢失的指示。发送端接收到3个重复ACK后,执行丢失数据包的重传,而不是等到重传计数器超时。

    快速重传算法发送丢失的数据包后,进入快速恢复阶段,直到非重复ACK到达。

    展开全文
  • linux tcp 大量 TIME_WAIT 问题

    千次阅读 2018-01-24 12:08:40
    查看linux tcp连接状态发现存在大量 TIME_WAIT 状态连接 netstat -na | awk '{print $5,$6}'| sort | uniq -c | sort -n 结果: 2500 10.50.23.90:6379 TIME_WAIT 解决方法: sudo vim /etc/sysct

    阿里云服务器监控中发现tcp连接数监控异常,状态如下图:
    这里写图片描述

    查看linux tcp连接状态发现存在大量 TIME_WAIT 状态连接

    netstat -na  | awk '{print $5,$6}'| sort | uniq -c | sort -n
    
    结果:
    2500 10.50.23.90:6379 TIME_WAIT

    解决方法:

    
    sudo vim /etc/sysctl.conf
    编辑下面参数:
    
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_fin_timeout = 30
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_timestamps=1
    
    执行命令
    sudo sysctl -p  # 从配置文件“/etc/sysctl.conf”加载内核参数设置

    原理牵扯到 tcp连接终止协议的四次握手,参考文章:

    https://www.cnblogs.com/yjf512/p/5327886.html

    http://blog.sina.com.cn/s/blog_8e5d24890102w9yi.html

    http://blog.csdn.net/he_jian1/article/details/40787269

    sysctl参数说明:

    net.core.netdev_max_backlog = 400000
    #该参数决定了,网络设备接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
    
    net.core.optmem_max = 10000000
    #该参数指定了每个套接字所允许的最大缓冲区的大小
    
    net.core.rmem_default = 10000000
    #指定了接收套接字缓冲区大小的缺省值(以字节为单位)。
    
    net.core.rmem_max = 10000000
    #指定了接收套接字缓冲区大小的最大值(以字节为单位)。
    
    net.core.somaxconn = 100000
    #Linux kernel参数,表示socket监听的backlog(监听队列)上限
    
    net.core.wmem_default = 11059200
    #定义默认的发送窗口大小;对于更大的 BDP 来说,这个大小也应该更大。
    
    net.core.wmem_max = 11059200
    #定义发送窗口的最大大小;对于更大的 BDP 来说,这个大小也应该更大。
    
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    #严谨模式 1 (推荐)
    #松散模式 0
    
    net.ipv4.tcp_congestion_control = bic
    #默认推荐设置是 htcp
    
    net.ipv4.tcp_window_scaling = 0
    #关闭tcp_window_scaling
    #启用 RFC 1323 定义的 window scaling;要支持超过 64KB 的窗口,必须启用该值。
    
    net.ipv4.tcp_ecn = 0
    #把TCP的直接拥塞通告(tcp_ecn)关掉
    
    net.ipv4.tcp_sack = 1
    #关闭tcp_sack
    #启用有选择的应答(Selective Acknowledgment),
    #这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);
    #(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用。
    
    net.ipv4.tcp_max_tw_buckets = 10000
    #表示系统同时保持TIME_WAIT套接字的最大数量
    
    net.ipv4.tcp_max_syn_backlog = 8192
    #表示SYN队列长度,默认1024,改成8192,可以容纳更多等待连接的网络连接数。
    
    net.ipv4.tcp_syncookies = 1
    #表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
    
    net.ipv4.tcp_timestamps = 1
    #开启TCP时间戳 默认开启
    #以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项。
    
    net.ipv4.tcp_tw_reuse = 1
    #表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
    
    net.ipv4.tcp_tw_recycle = 1
    #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
    
    net.ipv4.tcp_fin_timeout = 10
    #表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
    
    net.ipv4.tcp_keepalive_time = 1800
    #表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为30分钟。
    
    net.ipv4.tcp_keepalive_probes = 3
    #如果对方不予应答,探测包的发送次数
    
    net.ipv4.tcp_keepalive_intvl = 15
    #keepalive探测包的发送间隔
    
    net.ipv4.tcp_mem
    #确定 TCP 栈应该如何反映内存使用;每个值的单位都是内存页(通常是 4KB)。
    #第一个值是内存使用的下限。
    #第二个值是内存压力模式开始对缓冲区使用应用压力的上限。
    #第三个值是内存上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的 BDP 可以增大这些值(但是要记住,其单位是内存页,而不是字节)。
    
    net.ipv4.tcp_rmem
    #与 tcp_wmem 类似,不过它表示的是为自动调优所使用的接收缓冲区的值。
    
    net.ipv4.tcp_wmem = 30000000 30000000 30000000
    #为自动调优定义每个 socket 使用的内存。
    #第一个值是为 socket 的发送缓冲区分配的最少字节数。
    #第二个值是默认值(该值会被 wmem_default 覆盖),缓冲区在系统负载不重的情况下可以增长到这个值。
    #第三个值是发送缓冲区空间的最大字节数(该值会被 wmem_max 覆盖)。
    
    net.ipv4.ip_local_port_range = 1024 65000
    #表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
    
    net.ipv4.netfilter.ip_conntrack_max=204800
    #设置系统对最大跟踪的TCP连接数的限制
    
    net.ipv4.tcp_slow_start_after_idle = 0
    #关闭tcp的连接传输的慢启动,即先休止一段时间,再初始化拥塞窗口。
    
    net.ipv4.route.gc_timeout = 100
    #路由缓存刷新频率,当一个路由失败后多长时间跳到另一个路由,默认是300。
    
    net.ipv4.tcp_syn_retries = 1
    #在内核放弃建立连接之前发送SYN包的数量。
    
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    # 避免放大攻击
    
    net.ipv4.icmp_ignore_bogus_error_responses = 1
    # 开启恶意icmp错误消息保护
    
    net.inet.udp.checksum=1
    #防止不正确的udp包的攻击
    
    net.ipv4.conf.default.accept_source_route = 0
    #是否接受含有源路由信息的ip包。参数值为布尔值,1表示接受,0表示不接受。
    #在充当网关的linux主机上缺省值为1,在一般的linux主机上缺省值为0。
    #从安全性角度出发,建议你关闭该功能。
    展开全文
  • Linux TCP 系统参数配置

    千次阅读 2013-03-25 16:37:45
    Linux TCP 系统参数配置 我们这里应用的是CentOS5.3,并内核使用的是2.6.18-128.el5PAE #1 SMP 。修改部分TCP ,有的是为了提高性能与负载,但是存在降低稳定性的风险。有的则是安全方面的配置,则有可能牺牲了...

    Linux TCP 系统参数配置

    我们这里应用的是CentOS5.3,并内核使用的是2.6.18-128.el5PAE #1 SMP 。修改部分TCP ,有的是为了提高性能与负载,但是存在降低稳定性的风险。有的则是安全方面的配置,则有可能牺牲了性能。

    1.TCP keepalive TCP连接保鲜设置

    echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
    echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
    echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes

    keepalive是TCP保鲜定时器。当网络两端建立了TCP连接之后,闲置idle(双方没有任何数据流发送往来)了tcp_keepalive_time后,服务器内核就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的回答(ack包),则会在tcp_keepalive_intvl后再次尝试发送侦测包,直到收到对对方的ack,如果一直没有收到对方的ack,一共会尝试tcp_keepalive_probes次,每次的间隔时间在这里分别是15s, 30s, 45s, 60s, 75s。如果尝试tcp_keepalive_probes,依然没有收到对方的ack包,则会丢弃该TCP连接。

    2. syn cookies设置

    echo 0 > /proc/sys/net/ipv4/tcp_syncookies

    在CentOS5.3中,该选项默认值是1,即启用syn cookies功能。我们建议先关闭,直到确定受到syn flood攻击的时候再开启syn cookies功能,有效地防止syn flood攻击。也可以通过iptables规则拒绝syn flood攻击。

    3.TCP  连接建立设置

    echo 8192 > /proc/sys/net/ipv4/tcp_max_syn_backlog
    echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
    echo 2 > /proc/sys/net/ipv4/tcp_synack_retries

    tcp_max_syn_backlog  SYN队列的长度,时常称之为未建立连接队列。系统内核维护着这样的一个队列,用于容纳状态为SYN_RESC的TCP连接(half-open connection),即那些依然尚未得到客户端确认(ack)的TCP连接请求。加大该值,可以容纳更多的等待连接的网络连接数。
    tcp_syn_retries  新建TCP连接请求,需要发送一个SYN包,该值决定内核需要尝试发送多少次syn连接请求才决定放弃建立连接。默认值是5. 对于高负责且通信良好的物理网络而言,调整为2
    tcp_synack_retries  对于远端SYN连接请求,内核会发送SYN+ACK数据包来确认收到了上一个SYN连接请求包,然后等待远端的确认(ack数据包)。该值则指定了内核会向远端发送tcp_synack_retires次SYN+ACK数据包。默认设定值是5,可以调整为2

    4. TCP 连接断开相关设置

    echo 30 >  /proc/sys/net/ipv4/tcp_fin_timeout
    echo 15000 > /proc/sys/net/ipv4/tcp_max_tw_buckets
    echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
    echo 1 >  /proc/sys/net/ipv4/tcp_tw_recycle

    tcp_fin_timeout 对于由本端主动断开连接的TCP连接,本端会主动发送一个FIN数据报,在收到远端ACK后,且并没有收到远端FIN包之前,该TCP连接的状态是FIN_WAIT_2状态,此时当远端关闭了应用,网络不可达(拔网张),程序不可断僵死等等,本端会一直保留状态为FIN_WAIT_2状态的TCP连接,该值tcp_fin_timeout则指定了状态为FIN_WAIT_2的TCP连接保存多长时间,一个FIN_WAIT_2的TCP连接最多占1.5k内存。系统默认值是60秒,可以将此值调整为30秒,甚至10秒。
    tcp_max_tw_buckets 系统同时处理TIME_WAIT sockets数目。如果一旦TIME_WAIT tcp连接数超过了这个数目,系统会强制清除并且显示警告消息。设立该限制,主要是防止那些简单的DoS攻击,加大该值有可能消耗更多的内存资源。如果TIME_WAIT socket过多,则有可能耗尽内存资源。默认值是18w,可以将此值设置为5000~30000
    tcp_tw_resue 是否可以使用TIME_WAIT tcp连接用于建立新的tcp连接。
    tcp_tw_recycle 是否开启快带回收TIME_WAIT tcp连接的功能。

    5. tcp 内存资源使用相参数设定

    echo 16777216 > /proc/sys/net/core/rmem_max
    echo 16777216 > /proc/sys/net/core/wmem_max
    cat /proc/sys/net/ipv4/tcp_mem
    echo “4096 65536 16777216″ > /proc/sys/net/ipv4/tcp_rmem
    echo “4096 87380 16777216″ > /proc/sys/net/ipv4/tcp_wmem

    rmem_max 定义了接收窗口可以使用的最大值,可以根据BDP值进行调节。
    wmem_max 定义了发送窗口可以使用的最大值,可以根据BDP什值进行调整。
    tcp_mem [low, pressure, high] TCP用这三个值来跟踪内存使用情况,来限定资源占用。通常情况下,在系统boot之时,内核会根据可用内存总数计算出这些值。如果出现了Out of socket memory,则可以试着修改这个参数。
    1)low: 当TCP使用了低于该值的内存页面数时,TCP不会考滤释放内存。
    2)pressure: 当TCP使用了超过该值的内存页面数量,TCP试图稳定其对内存的占用,进入pressure模式,直到内存消耗达于low值,退出该模式。
    3)hight:允许所有tcp sockets用于排队缓冲数据报的内存页数。
    tcp_rmem [min, default, max]
    1)min 为每个TCP连接(tcp socket)预留用于接收缓冲的内存数量,即使在内存出现紧张情况下TCP socket都至少会有这么多数量的内存用于接收缓冲。
    2)default 为TCP socket预留用于接收缓冲的内存数量,默认情况下该值影响其它协议使用的 rmem_default的值,所以有可能被rmem_default覆盖。
    3)max 该值为每个tcp连接(tcp socket)用于接收缓冲的内存最大值。该值不会影响wmem_max的值,设置了选项参数 SO_SNDBUF则不受该值影响。
    tcp_wmem [min, default, max] 如上(tcp_rmen)只不过用于发送缓存。

    注:
    1)可以通过sysctl -w 或者写入/etc/sysctl.conf永久保存
    2)性能调优仅在于需要的时候进行调整,调整以后需要采集数据与基准测试数据进行比较。建议,不需要盲从地调整这些参数。

    展开全文
  • Linux TCP/IP 协议栈调优

    2016-03-05 15:21:52
    有些性能瓶颈和LinuxTCP/IP的协议栈的设置有关,所以特别google了一下Linux TCP/IP的协议栈的参数意义和配置,记录一下。 如果想永久的保存参数的设置, 可以将参数加入到/etc/sysctl.conf中。如果想临时的更改
  • linux TCP超时重传

    万次阅读 2013-06-04 11:16:48
    TCP超时重传是保证TCP可靠性传输的机制之一,当超时后...linux TCP超时重传是通过设置重传超时时钟icsk_retransmit_timer来实现的。 零窗探测超时时钟与重传超时时钟共用icsk_retransmit_timer,根据icsk_pending是IC
  • 彻底实现Linux TCP的Pacing发送逻辑-高精度hrtimer版

    万次阅读 热门讨论 2017-01-14 08:36:17
     如果单纯的将《彻底实现Linux TCP的Pacing发送逻辑-普通timer版》中的timer_list换成hrtimer,必然招致失败。因为在hrtimer的function中,调用诸如tcp_write_xmit这样的长路径函数是一种用丝袜装榴莲的行为。好吧...
  • Linux TCP/IP 协议栈源码分析

    千次阅读 2017-01-12 10:09:09
    [转载]Linux TCP/IP 协议栈源码分析 一.linux内核网络栈代码的准备知识 1. linux内核ipv4网络部分分层结构: BSD socket层: 这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一...
  • Linux TCP实现简单聊天程序

    千次阅读 2013-06-25 10:32:54
    Linux TCP实现简单聊天程序 /*tcp_chat_server.c*/ #include #include #include #include #include #include #include #include #include #define MAXBUF 1024 int main(int argc,char *argv[]) { int pid; int ...
  • Linux TCP Tuning(Tcp优化)

    千次阅读 2007-09-20 02:29:00
    原贴:http://blog.chinajavaworld.com/entry.jspa?id=1182 Linux TCP Tuning There are a lot of differences between Linux version 2.4 and 2.6, so first well cover the tuning i
  • Linux TCP 参数设置

    千次阅读 2018-10-31 13:49:34
    TCP链接建立之后,如果应用程序或者上层协议一直不发送数据,或者隔很长时间才发送一次数据,当链接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,链接还需不需要保持,这种...
  • linux tcp udp 调试工具

    热门讨论 2015-10-07 10:43:32
    sokit 是一款开源免费的 TCP / UDP 测试(调试)工具, 可以用来接收,发送或转发TCP/UDP数据包。 本程序可以工作在三种模式: 服务器模式,用来监听本地端口,接收外部数据包,并且可以回复自定义数据; 客户端模式,...
  • linux tcp repair及tcp热迁移

    千次阅读 2017-06-28 12:16:39
    概念 比如docker等容器在不同的机器之间无缝迁移...linux也在3.5版本中引入TCP_REPAIR socket选项来支持热迁移 获取状态及还原 当需要迁移的时候,为迁移的socket进入repair模式 setsockopt设置TCP_PRE
  • linux tcpreplay命令

    千次阅读 2019-04-08 19:26:50
    官网:http://tcpreplay.appneta.com/ 参考:https://blog.csdn.net/gdutliuyun827/article/details/26557273 https://www.cnblogs.com/legendbaby/p/5057044.html Tcpreplay is a suite of GPLv3 licensed...
  • NS2中的Linux TCP实现

    千次阅读 2013-09-02 18:55:08
    A Linux TCP implementation for NS2 (Part of the NS-2 enhancement project) David X. Wei   Prof. Pei Cao Netlab @ Caltech   CS @ Stanford May 2006; Revision 1 for ...
  • 全面了解linux TCP/IP协议栈

    万次阅读 多人点赞 2017-09-03 19:17:03
    简要说明 自从熟悉了linux socket编程(主要做posix socket的TCP/IP)之后,就一直以来就想写一篇对TCP/IP有一个比较全面的涵盖用户空间、内核以及网卡的文章,以便帮助大家在遇到基于socket的TCP/IP问题或困惑时能...
  • linux TCP/IP L2 handle 流程, 基于Linux kernel3.14。
  • linux tcp 之setsockopt

    千次阅读 2017-06-13 13:55:41
    该错误产生的原因是由于send 函数中的size变量大小超过了tcp_sendspace的值。tcp_sendspace定义了应用在调用send之前能够在kernel中缓存的数据量。当应用程序在socket中设置了O_NDELAY或者O_NONBLOCK
  • Linux TCP/IP 协议栈源码分析 - 数据 发送/接收 流程图
  • linux TCP 参数设置

    千次阅读 2013-08-26 10:39:45
    此文为网络转载,对理解linux内核tcp参数设置有一定帮助,设置tcp参数一定要小心谨慎,轻易不要更改线上环境,我贴一下我们线上环境中,sysctl.conf的内容,见文章底部 net.ipv4.tcp_tw_reuse = 1 ...
  • linux TCP Fast Open开启和测试

    万次阅读 2017-05-12 11:11:45
    linux上要开启TCP Fast Open,内核版本至少为3.7.0, 且需要设置 /proc/sys/net/ipv4/tcp_fastopen 为3. 开启后,如果有连接进来,使用如下命令查看: grep ‘^TcpExt:’ /proc/net/netstat | cut -d ’ ’ -f 91-...
  • 本文总结Linux内核中关于TCP协议相关的内核参数含义及其相关配置。目的是指出可能在某些情况下提高TCP网络性能的潜在内核可调参数,请确保在进行调整之前和之后进行测试以获得可测量的定量结果。 TCP状态转移图 ...
  • Linux tcp被动打开内核源码分析

    千次阅读 2014-04-28 22:08:50
    linux内核tcp被动打开内核源码分析
  • Linux TCP最大连接数

    2017-03-01 12:54:25
    1、修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都...
  • 分析Linux TCP/IP源码,涉及内核很多知识。特别是链表,哈希表,用户层与内核层的交互。后面将重点分析这几个知识。
  • netstat -nat|grep -i "80" ...net.ipv4.tcp_tw_reuse = 1 #打开快速回收 net.ipv4.tcp_tw_recycle = 1 保存后执行 sysctl -p 生效 临时生效的办法: sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w...
  • Linux TCP SYN包不返回问题解析

    千次阅读 2011-12-09 09:30:02
    Linux TCP SYN包不返回问题解析 现象描述及排查过程: 最早是在网站图片cache集群中出现监控系统偶尔报告vip连接超时的现象,当时集群是在netscaler上以三角DSR模式提供服务的。开始认为是服务器在三角模式下...
  • Linux TCP参数优化

    千次阅读 2014-07-22 19:20:57
    一、前言 本文档针对OOP8生产环境,具体优化策略需要根据实际情况进行调整;本文档将在以下几个方面来阐述...2) Linux性能诊断工具,介绍如何使用Linux自带的诊断工具进行性能诊断。 加粗斜体表示可以直接运行的命令

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,469
精华内容 16,987
关键字:

linuxtcp

linux 订阅