精华内容
下载资源
问答
  • TCP转发服务器 1 link 对应的源码link.h 和link.c,用来快速搭建TCP服务器/客户端的程序,基于前面TCP和epoll的章节,基本架构: Network Worker指的是TCP服务器/客户端,接收到的数据会经由Parser再到...

    本章节的代码在目录:…\tutorials\Patchs\5. TCP转发服务器

    1 link

    对应的源码link.h 和 link.c,用来快速搭建TCP服务器/客户端的程序,基于前面TCP和epoll的章节,基本架构:

    Network Worker指的是TCP服务器/客户端,接收到的数据会经由Parser再到Handler。

    接口文件link.h内容如下:

    全局初始化link_init,使用完需要释放资源的link_release

    创建TCP服务器link_build_tcp_server以及客户端link_connect_tcp_server

    单次轮询link_loop,无限轮询link_loop_forever

    2 linker

    linker体现在源码文件:linker.h linker.cpp,是对link的C++封装。

    其中成员函数establish就是建立TCP连接,connect是连接服务器。

    主框架是C++时可以包含linker进行使用。

    3 基本应用

    3.1服务器

    基于link来构建TCP服务器是非常简单的,源码见tcp_server.c :

    补充说明两个地方,parser和handler:

    parser:由于TCP传输的是流数据,因此我们需要在这个地方截取出完整的报文出来;

    hansler: 完整报文最终处理的函数。

    继续往后面看,我们会有一个实例(基于linker)讲解如何从流数据中截取出JSON格式的报文进行处理。

    编译命令:make server

    3.2客户端

    客户端也很简单:

    同样的,有数据过来时会经过parser再到handler。

    编译命令:make client

     

    编译完成后,输出可执行文件:c_server 和 c_client

    程序可以直接运行~

    4  代理服务器

    代理服务器的功能是接收数据,通过parser解析出JSON报文给handler进行处理,我们可以基于6.4的tcp_server.c来完成;但这里我们选择用c++来实现,大同小异,大家可以移植cJSON进来,然后基于tcp_server.c完成这个功能。

    c++的实现代码在tcp_task.cpp中:

    定义了两个类:tcp_task和json_task,继承关系:linker -> tcp_task -> json_task,然后json_task重写packet和handle,实际上packet就是parser;主函数通过json_task完成TCP服务器的搭建:

    当我们收到数据时,会进入到json_task的packet中,因此我们需要在packet中对流数据进行解析。

    这段代码完成流数据截取JSON报文的逻辑,接着到达handle中:

     

    JSON报文包含did和behavior,behavior取值是register和send;假设我们有一台设备(TCP客户端)连接进来,首先需要进行register然后才能向已经注册的设备发送数据报文。

    编译:直接 make 即可,编译完成输出文件cpp_server:

    运行cpp_server和c_client:

    客户端发送:{"did":"123456","behavior":"register"}

    服务端回复:{"ack":"ok"}

    我们可以在手机上下载一个TCP的终端,然后尝试连接到这个服务器上发送数据;需要注意的是,手机需要和终端保持在同一个局域网下,查看终端的ip可以用ifconfig:

    比如我查到的IP是192.168.10.228

    因此手机APP连接TCP也需要指定这个IP地址:

    手机发送:

    {"did":"333333","behavior":"send","target":"123456","message":"Hi, my did is 333333"}

    这个JSON报文会被服务器解析,由于behavior是send,目标target是123456,因此会把message也就是:"Hi, my did is 333333"转发出去;同时服务器回复手机APP响应信息ack。

    另一个终端会收到来自服务器转发的数据如下:

    5  补充说明

    link和linker的源码具体实现大家可以看link.c和linker.cpp,有疑问的地方或者不理解的地方可以找技术人员咨询,该版本的源码比较老,最新的程序应用在产品当中了,可以找技术人员咨询获取更多支持和更新版本的程序源码~

     

    ===================================================================================================================================================================

    如果觉得对您有帮助并想进一步深入学习交流可以扫描以下微信二维码或加入QQ群:928840648

    欢迎共同学习成长,有一群爱学习的小伙伴一起勉励!!一起加油!!也可点击

     

    笔者基于嵌入式系统框架内容如下整理编辑:

     

    展开全文
  • LINUX下高效TCP端口转发工具--RINETD

    千次阅读 2019-04-18 08:50:06
    @[TOC]LINUX下高效TCP端口转发工具–RINETD 转自LINUX下高效TCP端口转发工具–RINETD rinetd 介绍 一个C语言开发的开源TCP端口转发工具,在当前公网IP稀缺的情况下,只能通过NAT来实现内部服务器对外提供服务,该...

    @[TOC]LINUX下高效TCP端口转发工具–RINETD
    转自LINUX下高效TCP端口转发工具–RINETD

    rinetd 介绍

    一个C语言开发的开源TCP端口转发工具,在当前公网IP稀缺的情况下,只能通过NAT来实现内部服务器对外提供服务,该工具除了高效的实现TCP端口转发外,具有对源地址鉴权的安全功能,并且安装方便。
    将TCP连接从一个IP地址和端口重定向到另一个IP地址和端口。rinetd是单进程服务,它处理配置文件中指定的地址/端口对的任意数量连接 。rinetd使用非阻塞I / O作为单个进程运行,能够重定向大量连接,同时不会对计算机造成严重影响。
    rinetd 不支持重定向FTP,因为FTP需要多个套接字。

    程序获取

    官方地址:https://boutell.com/rinetd/
    直接下载:

    # wget https://boutell.com/rinetd/http/rinetd.tar.gz
    

    程序安装

     [root@localhost src]# tar -zxvf rinetd.tar.gz
     [root@localhost src]# cd rinetd
     [root@localhost rinetd]# vim Makefile#修改下Makefile
     CFLAGS=-DLINUX -g
    
    rinetd: rinetd.o match.o
            gcc rinetd.o match.o -o rinetd
    
    install: rinetd
            install -m 700 rinetd /usr/sbin
            install -m 644 rinetd.8 /usr/local/share/man/man8
     [root@localhost rinetd]# make && make install
    

    主程序安装好

    [root@YNCOMM4 rinetd]#  rinetd --help
    Usage: rinetd [OPTION]
      -c, --conf-file FILE   read configuration from FILE
      -h, --help             display this help
      -v, --version          display version number
    
    Most options are controlled through the
    configuration file. See the rinetd(8)
    manpage for more information.
    

    配置文件

    vi /etc/rinetd.con,添加如下行

    0.0.0.0 8888 10.1.1.2 80         #将本机的8888端口转到内网10.1.1.2 的80端口
    allow 112.25.222.170             #只允许源地址访问该端口
    

    启动程序

    rinetd -c /etc/rinetd.conf
    

    查看端口

    在这里插入图片描述

    展开全文
  • nginx的TCP代理功能跟nginx的反向代理不同的是:请求该端口的所有流量都会转发到目标服务器,而在反向代理中可以细化哪些请求分发给哪些服务器;另一个不同的是,nginx做TCP代理并不仅仅局限于WEB的URL请求,还可以...

    nginx在1.9版本之后可以充当端口转发的作用,即:访问该服务器的指定端口,nginx就可以充当端口转发的作用将流量导向另一个服务器,同时获取目标服务器的返回数据并返回给请求者。nginx的TCP代理功能跟nginx的反向代理不同的是:请求该端口的所有流量都会转发到目标服务器,而在反向代理中可以细化哪些请求分发给哪些服务器;另一个不同的是,nginx做TCP代理并不仅仅局限于WEB的URL请求,还可以转发如memcached、MySQL等点到点的请求.。
    实现步骤如下:

    一、安装nginx的依赖库

    1.安装prce(重定向支持)和openssl(https支持,如果不需要https可以不安装。)
    推荐使用yum装方便,也可以下载tar编译安装

    yum -y install pcre*
    yum -y install openssl*
    

    2.安装ngx_cache_purge

    wget -O /usr/src/ngx_cache_purge-2.3.tar.gz  http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz
    tar zxvf /usr/src/ngx_cache_purge-2.3.tar.gz

    下载后,解压放到/usr/local/src/目录即可,无需编译安装

    二、下载并安装nginx

    1.下载nginx包

    wget http://nginx.org/download/nginx-1.9.9.tar.gz
    

    2.解压并且安装

    tar -zxvf nginx-1.9.9.tar.gz
    

    3.进入到nginx目录编译安装

    cd nginx-1.9.9
    ./configure --prefix=/usr/local/nginx --add-module=/usr/local/src/ngx_cache_purge-2.3 --with-http_stub_status_module --with-stream
    make && make install;
    

    其中 /usr/local/src/ngx_cache_purge-2.3 是下载 ngx_cache_purge-2.3 解压后的目录

    4.修改nginx的配置文件nginx.conf
    内容如下

    worker_processes  32; 
    
    events {
        #use epoll;          
        worker_connections  65535;
    }
    
    stream {
        upstream zifangsky {
            hash $remote_addr consistent;
            server 192.168.4.137:8360;
        }
        server {
            listen 80;
            proxy_connect_timeout 5s;
            proxy_timeout 5s;
            proxy_pass zifangsky;
        }
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        keepalive_timeout  65;
    
        #gzip  on;
    
        server {
            listen       9000;
            server_name  localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   html;
                index  index.html index.htm;
            }
        }
    
    }
    

    三、相关命令

    1.查看是否监听端口

    netstat -apn | grep 8080:
    

     

    2.查看nginx的进程

    ps -ef | grep nginx
    

     

    3.查看nginx的版本

    nginx -V
    

     

    4.记得关闭防火墙或者开启相关端口
    查看防火墙的状态
    关闭防护墙
    开机关闭防火墙

    firewall-cmd --state
    systemctl stop firewalld.service
    systemctl disable firewalld.service
    

    5.杀掉占用80端口的进程

    fuser -k 80/tcp
    如果没有fuser命令,则用yum安装
    yum install -y psmisc
    展开全文
  • 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下使用haproxy配置tcp转发

    千次阅读 2019-05-07 09:55:23
    本文基于阿里云CentOS 7.6测试成功 # cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) ... TCP转发代理 192.168.200.10 目的服务器 需要转发的端口: (1)8888...
  • 2.1 TCP代理服务器可以隐藏背后真正TCP服务器 2.2 保护TCP服务器免受应用层以下级别的协议栈攻击 2.3 TCP转址机 3、socket透明代理的实现原理? 4、在实现TCP代理服务器时,遵循以下几点原则 5、应用背景 6、...
  • Linux下使用bufferevent实现tcp代理功能

    千次阅读 2017-12-10 21:15:26
    有了大概的概念后,来通过一个例子实践一下:通过bufferevent实现了一个简单的tcp代理程序(例子来源sample/le-proxy.c) 主要流程是:创建本地监听、接受客户端的新连接、发起服务端的新连接、接收客户端数据...
  • 摘要: ssh是一个非常棒的工具, 不但能建立动态转发, 例如chrome的Switchy... http://blog.163.com/digoal@126/blog/static/163877040201141821810103/ 还能建立TCP转发隧道, 例如我以前写过的关于使用ssh
  • 目录 1.下载nginx 2.新增stream模块,编译与安装nginx 3.修改配置文件 4.重载nginx ...ngx_stream_core_module 这个...本人在安装的时候也遇到过很多坑,初次是在window下安装nginx1.16.1实现tcp/udp的代理转发,...
  • nginx1.9+做TCP代理(端口转发

    千次阅读 2017-12-27 16:52:37
    nginx的TCP代理功能跟nginx的反向代理不同的是:请求该端口的所有流量都会转发到目标服务器,而在反向代理中可以细化哪些请求分发给哪些服务器;另一个不同的是,nginx做TCP代理并不仅仅局限于WEB的U
  • windows 下 TCP 端口转发

    2017-03-27 09:10:00
    windows 下 TCP 端口转发 -----haproxy主要用于负载,如果只是负载一个,那就成为端口映射了,但是毕竟是linux下面的程序。 -----windows下也有一个小工具:portforward.exe,图形界面容易操作,个人平常使用可以,...
  • 基于常规DNS隧道进行的tcp端口转发dns2tcp的使用 0x01安装Dns2TCP dns2tcp 是一个利用DNS隧道转发TCP连接的工具,使用C语言开发。 sudo apt-get install dns2tcp 0x02配置dns2tcp 配置DNS2TCP...
  • 不同机子tcp报文转发

    千次阅读 2015-06-10 17:30:22
    服务器A只配置有内网IP,服务器B配置有内外网IP,但是与服务器A处在不同网段内且ping不通。...在既可以与服务器A通讯又可以与服务器B通讯的服务器上配置一个tcp代理。把访问本机的2013端口的所有请求代理到10.1.27
  • 全平台正向tcp端口转发工具rinetd的使用 Linux下做地址NAT有很多种方法。比如haproxy、nginx的4层代理linux自带的iptables等都能实现。其实,Linux下有一个叫rinetd的工具,安装简单,配置也不...
  • haproxy实现tcp代理

    万次阅读 2016-07-29 09:44:58
    简介haproxy大多数情况下在http(七层)代理,如apache,tomcat等,下面我们就来讲下haproxy的tcp(四层)代理,可以用于ssh、mysql、mongodb等多种... tcp代理 10.10.10.16 mongodb master 10.10.10.17 mongodb
  • Linux环境下iptables代理及NAT转发

    千次阅读 2019-04-08 10:33:04
    Linux rhel6.5作为WEB-内网(client),IP地址:192.168.10.10 ,——VMnet8 ; Linux rhel6.5作为GATEWAY—网关, eth1的IP地址:192.168.10.1——VMnet8;eth2的IP地址:202.100.10.1——VMnet1; Linux rhel6.5...
  • Linux配置端口转发

    万次阅读 2017-02-08 14:21:39
    开启IP转发 首先开启IP转发功能,默认是关闭的。 临时修改: # echo 1 >/proc/sys/net/ipv4/ip_forward 修改过后就马上生效,但如果系统重启后则又恢复为默认值0。 永久修改: vi /etc/sysctl.conf #...
  • android平台redSocks+ipTable+socks5实现tcp转发 要求android系统有root权限,可以执行程序,修改iptable。参考url: https://blog.csdn.net/yeshennet/article/details/79397651 一、redsocks编译 1. ...
  • socat是一个强大的转发工具,能实现不同接口间的转发,常用的文件、管道、设备(终端或调制解调器等)、插座(Unix,IP4,IP6 - raw,UDP,TCP)、SSL、SOCKS4客户端或代理CONNECT。 二、安装 1、命令行安装 sudo ...
  • Linux系统数据包转发

    万次阅读 2016-09-07 19:15:07
    出于安全考虑,Linux系统默认是禁止数据包转发的。所谓转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将包发往本机另一网卡,该网卡根据路由表继续发送数据包。这通常就是路由器所...
  • Linux实现端口转发

    千次阅读 2016-03-04 10:04:28
    本文讲述了如何在Linux下实现端口转发,以实现通过VPN连接至远端开放给VPN连接的计算机后,如何通过该计算机代理访问远端内网中其它计算机。 情 景是这样的,A公司给B公司开发了项目,之后需要对B公司的项目进行维护...
  • 目前只支持tcp协议的端口转发,前提需要作为portproxy的主机需要安装IPV6,安装可以不启用IPV6。 A.配置方法 假定需要通过192.168.1.8的14941端口连接192.168.1.118的1494端口,则需要在192.168.1.8主机的命令行...
  • 【nginx】用stream模块实现TCP端口转发

    千次阅读 2019-07-24 20:28:42
    将本机的6033端口转发至本机3306 二、安装 # yum -y install maridb* nginx 三、配置 设置selinux为宽松模式 # setenforce 0 # sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config # ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,544
精华内容 13,017
关键字:

linuxtcp代理转发

linux 订阅