精华内容
下载资源
问答
  • 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和前面的一份代码,我想大家一定是明白了的。

    展开全文
  • TCP 组包和拆包算法

    千次阅读 2016-04-03 21:38:13
    TCP 组包和拆包实现算法,由于TCP是基于流的,发的数据不是发几包就能收到几包的,会把你发的数据重新组包,所以自己要加入帧格式,自己来判断每一包。一个是发定长的包,这个好实现,但是会浪费带宽,这里实现的是...
    /*************************************
    文件名: server.c
    TCP 组包和拆包实现算法


    作者:   马中海
    QQ:     284358503
    Email: zhonghaima001@163.com
    */
    #include <stdlib.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/socket.h>
    #include <linux/in.h>
    #include <string.h>


    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>



    #define BUF_SIZE 1024*5


    //#define TCP_PACK_DEBUG 1


    int main()
    {

        int nBufuseLen=0;       //缓冲区里用到的长度
        int nRevOnceLen=0;      //read 一次读到的长度
        int nPackLen=0;         //每一包的长度,包括头和两个长度和尾字节
        int bufSize=BUF_SIZE;


        unsigned char buf[BUF_SIZE];     //read 的缓存


        int i=0;


        int sfp,nfp; /* 定义两个描述符 */
        struct sockaddr_in s_add,c_add;
        int sin_size;
        unsigned short portnum=6000; /* 服务端使用端口 */
        printf("Hello,welcome to my server !\r\n");
        sfp = socket(AF_INET, SOCK_STREAM, 0);
        if(-1 == sfp)
        {
            printf("socket fail ! \r\n");
            return -1;
        }
        printf("socket ok !\r\n");
        /* 填充服务器端口地址信息,以便下面使用此地址和端口监听 */
        bzero(&s_add,sizeof(struct sockaddr_in));
        s_add.sin_family=AF_INET;
        s_add.sin_addr.s_addr=htonl(INADDR_ANY); /* 这里地址使用全0,即所有 */
        s_add.sin_port=htons(portnum);
        /* 使用bind进行绑定端口 */
        if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
        {
            printf("bind fail !\r\n");
            return -1;
        }
        printf("bind ok !\r\n");
        /* 开始监听相应的端口 */
        if(-1 == listen(sfp,5))
        {
            printf("listen fail !\r\n");
            return -1;
        }
        printf("listen ok\r\n");
        //  while(1)
        {


            //    char buf[1027];
            //    int readlen;


            sin_size = sizeof(struct sockaddr_in);
            /* accept服务端使用函数,调用时即进入阻塞状态,等待用户进行连接,在没有客户端进行连接时,程序停止在此处,
               不会看到后面的打印,当有客户端进行连接时,程序马上执行一次,然后再次循环到此处继续等待。
               此处accept的第二个参数用于获取客户端的端口和地址信息。
                */
            nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
            if(-1 == nfp)
            {
                printf("accept fail !\r\n");
                return -1;
            }
            printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));
            /* 这里使用write向客户端发送信息,也可以尝试使用其他函数实现 */
            if(-1 == write(nfp,"hello,welcome to my server \r\n",32))
            {
                printf("write fail!\r\n");
                return -1;
            }


    //头0xF9   两个长度字节   数据   尾0xF8
            while(1)
            {
    #ifdef TCP_PACK_DEBUG
                {
                    printf("bufSize = %d ,nBufuseLen = %d\n",bufSize,nBufuseLen);
                }
    #endif
                if(bufSize>nBufuseLen)
                {
                    nRevOnceLen=read(nfp,buf+nBufuseLen,bufSize-nBufuseLen);
                    nBufuseLen=nBufuseLen+nRevOnceLen;


    #ifdef TCP_PACK_DEBUG
                    {
                        printf("nBufuseLen = %d>3\n",nBufuseLen);
                    }
    #endif
                    /*
                                    printf(" nRevOnceLen data:\n");
                                    for(i=0; i<nRevOnceLen; i++)
                                    {
                                        printf(" 0x%x ",buf[i]&0xFF);
                                    }
                                    printf(" \n");
                    */


                }
                else
                {
                    printf("buf if full\n");
                }




                while(nBufuseLen>3)            //用到的大于3个字节
                {


                    /*            printf(" nBufuseLen data:\n");
                                for(i=0; i<nBufuseLen; i++)
                                {
                                    printf(" 0x%x ",buf[i]&0xFF);
                                }
                                printf(" \n");
                    */

    #ifdef TCP_PACK_DEBUG
                    {
                        printf("buf[0] = 0x%x \n",buf[0]&0xFF);
                    }
    #endif
                    if((buf[0]&0xFF)==0xF9)         //头
                    {
                        nPackLen=(buf[1]*256)+buf[2];


    #ifdef TCP_PACK_DEBUG
                        {
                            printf("nBufuseLen=%d ,nPackLen=%d\n",nBufuseLen,nPackLen);
                        }
    #endif


                        if(nBufuseLen>=nPackLen)                //大于和=
                        {
    #ifdef TCP_PACK_DEBUG
                            {
                                printf("buf[nPackLen-1] = 0x%x \n",buf[nPackLen-1]&0xFF);
                            }
    #endif


                            if((buf[nPackLen-1]&0xFF)==0xF8)                  //尾
                            {
                                //找到一包数据,所有的数据都往前移动nPackLen个字节
    #ifdef TCP_PACK_DEBUG
                                {
                                    printf("head 0x%x length %d tail 0x%x \n", buf[0]&0xFF,nPackLen,buf[nPackLen-1]&0xFF);
                                }
    #endif


                                for(i=0; i<nBufuseLen-nPackLen; i++)
                                {
                                    buf[i]=buf[nPackLen+i];
                                }


                                nBufuseLen=nBufuseLen-nPackLen;      //用到的字节变小了
                                //   continue;           //不要read了
                            }
                            else
                            {


    #ifdef TCP_PACK_DEBUG
                                printf("buf[%d-1]=0x%x,(buf[0]!=0xF8)\n",nPackLen,(buf[nPackLen-1]!=0xF8));        //这里应该不会执行
                                for(i=0; i<5; i++)
                                {
                                    printf(" 0x%x ",buf[i]&0xFF);
                                }
                                printf(" \n");
    #endif
                                printf(" (buf[nPackLen-1]&0xFF)!=0xF8 \n");
                                nBufuseLen=0;           //清除错误的包   //这里应该不会执行




                                break;


                            }
                        }
                        else
                        {


    #ifdef TCP_PACK_DEBUG
                            printf(" nBufuseLen<=nPackLen\n");
    #endif
                            break;
                        }
                    }
                    else
                    {
                        printf(" (buf[0]!=0xF9)\n");        //这里应该不会执行
                        nBufuseLen=0;           //清除错误的包   //这里应该不会执行
                        break;
                    }
                }


                /*
                            if((0xFF&buf[0])!=0xF2)
                            {
                                printf("readlen = %d buf  0x%x  0x%x  0x%x \n",readlen,0xFF&buf[0],0xFF&buf[1],0xFF&buf[2]);
                            }




                            if(readlen!=1027)
                                printf("readlen = %d buf  0x%x  0x%x  0x%x \n",readlen,0xFF&buf[0],0xFF&buf[1],0xFF&buf[2]);


                */
            }
            //  close(nfp);
        }
        // close(sfp);
        return 0;
    }
    展开全文
  • 这里附上源代码来个小结吧。首先,循环网络服务器编程实现的步骤是这样的:这种服务器模型是典型循环服务,如果不加上多进程/线程技术,此种服务吞吐量有限,大家都可以看到,如果前一个连接服务数据没有收发完毕...

    http://www.linuxeden.com/forum/t146870.html

     

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

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

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

    /************关于本文档********************************************
    *filename: Linux下各类TCP网络服务器的实现源代码
    *purpose: 记录Linux下各类tcp服务程序源代码
    *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    *date time:2006-07-04 22: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服务端程序
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-04 22:00:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Google.com
    
    *********************************************************************/
    
    int main(int argc, char ** argv)
    
    {
    
        int sockfd,new_fd; /* 监听socket: sock_fd,数据传输socket: new_fd */
    
        struct sockaddr_in my_addr; /* 本机地址信息 */
    
        struct sockaddr_in their_addr; /* 客户地址信息 */
    
        unsigned int sin_size, myport, lisnum;
    
    
    
        if(argv[1])  myport = atoi(argv[1]);
    
        else myport = 7838;
    
    
    
        if(argv[2])  lisnum = atoi(argv[2]);
    
        else lisnum = 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, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
    
            perror("bind");
    
            exit(1);
    
        }
    
    
    
        if (listen(sockfd, lisnum) == -1) {
    
            perror("listen");
    
            exit(1);
    
        }
    
        while(1) {
    
            sin_size = sizeof(struct sockaddr_in);
    
            if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
    
                perror("accept");
    
                continue;
    
            }
    
            printf("server: got connection from %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>
    
    #define MAXDATASIZE 100 /*每次最大数据传输量 */
    
    /*********************************************************************
    
    *filename: cycletcpclient.c
    
    *purpose: 循环tcp客户端程序
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-04 22:20:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Google.com
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *********************************************************************/
    
    
    
    int main(int argc, char *argv[])
    
    {
    
        int sockfd, numbytes;
    
        char buf[MAXDATASIZE];
    
        struct hostent *he;
    
        struct sockaddr_in their_addr;
    
        unsigned int myport;
    
    
    
        if(argv[2]) myport = atoi(argv[2]);
    
        else myport = 7838;
    
    
    
        if (argc != 3) {
    
            fprintf(stderr,"usage: %s hostname port\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 = *((struct in_addr *)he->h_addr);
    
        bzero(&(their_addr.sin_zero),0);
    
        if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -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);
    
        return 0;
    
    }
    
    /*----------------------源代码结束--------------------------------------------*/
    


     

    用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分别编译上述代码后运行情况如下:
    服务端运行显示:

    administrator@ubuzlf:/data/example/c$ ./tcpserver
    server: got connection from 127.0.0.1
    server: got connection from 127.0.0.1
    server: got connection from 127.0.0.1


    客户端运行显示:

    administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
    Received: Hello, world!

    administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
    Received: Hello, world!

    administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
    Received: Hello, world!


    不得不说的一个概念性问题:阻塞与非阻塞
    在阻塞服务中,当服务器运行到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>
    
    
    
    static int forward_port;
    
    
    
    #undef max
    
    #define max(x,y) ((x) > (y) ? (x) : (y))
    
    
    
    /*************************关于本文档************************************
    
    *filename: tcpforwardport.c
    
    *purpose: 演示了select的用法,这是一个极好的代理软件核心,专门作端口映射用
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-05 19:00:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *********************************************************************/
    
    
    
    static int listen_socket (int listen_port) {
    
        struct sockaddr_in a;
    
        int s;
    
        int yes;
    
        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, (struct sockaddr *) &a, sizeof (a)) < 0) {
    
            perror ("bind");
    
            close (s);
    
            return -1;
    
        }
    
        printf ("accepting connections on port %d\n", (int) listen_port);
    
        listen (s, 10);
    
        return s;
    
    }
    
    
    
    static int connect_socket (int connect_port, char *address) {
    
        struct sockaddr_in a;
    
        int s;
    
        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, (struct in_addr *) &a.sin_addr.s_addr)) {
    
            perror ("bad IP address format");
    
            close (s);
    
            return -1;
    
        }
    
    
    
        if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
    
            perror ("connect()");
    
            shutdown (s, SHUT_RDWR);
    
            close (s);
    
            return -1;
    
        }
    
        return s;
    
    }
    
    
    
    #define SHUT_FD1 { \
    
        if (fd1 >= 0) {   \
    
            shutdown (fd1, SHUT_RDWR);  \
    
            close (fd1);  \
    
            fd1 = -1;     \
    
        }   \
    
    }
    
    
    
    #define SHUT_FD2 { \
    
        if (fd2 >= 0) {   \
    
            shutdown (fd2, SHUT_RDWR);  \
    
            close (fd2);  \
    
            fd2 = -1;     \
    
        }   \
    
    }
    
    
    
    #define BUF_SIZE 1024
    
    
    
    int main (int argc, char **argv) {
    
        int h;
    
        int fd1 = -1, fd2 = -1;
    
        char buf1[BUF_SIZE], buf2[BUF_SIZE];
    
        int buf1_avail, buf1_written;
    
        int buf2_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 (;;) {
    
            int r, nfds = 0;
    
            fd_set rd, 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)) {
    
                unsigned int l;
    
                struct sockaddr_in client_address;
    
                memset (&client_address, 0, l = sizeof (client_address));
    
                r = accept (h, (struct sockaddr *)&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 ("connect from %s\n", inet_ntoa(client_address.sin_addr));
    
                }
    
            }
    
    
    
            /* NB: read oob data before normal reads */
    
            if (fd1 > 0)
    
            if (FD_ISSET (fd1, &er)) {
    
                char c;
    
                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)) {
    
                char c;
    
                errno = 0;
    
                r = recv (fd2, &c, 1, MSG_OOB);
    
                if (r < 1) {
    
                    SHUT_FD1;
    
                } else
    
                    send (fd1, &c, 1, MSG_OOB);
    
            }
    
    
    
            /* NB: read data from fd1 */
    
            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: read data from fd2 */
    
            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: write data to fd1 */
    
            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: write data to fd1 */
    
            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;
    
            }
    
    
    
            /* check if write data has caught read data */
    
            if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0;
    
            if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0;
    
    
    
            /* one side has closed the connection, keep writing to the other side until empty */
    
            if (fd1 < 0 && buf1_avail - buf1_written == 0) {
    
                SHUT_FD2;
    
            }
    
            if (fd2 < 0 && buf2_avail - buf2_written == 0) {
    
                SHUT_FD1;
    
            }
    
        }
    
        return 0;
    
    }
    
    /*----------------------源代码结束--------------------------------------------*/
    


     

    用gcc tcpforwardport.c -o MyProxy编译此程序后运行效果如下:

    ./MyProxy 8000 80 172.16.100.218
    accepting connections on port 8000
    connect from 127.0.0.1


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

    关于select函数:
    其函数原型为:
    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    此函数的功能是由内核检测在timeout时间内,是否有readfds,writefds,exceptfds三个句柄集(file descriptors)里的某个句柄(file descriptor)的状态符合寻求,即readfds句柄集里有句柄可读或writefds句柄集里有可写或exceptfds句柄集里有例外发生,任何一个有变化函数就立即返回,返回值为timeout发生状态变化的句柄个数。
    n是所有readfds,writefds,exceptfds三个句柄集(file descriptors)里编号最大值加1。比如:要检测两个socket句柄fd1和fd2在timeout时间内是否分别可读和可写就可以这样:
    先把两个句柄集(file descriptors)清零:
            FD_ZERO (&readfds);
            FD_ZERO (&writefds);
    然后把fd1加入读检测集:
            FD_SET (fd1, &readfds);
    然后把fd2加入写检测集:
            FD_SET (fd2, &writefds);
    再给timeout设置值,timeout是这样的一个结构:
                  struct timeval {
                      long    tv_sec;         /* seconds */
                      long    tv_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));}
    
            else if(ret == 0) {printf("select超时返回,没有任何句柄状态发生变化!");}
    
            //有句柄状态发生了变化
    
            if(FD_ISSET(fd1, &readfds)) {
    
                fd1有数据可读;
    
                fd1里的数据被读出来;
    
            }
    
            if(FD_ISSET(fd2, &writefds)) {
    
                fd2可写;
    
                fd2里发送数据给对方;
    
            }
    
        }
    
    /*----------------------示意代码结束--------------------------------------------*/
    


     

    用gcc tcpforwardport.c -o MyProxy编译此程序后运行效果如下:
    ./MyProxy 8000 80 172.16.100.218
    accepting connections on port 8000
    connect from 127.0.0.1


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

    关于select函数:
    其函数原型为:
    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    此函数的功能是由内核检测在timeout时间内,是否有readfds,writefds,exceptfds三个句柄集(file descriptors)里的某个句柄(file descriptor)的状态符合寻求,即readfds句柄集里有句柄可读或writefds句柄集里有可写或exceptfds句柄集里有例外发生,任何一个有变化函数就立即返回,返回值为timeout发生状态变化的句柄个数。
    n是所有readfds,writefds,exceptfds三个句柄集(file descriptors)里编号最大值加1。比如:要检测两个socket句柄fd1和fd2在timeout时间内是否分别可读和可写就可以这样:
    先把两个句柄集(file descriptors)清零:
            FD_ZERO (&readfds);
            FD_ZERO (&writefds);
    然后把fd1加入读检测集:
            FD_SET (fd1, &readfds);
    然后把fd2加入写检测集:
            FD_SET (fd2, &writefds);
    再给timeout设置值,timeout是这样的一个结构:
                  struct timeval {
                      long    tv_sec;         /* seconds */
                      long    tv_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));}
    
            else if(ret == 0) {printf("select超时返回,没有任何句柄状态发生变化!");}
    
            //有句柄状态发生了变化
    
            if(FD_ISSET(fd1, &readfds)) {
    
                fd1有数据可读;
    
                fd1里的数据被读出来;
    
            }
    
            if(FD_ISSET(fd2, &writefds)) {
    
                fd2可写;
    
                fd2里发送数据给对方;
    
            }
    
        }
    
    /*----------------------示意代码结束--------------------------------------------*/
    


     

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

    /*----------------------源代码代码开始--------------------------------------------*/
    
    int
    
    OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {
    
    /*************************关于本函数************************************
    
    *function_name: OpenSCPServer
    
    *参数说明:port整数型监听端口号,total整数型监听个数,sendbuflen整数型发送缓冲区大小
    
    *          recvbuflen整数型接收缓冲区大小,blockORnot整数型是否阻塞,reuseORnot整数型是否端口重用
    
    *purpose: 用来建立一个tcp服务端socket
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-05 20:00:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含GetCurrentTime.h头文件
    
    *********************************************************************/
    
        int    sockfd = 0, ret = 0, opt = 0, flags=1;
    
        struct sockaddr_in    laddr;
    
    
    
        ret = sockfd = socket(PF_INET, SOCK_STREAM, 0);
    
        if(ret < 0)    {
    
            sprintf(errorMessage, "OpenTCPServer socket() 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, "OpenTCPServer setsockopt() reuse error! 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, "OpenTCPServer setsockopt() recvbuf error! 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, "OpenTCPServer setsockopt() sendbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
            return -4;
    
        }
    
    
    
        ioctl(sockfd,FIONBIO,&blockORnot);/*block or not*/
    
    
    
        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, (struct sockaddr *)&laddr, sizeof(struct sockaddr));
    
        if(ret < 0)    {
    
            sprintf(errorMessage, "OpenTCPServer bind() 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, "OpenTCPServer listen() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
            close(sockfd);
    
            return -6;
    
        }
    
        sprintf(errorMessage, "OpenTCPServer opened on port.%d(%d) OK, socket(%d), buf(%d:%d)! %s", port, total, sockfd, sendbuflen, recvbuflen, GetCurrentTime(0, 0));
    
        return sockfd;
    
    }
    
    /*----------------------源代码代码结束--------------------------------------------*/
    


     

    2、连接服务器的函数

    /*----------------------源代码代码开始--------------------------------------------*/
    
    int
    
    ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {
    
    /*************************关于本函数************************************
    
    *function_name: ConnectSCPServer
    
    *参数说明:serverip服务器IP地址或主机名,serverport服务器端口,blockORnot整数型是否阻塞
    
    *purpose: 用来建立一个tcp客户端socket
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-05 20:40:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    
    *********************************************************************/
    
        int    serversock = 0, ret = 0;
    
        unsigned long    addr;
    
        struct sockaddr_in    sin;
    
        struct hostent *he;
    
    
    
        if((he=gethostbyname(serverip))== 0) {
    
            sprintf(errorMessage, "ConnectSCPServer IP address '%s' error! return:-1 %s", serverip, GetCurrentTime(0, 0));
    
            return -1;
    
        }
    
    
    
        serversock = socket(PF_INET, SOCK_STREAM, 0);
    
        if(serversock == -1)    {
    
            sprintf(errorMessage, "ConnectSCPServer socket() error! return:-2, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
    
            return -2;
    
        }
    
    
    
        ioctl(serversock, FIONBIO, &blockORnot);  //block or not
    
    
    
        memset((char*)&sin, 0, sizeof(struct sockaddr_in));
    
        sin.sin_family = PF_INET;
    
        sin.sin_port = htons(serverport);
    
        sin.sin_addr = *((struct in_addr *)he->h_addr);
    
    
    
        ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin));
    
    
    
        if(ret == -1)    {
    
            sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
    
            close(serversock);
    
            return -3;
    
        }
    
    
    
        return serversock;
    
    }
    
    /*----------------------源代码代码结束--------------------------------------------*/
    


    发送数据函数Send

    /*----------------------源代码代码开始--------------------------------------------*/
    
    int
    
    Send(int sock, char * buf, size_t size, int flag, int timeout)    {
    
    /*************************关于本函数************************************
    
    *function_name: Send
    
    *参数说明:sock整数型socket,buf待发送的内容,size要发送的大小,flag发送选项,timeout超时时间值
    
    *purpose: 用来通过一个socket在指定时间内发送数据
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-05 20:58:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    
    *********************************************************************/
    
        int i = 0, ret = 0, intretry = 0;
    
    
    
        struct timeval tival;
    
        fd_set writefds;
    
        int maxfds = 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, "Send socket:%d error! 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, "Send socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
            else sprintf(errorMessage, "Send socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
    
            close(sock);
    
            return -3;
    
        }
    
        if(!(FD_ISSET(sock, &writefds)))    {
    
            sprintf(errorMessage, "Send socket:%d not in writefds! %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, "Send socket:%d send() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
    
    
                if (EINTR == errno)
    
                  if(intretry < 10)  {intretry++;continue;}
    
                  else sprintf(errorMessage, "Send socket:%d send() error!EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
    
    
    
                close(sock);
    
                return -1;
    
            }
    
            else i += ret;
    
        }
    
        sprintf(errorMessage, "Send socket:%d send() OK! %d/%d bytes sent! %s", sock, i, size, GetCurrentTime(0, 0));
    
        return i;
    
    }
    
    /*----------------------源代码代码结束--------------------------------------------*/
    


     

    4、接收数据函数Recv

    /*----------------------源代码代码开始--------------------------------------------*/
    
    int
    
    Recv(int sock, char * buf, size_t size, int flag, int timeout)    {
    
    /*************************关于本函数************************************
    
    *function_name: Recv
    
    *参数说明:sock整数型socket,buf接收数据的缓冲区,size要接收数据的大小,flag接收选项,timeout超时时间值
    
    *purpose: 用来从一个socket在指定时间内读取数据
    
    *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
    
    Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
    
    *date time:2006-07-05 21:10:00
    
    *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
    
    * 但请遵循GPL
    
    *Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
    
    *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
    
    *Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
    
    *********************************************************************/
    
        int i = 0, ret = 0, intretry = 0;
    
    
    
        struct timeval tival;
    
        fd_set readfds;
    
        int maxfds = 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, "Recv socket:%d error! 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, "Recv socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
            else sprintf(errorMessage, "Recv socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
    
            close(sock);
    
            return -3;
    
        }
    
        if(!(FD_ISSET(sock, &readfds)))    {
    
            sprintf(errorMessage, "Recv socket:%d not in readfds! %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, "Recv socket:%d recv() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
    
                if(errno == EINTR)   
    
                    if(intretry < 10)  {intretry++;continue;}
    
                    else sprintf(errorMessage, "Recv socket:%d recv() error! EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
    
                close(sock);
    
                return -1;
    
            }
    
            else i += ret;
    
        }
    
        sprintf(errorMessage, "Recv socket:%d recv() OK! %d/%d bytes received! %s", sock, i, size, GetCurrentTime(0, 0));
    
        return i;
    
    }
    

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

    展开全文
  • LinuxTCP,UDP以及广播与多播通信(代码大全)

    千次阅读 多人点赞 2012-12-08 11:44:22
    TCP、UDP、广播、多播的客户端...tcp代码:http://download.csdn.net/detail/huangminqiang201209/4860661 udp代码:http://download.csdn.net/detail/huangminqiang201209/4860665 广播代码:http://download.csdn.

    TCP、UDP、广播、多播的客户端服务器代码链接地址为(for free):

    tcp代码:http://download.csdn.net/detail/huangminqiang201209/4860661
    udp代码:http://download.csdn.net/detail/huangminqiang201209/4860665
    广播代码:http://download.csdn.net/detail/huangminqiang201209/4860672
    多播代码:http://download.csdn.net/detail/huangminqiang201209/4860719

     

        此文主要还是在于上面的代码,我这段时间因为要构建一个TCP服务器,所以就把socket这块完整的熟悉了一下,以下这些整理的比较随意,还望见谅哦吐舌头

    TCP

        Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议,由IETF的RFC 793说明(specified)。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,UDP是同一层内另一个重要的传输协议。  
         在因特网协议族(Internet protocol suite)四层协议中,TCP层是位于IP层之上,应用层之下的传输层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。  

     

    UDP
         UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。

     

    广播

         广播和多播仅应用于UDP,它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议,它意味着分别运行于两主机(由IP地址确定)内的两进程(由端口号确定)间存在一条连接。
        考虑包含多个主机的共享信道网络如以太网。每个以太网帧包含源主机和目的主机的以太网地址(48 bit)。通常每个以太网帧仅发往单个目的主机,目的地址指明单个接收接口,因而称为单播(unicast)。在这种方式下,任意两个主机的通信不会干扰网内其他主机(可能引起争夺共享信道的情况除外)。然而,有时一个主机要向网上的所有其他主机发送帧,这就是广播。通过ARP和RARP可以看到这一过程。多播(multicast) 处于单播和广播之间:帧仅传送给属于多播组的多个主机。

     

    多播

        多播数据仅由对该数据报感兴趣的接口接收,也就是说,由运行希望参加多播会话应用系统的主机上的接口接收。广播一般局限与局域网,而多播既可用于局域网,也可用于广域网。
    IP多播提供两类服务:
        1) 向多个目的地址传送数据。有许多向多个接收者传送信息的应用:例如交互式会议系统和向多个接收者分发邮件或新闻。如果不采用多播,目前这些应用大多采用TCP来完成(向每个目的地址传送一个单独的数据复制)。然而,即使使用多播,某些应用可能继续采用TCP来保证它的可靠性。
        2) 客户对服务器的请求。例如,无盘工作站需要确定启动引导服务器。目前,这项服务是通过广播来提供的,但是使用多播可降低不提供这项服务主机的负担。

     

    函数

    1.socket 函数
       指定期望的通信协议类型。
    #include <sys/types.h> /* See NOTES */
    #include <sys/socket.h>

     int socket(int domain, int type, int protocol);
            返回:若成功则为非负描述符,出错则为-1。

    参数说明:
     domain:   指明协议族,也称为协议域,是一个常值。
               AF_INET              IPv4 协议
               AF_INET6             IPv6 协议
               AF_LOCAL/AF_UNIX       Unix协议域
               AF_ROUTE                 路由套接字
               AF_KEY                   密匙套接字
        
     type:    指明套接字的类型。
               SOCK_STREAM             字节流套接字(TCP)
               SOCK_DGRAM             数据报套接字(UDP)
               SOCK_SEQPACKET        有序分组套接字
               SOCK_RAW               原始套接字
       
     protocol: 指明协议类型。一般为0,以选择给定的domain和type组合的系统默认值。
               IPPROTO_TCP           TCP传输协议
               IPPROTO_UDP           UDP传输协议
               IPPROTO_SCTP          SCTP传输协议
    函数描述:
        socket 函数在成功时返回一个小的非负整数值,与文件描述符类似,我们称它为套接字描述符,简称 sockfd。为了得到这个套接字描述符,我们只是指定了协议族(IPv4、IPv6
        或Unix)和套接字类型(字节流、数据报或原始套接字)。我们并没有指定本地跟远程的协议地址。
        
    2.bind 函数    
       将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址和128位的IPv6地址与16位的TCP或UDP端口号的组合。bind 函数主要用于服务器端,用来指定本地
       主机的哪个网络接口(IP,可以是INADDR_ANY,表示本地主机的任一网络接口)可以接受客户端的请求,和指定端口号(即开启的等待客户来连接的进程)。
     
    #include <sys/socket.h>
     int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
             返回:若成功则为0,出错则为-1。

    参数说明:
        sockfd:          socket 函数返回的套接字描述符。
        myaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。

     struct sockaddr结构说明如下:
         struct sockaddr_in {
       short int sin_family;   /* 地址族 */
       unsigned short int sin_port;  /* 端口号 */
       struct in_addr sin_addr;   /* IP地址 */
       unsigned char sin_zero[8];  /* 填充0 以保持与struct sockaddr同样大小 */
         };

    函数描述:
        对于 TCP ,调用 bind 函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以两者都不指定。
        服务器在启动时捆绑它们众所周知的端口号(如何捆绑?)。如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用 connect 或 listen 时,内核就要为相应的套接字选择一个临时端口。
        让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口。然而对于TCP服务器来说却极为罕见,因为服务器是通过它们的众所周知的端口号来被大家认识的。
     
        进程可以把一个特定的IP捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一(对于TCP服务器)。对于TCP客户,这就为在该套接字上发送的IP数据报指派了源IP地址(服务器源地址)。对于TCP服务器,这就限定该套接字只接收那些目的地为这个IP地址的客户连接。TCP套接字通常不把IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源IP地址,而所用外出端口则取决于到达服务器所需的路径。如果TCP服务器没有把IP地址捆绑到它的套接字上,内核就会把发送的SYN的目的IP地址作为服务器的源IP地址(即服务器IP等于INADDR_ANY的情况)。
        实际上客户的源IP地址就是服务器的目的地址,服务器的源IP地址就是客户的目的地址,说到底也就只存在两个IP地址:客户IP跟服务器IP。
     
    3.connec函数
    t    TCP 客户用 connect 函数来与 TCP 服务器建立连接。
      
    #include <sys/socket.h>
     int connect( int sockfd, const struct sockaddr *servaddr, socklen_t addrlen );
     返回:若成功则为0,出错则为-1。

    参数说明:
        sockfd:            由 socket 函数返回的套接字描述符。    
        servaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。

    函数描述:
        客户在调用 connect 函数前并不一定得调用 bind 函数,如果需要的话,内核会确定源P地址,并选择一个临时端口作为源端口。所以在客户进程中的套接字一般只需指明客户所要连接的服务器的IP跟端口号。
        如果是 TCP 套接字,调用 connect 函数将激发 TCP 的三路握手。而且仅在连接成功或出错时才返回。其中出错的情况有如下几种:
        1-> TCP 客户没有收到 SYN 分节的响应。
        2-> TCP 服务器对客户的 SYN 分节的响应是 RST 。
        3-> 客户发出的 SYN 分节在某个路由器器上发生了错误。
        若 connect 调用失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次执行 connect 函数。

     

    4.listen 函数
    #include <sys/socket.h>
     int listen(int sockfd, int backlog);
             返回:若成功则为0,出错则为-1。

    函数描述:        
        listen 函数仅由 TCP 服务器调用,它做两件事情。
        (1)把一个未连接的套接字(主动)转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。
        (2)backlog 参数规定了内核应该为相应套接字排队的最大连接数。其中内核始终为监听套接字维护两个队列。
           (1)未完成连接队列,每个SYN分节对于其中一项:
               已由某个客户发出并到达服务器,而服务器正在等待待完成的TCP三路握手过程。这些套接字处于SYN_RCVD状态。
           (2)已完成连接队列
              每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
              backlog 就是这两个队列和的最大值。
        在三路握手完成之后,但在服务器调用 accept 之前到达的数据应由 TCP 服务器排队,最大数据量为相应已连接套接字的接收缓冲区的大小。

     

    5.accept 函数
        accept 函数由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成队列为空,那么进程被投入睡眠(假设套接字为默认的阻塞方式)。
    #include <sys/socket.h>
    int accept(int sockfd ,struct sockaddr *cliaddr, socklen_t *addrlen);
            返回:若成功则为非负已连接描述符和对端的IP和端口号,出错则为-1。

    参数说明:
        cliaddr、addrlen 用来返回已连接的对端进程(客户)的协议地址 。调用前,我们将由 *addrlen 所引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为内核存放在该套接字地址机构内的确切字节数。

    函数描述:
        如果 accept 调用成功,那么其返回值是由内核自动生成的一个全新描述符,代表着与所返回客户的TCP连接。在讨论 accept 函数时,我们称它的第一个参数为监听套接字描述符(由 socket 创建,随后用作bind 和 listen 的第一个参数的描述符),称它的返回值为已连接套接字描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在服务器的生命期内一直存在。内核为每个服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个连接客户的服务时,相应的已连接套接字就要被关闭。

     

    6.recv/recvfrom函数
        从套接字上接收一个消息。对于recvfrom ,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。如果消息太大,无法完整存放在所提供的缓冲区,根据不同的套接字,多余的字节会丢弃。假如套接字上没有消息可以读取,除了套接字已被设置为非阻塞模式,否则接收调用会等待消息的到来。

    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t recv(int sock, void *buf, size_t len, int flags);
    ssize_t recvfrom(int sock, void *buf, size_t len, int flags,  struct sockaddr *from, socklen_t *fromlen);

    参数:  
    sock:索引将要从其接收数据的套接字。
    buf:存放消息接收后的缓冲区。
    len:buf所指缓冲区的容量。
    flags:是以下一个或者多个标志的组合体,可通过or操作连在一起
    MSG_DONTWAIT:操作不会被阻塞。
    MSG_ERRQUEUE:指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。错误以sock_extended_err结构形态被使用,定义如下
    #define SO_EE_ORIGIN_NONE    0
    #define SO_EE_ORIGIN_LOCAL   1
    #define SO_EE_ORIGIN_ICMP    2
    #define SO_EE_ORIGIN_ICMP6   3
    struct sock_extended_err
    {
        u_int32_t ee_errno;   /* error number */
        u_int8_t ee_origin; /* where the error originated */
        u_int8_t ee_type;    /* type */
        u_int8_t ee_code;    /* code */
        u_int8_t ee_pad;
        u_int32_t ee_info;    /* additional information */
        u_int32_t ee_data;    /* other data */
        /* More data may follow */
    };

    MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
    MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
    MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
    MSG_EOR:指示记录的结束,返回的数据完成一个记录。
    MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
    MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
    MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
    MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
    from:指向存放对端地址的区域,如果为NULL,不储存对端地址。
    fromlen:作为入口参数,指向存放表示from最大容量的内存单元。作为出口参数,指向存放表示from实际长度的内存单元。

     

    7.send/sendto函数
         用于发送消息。send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。sendto 和 sendmsg既可用于无连接的套接字,也可用于基于连接的套接字。除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
     
    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t send(int sock, const void *buf, size_t len, int flags);
    ssize_t sendto(int sock, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
    ssize_t sendmsg(int sock, const struct msghdr *msg, int flags);

    参数: 
    sock:索引将要从其发送数据的套接字。
    buf:指向将要发送数据的缓冲区。
    len:以上缓冲区的长度。len可以大于sizeof(buf),例如send("123",10)-->则实际发送10字节数据
    flags:是以下零个或者多个标志的组合体,可通过or操作连在一起
    MSG_DONTROUTE:不要使用网关来发送封包,只发送到直接联网的主机。这个标志主要用于诊断或者路由程序。
    MSG_DONTWAIT:操作不会被阻塞。
    MSG_EOR:终止一个记录。
    MSG_MORE:调用者有更多的数据需要发送。
    MSG_NOSIGNAL:当另一端终止连接时,请求在基于流的错误套接字上不要发送SIGPIPE信号。
    MSG_OOB:发送out-of-band数据(需要优先处理的数据),同时现行协议必须支持此种操作。
    to:指向存放接收端地址的区域,可以为NULL。
    tolen:以上内存区的长度,可以为0。
    msg:指向存放发送消息头的内存缓冲,结构形态如下
    struct msghdr {
        void           *msg_name;     
        socklen_t      msg_namelen;  
        struct iovec  *msg_iov;      
        size_t          msg_iovlen;   
        void           *msg_control;  
        socklen_t      msg_controllen;
        int             msg_flags;    
    };
    可能用到的数据结构有
    struct cmsghdr {
        socklen_t cmsg_len;   
        int       cmsg_level; 
        int       cmsg_type; 
    };

     

    8.getsockname 函数
      获取一个套接口的本地名字。
    #include <sys/socket.h>
        int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
             返回:成功则不返回,出错则为-1。

    参数:
     sockfd:标识一个已捆绑套接口的描述字。   
       localaddr:接收套接口的地址(名字)。   
       addrlen:名字缓冲区长度。

    函数描述:
          getsockname()函数用于获取一个套接字的名字。它用于一个已捆绑或已连接套接字sockfd,本地地址将被返回。本调用特别适用于如下情况:未调用bind()就调用了connect(),这时唯有getsockname()调用可以获知系统内定的本地地址。在返回时,namelen参数包含了名字的实际字节数。   
         若一个套接字与INADDR_ANY捆绑,也就是说该套接字可以用任意主机的地址,此时除非调用connect()或accept()来连接,否则getsockname()将不会返回主机IP地址的任何信息。除非套接字被连接,WINDOWS套接字应用程序不应假设IP地址会从INADDR_ANY变成其他地址。这是因为对于多个主机环境下,除非套接字被连接,否则该套接字所用的IP地址是不可知的。

     

    9.getpeername 函数函数
      获取与套接口相连的端地址。
     int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
             返回:成功则不返回,出错则为-1。

    函数描述:
     getpeername()函数用于从端口sockfd中获取与它捆绑的端口名,并把它存放在sockaddr类型的name结构中。它适用于数据报或流类套接口。

     

    10.setsockopt/getsockopt函数
     设置/获取套接口选项
      #include <winsock.h>
      int setsockopt(int sockfd, int level, int optname, void *optval,  int optlen);
      int getsockopt(int sockfd, int level, int optname, void *optval,  int *optlen) ;

    参数:
      s:标识一个套接口的描述字。
      level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
      optname:需设置的选项。
      optval:指针,指向存放选项值的缓冲区。
      optlen:optval缓冲区的长度。

    注释:
            setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

     

     

     

    展开全文
  • Linux测试TCP通信

    2019-11-25 14:03:17
    传输控制协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门...现在就来尝试一下在Linux上实现TCP-Server和TCP-Client的双向通信。 系统环境:Ubuntu 18.04/vi...
  • 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网络通信过程

    千次阅读 2017-05-28 11:16:06
    编写服务器时,许多程序员习惯于使用高层次的组件、中间件(例如OO(面向对象)层层封装过的开源组件),相比于服务器的运行效率而言,他们更关注程序开发的效率,追求更快的完成项目功能点、希望应用代码完全不关心...
  • Linux TCP/IP 协议栈之 Socket的实现分析

    千次阅读 2011-06-30 09:48:00
    Linux TCP/IP 协议栈之 Socket的实现分析(一 套接字的创建) [size=6]Linux TCP/IP 协议栈之 Socket的实现分析[/size] 内核版本:2.6.12 作者:kendo 版权所有,转载请注明出处[[url]www.skynet.org.cn[/url]];...
  • Linux下TCP,UDP以及广播与多播通信(代码大全) ...tcp代码:http://download.csdn.net/detail/huangminqiang201209/4860661 udp代码:http://download.csdn.net/detail/huangminqiang201209/4
  • 用于常见的“包头,包体,包尾”等等封包格式,给server或client设置一个封包解析函数后,则server或client在收到数据时会自动调用解析函数进行拆包组包等操作,确保投递给用户数据接收监听器的是一个完整的封包;...
  • Linux_tcpdump抓分析详解

    万次阅读 2020-05-19 21:00:13
    tcpdump是一个用于截取网络分组,并输出分组内容的工具,简单说就是数据包抓工具。tcpdump凭借强大的功能和灵活的截取策略,使其成为Linux系统下用于网络分析和问题排查的首选工具。tcpdump提供了源代码,公开了...
  • Linux TCP/IP 协议栈数据发送流程

    千次阅读 2017-08-13 16:50:05
    tcp protocal
  • Linux TCP/UDP小例子

    万次阅读 2013-03-31 17:33:16
    1、网络中进程之间如何通信? 2、Socket是什么? 3、socket的基本操作 3.1、socket()函数 3.2、bind()函数 3.3、listen()、connect()函数 ...4、socket中TCP的三次握手建立连接详解
  • http://linux.chinaunix.net/techdoc/system/2007/10/10/969538.shtml  好文不得不转: http://bbs.chinaunix.net/viewthread.php?tid=786283 作者:zhoulifa, safedead zhoulifa   发表于: 2006-7-5 23...
  • Linux下进行TCP简单通信

    千次阅读 2018-05-09 23:03:34
    体会TCP与UDP编程的不同,UDP编程:http://blog.csdn.net/yueguanghaidao/article/details/7055985二、实验平台Linux操作系统三、实验内容编写LinuxTCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦...
  • sk_buff结构可能是linux网络代码中最重要的数据结构,它表示接收或发送数据包的包头信息。它在中定义,并包含很多成员变量供网络代码中的各子系统使用。 这个结构在linux内核的发展过程中改动过很多次,或者是增加新...
  • sk_buff结构可能是linux网络代码中最重要的数据结构,它表示接收或发送数据包的包头信息。它在中定义,并包含很多成员变量供网络代码中的各子系统使用。 这个结构在linux内核的发展过程中改动过很多次,或者是...
  • Linux TCP/IP 协议栈源码分析 - 数据 发送/接收 流程图
  • Golang可以通过syscall包中的相关socket函数(Windows和Linux中syscall中网络编程的函数不太一样)来进行网络编 程,可以用我们熟悉的方法使用raw socket,因为最近在搞...代码如下: package main import ( "bytes
  • 基于Linux下的TCP编程

    万次阅读 多人点赞 2011-09-14 18:27:31
    基于LinuxTCP网络编程一.LinuxTCP编程框架TCP网络编程的流程包含服务器和客户端两种模式。服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;客户端模式则...
  • Linux内核中TCP的连接跟踪

    千次阅读 2012-09-11 11:10:11
    在2.6.1*以上的Linux内核中,关于TCP连接跟踪处理有了比较大的修改,增加了TCP可能标志位组合的检查;增加了通过序列号、确认号和窗口值来判断数据包合法性的功能,支持SACK选项;状态转换数组也进行了一些修改和...
  • TCP发包

    千次阅读 2009-08-12 16:07:00
    TCP Test Tool、anysend、pcattcp、tcpreplay、tcp_send、Hping、sendip、nessus、ipsend、ippacket、sniffer =======================================================常用IP发包工具介绍1. 简介在从事网络产品...
  • 原文出处: Dan Nanni 译文出处:Linux.Cn-KayGuoWhu  如今很多系统管理员依然通过组合...net-tools起源于BSD的TCP/IP工具箱,后来成为老版本Linux内核中配置网络功能的工具。但自2001年起,Linux社区已经对其停
  • 本文通过学习RealTek8169/8168/8101网卡的驱动代码(drivers/net/r8169.c),梳理一下Linux下网卡的收过程。 在下水平相当有限,有不当之处,还请大家斧正^_^   驱动的初始化 如下的rtl8169_init_module函数是...
  • linux socket编程之TCP与UDP

    千次阅读 2012-02-15 21:41:37
    TCP/IP虽然叫传输控制协议(TCP)和网际协议(IP),但是实际上是一协议,包含ICMP, RIP, TELENET, FTP, SMTP, ARP, TFTP等。 从协议分层模型方面来讲,TCP/IP由四个层次组成:网络接口层、网络层、传输层...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 77,094
精华内容 30,837
关键字:

linuxtcp代码组包

linux 订阅