首页 > 学技术 > 技术网文 > C/C++ > 正文

[原创] IPv[46]简单TCP服务器客户端[200行打造]


来源 chinaunix.net kuqin整理


/**********************************************
作者:Minuit
时间:2007年01月30日 星期二 04时36分08秒
文件名:tcp6servcli.c
描述:tcp服务器客户端
**********************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
enum {
        NETINIT_TCPSERV=1,  /*创建一个tcp已经监听套接口服务器*/
        NETINIT_TCPCLI,         /*创建一个tcp已经连接套接口客户端*/
        NETINIT_TCPCLIBIND,  /*创建一个tcp已经连接并且绑定本IP地址和端口的客户端*/
        NETINIT_UDPSERV,   /*创建一个udp已经绑定套接口服务器*/
        NETINIT_UDPCLI,    /*创建一个udp客户端*/
        NETINIT_UDPCLICONN,  /*创建一个udp并且已经连接服务器套接口客户端*/
        NETINIT_BACKLOG=100  
};
/*
netinit 所能干的事情就上面那几种
第一个参数主机名或者ip地址(当建服务器时此参数可为空默认BING一个通配的ipv6地址)
第二个参数服务名在/etc/services或者端口号(不能为空)
第三,四参数就不用介绍了说说有什么用处当建立服务器时他返回相关协议的地址长度和地址结构两个都可能为空
根据须要比如建一个服务器当调用accept(2)的时候如果想返回客户机的信息那就要用到第四个参数地址长度(注:accept后两个参数是可选的)又比如客户端想用sendto发送信息到服务器那就可能须要一个服务器的地址如果你填了第三个参数它就返回一个服器的地址结构这个还是很智能的(^_^)当然像udp服务器只有调用recvfrom或者recvmsg才须要第四个参数,udp客户端只用用sendto或者sendmsg时会用到第三个参数总之只要第三,四个参数不空它就会给你填上你想要的东西如果为空那就是你不想让他返回什么(你也可以过后调用get[sock|peer]name系统调用获取你想要的结构信息)
第四个参数已经说过了
*/
int netinit(const char *host,const char *serv,struct sockaddr *addr,socklen_t *addrlen,int flags)
{
        struct addrinfo hist,*res,*save;   
        int sockfd,on=1,errcode;
        bzero(&hist,sizeof(struct addrinfo));
        if(serv==NULL)
                return -1;
        hist.ai_family=AF_UNSPEC;
        switch(flags)
          {
                case NETINIT_TCPSERV:
                case NETINIT_UDPSERV:
                        hist.ai_flags=AI_PASSIVE;   /*如果是服务器的话就设置被动模式*/
                        break;
                case NETINIT_TCPCLI:
                case NETINIT_UDPCLI:
                case NETINIT_UDPCLICONN:
                case NETINIT_TCPCLIBIND:
                        hist.ai_flags=AI_CANONNAME;  /*如果是客户端那就addrinfo结构组成的链表第一个结构里面的一个成员被填充主机名*/
                        break;
                default:
                        errno=ENOTSUP;  /*不支持有时间把unix域加上*/
                        return -1;
          }
        switch(flags)
          {
                case NETINIT_TCPCLI:
                case NETINIT_TCPSERV:
                case NETINIT_TCPCLIBIND:
                        hist.ai_socktype=SOCK_STREAM;  /*udp还是tcp*/
                        break;
                case NETINIT_UDPSERV:
                case NETINIT_UDPCLI:
                case NETINIT_UDPCLICONN:
                        hist.ai_socktype=SOCK_DGRAM;
                        break;
                default:
                        errno=ENOTSUP;
                        return -1;
          }
        if((errcode=getaddrinfo(host,serv,&hist,&res))!=0)
          {
                fprintf(stderr,"getaddrinfo:%s\n",gai_strerror(errcode));
                return -1;
          }
        save=res;  /*保存res待会好毁了它*/
        do
          {
               /*创建套接口不管是服务器还是客户端的共同点必须要做的*/
                if((sockfd=socket(res->ai_family,res->ai_socktype,res->ai_protocol))>0)
                  {
                        /*要绑定地址的者在这里做*/
                        if(flags==NETINIT_TCPSERV||flags==NETINIT_TCPCLIBIND||flags==NETINIT_UDPSERV)
                          {
                                if(flags!=NETINIT_TCPCLIBIND)
                                        if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))!=0)  /*给服务器设置端口重用*/
                                                return -1;
                                if(bind(sockfd,res->ai_addr,res->ai_addrlen)==0)
                                        break;
                          }
                        else
                          {
                               /*要建连接的者在这里做*/
                                if(flags!=NETINIT_UDPCLI)
                                  {
                                        if(connect(sockfd,res->ai_addr,res->ai_addrlen)==0)
                                                break;
                                  }
                                else
                                        break;
                          }/*如果成功建立套接口那就先出去不然等到链表里的结构都试完了就出去*/
                  }
                close(sockfd);  /*如果创建不成功的话先关闭再说再试下面一个结构*/
          } while((res=res->ai_next)!=NULL);  
        if(res==NULL)  /*如果失败了返回*/
                return -1;
        if(addr!=NULL)  
                memcpy(addr,res->ai_addr,res->ai_addrlen);
        if(addrlen!=NULL)
                *addrlen=res->ai_addrlen;
        freeaddrinfo(save);  /*释放addrinfo链表之前已经保存了地址*/
        if(flags!=NETINIT_TCPSERV)  /*如果是tcp服务器的话还有一点事没做其它的返回*/
                return sockfd;
        else
                if(listen(sockfd,NETINIT_BACKLOG)!=0)     /*监听套接口*/
                        return -1;
        return sockfd;
}
void usage(char *arg)
{
        fprintf(stderr,\
                        "Usage:%s [-cs] [<ip or hostname>] <port or server>\n",\
                        arg);
        exit(-1);
}
int main(int argc,char **argv)
{
        struct sockaddr *addr;
        socklen_t  addrlen;
        int servfd,clifd,pid,c,mode=1;
        opterr=0;
        while((c=getopt(argc,argv,"cs"))!=EOF)
          {
                switch(c)
                  {
                        case 'c':
                                mode=0;
                                break;
                        case 's':
                                mode=1;
                                break;
            default:
                                usage(argv[0]);
                  }
          }
        if((addr=malloc(sizeof(struct sockaddr_in6)))==NULL)
                return -1;
        c=argc-optind;
        switch(c)
          {
                case 1:
                        if(!mode)
                                usage(argv[0]);
                        servfd=netinit(NULL,argv[optind],addr,NULL,NETINIT_TCPSERV);
                        break;
                case 2:
                        servfd=netinit(argv[optind],argv[optind+1],addr,mode?NULL:&addrlen,mode?NETINIT_TCPSERV:NETINIT_TCPCLI);
                        break;
                default:
                        usage(argv[0]);
          }
        if(servfd==-1)
                usage(argv[0]);
        if(mode)
          {
                while(1)
                  {
                        if((clifd=accept(servfd,addr,&addrlen))<=0)
                          {
                                fprintf(stderr,"accept:%s\n",strerror(errno));
                                return -1;
                          }
                        if((pid=fork())<0)
                          {
                                fprintf(stderr,"fork:%s\n",strerror(errno));
                                return -1;
                          }
                        else if(pid==0)
                          {
                                char buf[BUFSIZ];
                                int n;
                                while((n=read(clifd,buf,BUFSIZ))>0)
                                        write(clifd,buf,n);
                                exit(0);
                          }
                        close(clifd);
                  }
          }
        else
          {
                int n;
                char buf[BUFSIZ];
                while(1)
                  {
                if(n=read(STDIN_FILENO,buf,BUFSIZ))
                  {
                  if(write(servfd,buf,n)!=n)
                                  break;
                  }
                else
                        break;
                if((n=read(servfd,buf,BUFSIZ))>0)
                  {
                         if(write(STDOUT_FILENO,buf,n)!=n)
                                 break;
                  }
                else
                        break;
                  }
                close(servfd);
          }
        return 0;
}


看了一步一步网络编程*不知道还有人爱好这个
就把我前几个月写的通用的网络初始化函数截下来写这个二百行的服务器+客户端简单网络程序
让大家也偷偷懒写ipv[46]服务器客户端也就那两下子而且主机名IP地址 服务端口都可以用
我就不试范怎么用(4点多钟来的才写了这么多注释^_^!)
就说一下两个命令行参数
-c  以客户端起动
-s  以服务器起动
如果没有选项的话那默认就服务器
服务器还最少要一个参数那就是端口或者服务名如果两个参数那就绑定第一个参数所指的ip地址
客户端必须两个参数并且-c选项不能少
就这么多了
如果有什么不足之处还请指出^_^

[ 本帖最后由 lovesaka 于 2007-1-30 07:13 编辑 ]



 percy 回复于:2007-01-30 09:26:01

谢谢LZ,学习哈


 lovesaka 回复于:2007-01-30 17:03:58

这个通用的与协议无关函数不好用吗
怎么没大侠出来指点一下呀^_^!


 arenxl 回复于:2007-01-30 19:29:56

多谢lz共享,只是我的水平还没有到那个指点的水平上 ~~
等以后进步了再说吧,呵呵


 lovesaka 回复于:2007-02-03 02:08:37

多谢两位支持
看来这东西使用上还是有点麻烦
但是他确实可以去掉很多每次老是要写的代码,又是IPV4,IPV6都能用的麻烦也是值得的^_^


 bleem1998 回复于:2007-02-03 12:00:52

支持
虽然没看代码
但感觉是非常实用的东西啊
收藏了将来没准能用上




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=893242
转载请注明作者名及原文出处



收藏本页到:      

收藏本页到: