zoukankan      html  css  js  c++  java
  • linux高级编程day10 笔记

    一.TCP的编程模型
     回顾:
      UDP模型的UML图
      TCP模型的UML图
     案例1:
      TCP的服务器(在案例中使用浏览器作为客户程序)  
     socket建立服务器的文件描述符号缓冲
     bind把IP地址与端口设置到文件描述符号中
     listen负责根据客户连接的不同IP与端口,负责生成对应的文件描述符号及其信息
     accept一旦listen有新的描述符号产生就返回,否则阻塞。

    View Code
    //tcpserver.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    main()
    {
        int serverfd;
        int cfd;
        int a;
        struct sockaddr_in sadr;
        struct sockaddr_in cadr;
        socklen_t len;
        int r;
        char buf[1024];
        //1.socket
        serverfd=socket(AF_INET,SOCK_STREAM,0);
        if(serverfd==-1) printf("1:%m\n"),exit(-1);
        printf("建立服务器socket成功!\n");
        //2.bind
        sadr.sin_family=AF_INET;
        sadr.sin_port=htons(9999);
        inet_aton("192.168.180.92",&sadr.sin_addr);
        r=bind(serverfd,
                (struct sockaddr*)&sadr,sizeof(sadr));
        if(r==-1) printf("2:%m\n"),exit(-1);
        printf("服务器地址绑定成功!\n");
        
        //3.listen
        r=listen(serverfd,10);
        if(r==-1) printf("3:%m\n"),exit(-1);
        printf("监听服务器成功!\n");
        
        //4.accept
        len=sizeof(cadr);
        cfd=accept(serverfd,
                (struct sockaddr*)&cadr,&len); //每接受一个新的连接,就会返回一个新的文件描述符,来分辨是哪个连接。
        printf("有人连接:%d,IP:%s:%u\n",
                cfd,inet_ntoa(cadr.sin_addr),
                ntohs(cadr.sin_port));        
        
        //5.处理代理客户描述符号的数据
        while(1)
        {
            r=recv(cfd,&a,4,MSG_WAITALL);        
            if(r>0)
            {
                //buf[r]=0;
                printf("::%d\n",a);
            }
            
            if(r==0)
            {
                printf("连接断开!\n");
                break;
            }
            if(r==-1)
            {
                printf("网络故障!\n");
                break;
            }
        }
        close(cfd);
        close(serverfd);
    }

    案例2:
       每个客户的代理描述符号的通信

    二.TCP通信特点(相对于UDP)
     案例3:
      有连接:主要连接后,发送数据不用指定IP与端口
      数据无边界:TCP数据流,非数据报文.
      描述符号双工:
      数据准确:TCP协议保证数据时完全正确
     案例4:
      使用TCP发送数据注意:
        不要以为固定长的数据,一定接收正确,要求使用MSG_WAITALL
      
     案例5:
      TCP数据发送的分析:
        基本数据int short long float  double
        结构体数据struct
        建议使用MSG_WAITALL
        字符串数据以及文件数据等不固定长度的数据怎么发送?
        
      制定数据包:
         头:大小固定(数据大小)
         体:大小变化(数据)  
     案例6:
       使用TCP传送文件
       定义文件数据包.
         int 数据大小;
         char[]数据
           
       传递文件名
       传递数据(循环)
       传递0长度的数据表示文件结束
    代码如下:

    View Code
    //demo1Client.c
    //发送端的代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    main()
    {
        //1. 建立socket
        //2. 连接到服务器
        //3. 打开文件
        //4. 发送文件名
        //5. 循环发送文件
        //6. 读取到文件尾,发送0数据包
        int sfd; //socket描述符
        int ffd; //文件描述符
        int size;   //读取和发送文件的长度
        int r;  //函数返回值
        
        int  len;  //要发送的文件名的长度
        char buf[128]; //数据的缓存
        
        struct sockaddr_in dr;  //网络地址
        char filename[]="udp_a.c";  //文件名
        
        //1.建立socket
        sfd=socket(AF_INET,SOCK_STREAM,0);
        if(sfd==-1) 
            printf("1:%m\n"),exit(-1);
        printf("socket成功!\n");
        //2.连接到服务器
        dr.sin_family=AF_INET;
        dr.sin_port=htons(9988);
        inet_aton("192.168.180.92",&dr.sin_addr);
        r=connect(sfd,(struct sockaddr*)&dr,sizeof(dr));
        if(r==-1) 
            printf("2:%m\n"),close(sfd),exit(-1);    
        printf("connect成功!\n");
        //3.打开文件
        ffd=open(filename,O_RDONLY);
        if(ffd==-1) 
            printf("3:%m\n"),close(sfd),exit(-1);
        printf("open文件成功!\n");
        //4.发送文件名
        len=strlen(filename);    
        r=send(sfd,&len,sizeof(len),0);//发送文件名长度(告诉流,文件名占多长)
        r=send(sfd,filename,len,0);//发送文件名 
        if(r==-1)
        printf("4:%m\n"),close(ffd),close(sfd),exit(-1);
        printf("发送文件名成功!\n");
        //5.循环发送数据
        while(1)
        {
            size=read(ffd,buf,128);
            if(size==-1) break; //read错误,跳出循环
            if(size==0) break;  //读到文件尾,跳出循环
            if(size>0)
            {
                //先发送数据的长度,再发送数据
                //发送数据长度
                r=send(sfd,&size,sizeof(size),0);
                if(r==-1) break;
                r=send(sfd,buf,size,0);//发送数据
                if(r==-1) break;
            }
        }
        //6.读取到文件尾,发送0数据包
        size=0;
        r=send(sfd,&size,sizeof(size),0);
        close(ffd);
        close(sfd);
        printf("OK!\n");
    }
    View Code
    //demo1server.c
    //接收服务器的代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    main()
    {
        //1. 建立服务器socket
        //2. 绑定IP地址与端口
        //3. 监听
        //4. 接收连接
        //5. 接收文件名
        //6. 创建文件
        //7. 循环接收文件数据
        
        int sfd, cfd, ffd;
        int r;
        int len;
        char buf[128];  //发送端定义的缓存大小是128,这里最好不要小于128
        char filename[100];
        struct sockaddr_in dr;
        //1. 建立服务器socket
        sfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sfd == -1)
            printf("1:%m\n"), exit(-1);
        printf("socket服务器创建成功!\n");
        //2. 绑定IP地址与端口
        dr.sin_family = AF_INET;
        dr.sin_port = htons(9988);
        inet_aton("192.168.180.92", &dr.sin_addr); //dr.sin_addr.s_addr = inet_addr("192.168.180.92");注意区别
        r = bind(sfd, (struct sockaddr*)&dr, sizeof(dr));
        if(r == -1)
            printf("2:%m"), close(sfd), exit(-1);
        printf("绑定地址成功!\n");
        //3. 监听
        r = listen(sfd, 10);
        if(r == -1)
            printf("3:%m\n"), close(sfd), exit(-1);
        printf("监听成功!\n");
        //4. 接收连接
        cfd = accept(sfd, 0, 0); //这里我们不关心发送端的IP等信息,所以后面两个参数都为0。这里返回一个新的描述符,代表接收的连接
        if(cfd == -1)
            printf("4:%m\n"), close(sfd), exit(-1);
        printf("开始接收文件!\n");
        //5. 接收文件名
        r = recv(cfd, &len, sizeof(len), MSG_WAITALL);  //接收文件名的长度
        r = recv(cfd, filename, len, MSG_WAITALL);  //根据文件名长度,接收文件名
        filename[r] = 0;  //在文件名后面加结束符
        //6. 创建文件
        ffd = open(filename, O_CREAT|O_RDWR, 0666);   //如果文件存在,直接覆盖.不要和发送文件放在同一个目录运行,会覆盖发送文件
        if(ffd == -1)
            printf("6:%m\n"), close(sfd), close(cfd), exit(-1);
        printf("创建文件成功!\n");
        //7. 循环接收文件数据
        while(1)
        {
            r = recv(cfd, &len, sizeof(len), MSG_WAITALL);
            if(len == 0)
                break; //长度为0,表示文件传送完毕的信号
            r = recv(cfd, buf, len, MGS_WAITALL);
            r = write(ffd, buf, len);
        }
        close(ffd);
        close(cfd);
        close(sfd);
        printf("接收数据完毕!\n");
    }

    PS:UDP面向无连接,TCP面向连接,所以推荐UDP不用connect,直接sendto, 而TCP则先连接,然后send,而不是sendto。

    三.TCP服务器编程模式
      TCP的服务器端维护多个客户的网络文件描述符号.
      对服务器多个客户描述符号同时做读操作,是不可能.需要多任务模型完成.
      多任务模型?
      1.多进程
      2.IO的异步模式(select模式/poll模式)
      3.多线程模式  
      4.多进程池
      5.线程池

    四.综合应用--多进程应用
      1.怎样使用多进程
      2.多进程的缺陷,以及怎么解决

    小例子:用TCP写一个聊天程序
       客户端
         2.1.建立socket
         2.2.连接服务器
         2.3.创建CURSES界面
         2.4.创建子进程
         2.5.在父进程中,输入,发送聊天信息
         2.6.在子进程中,接收服务器传递其他客户聊天信息

    View Code
    //chatclient.c
    //聊天程序客户端
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <curses.h>
    #include <signal.h>
    WINDOW*winfo,*wmsg;
    int fd;
    int r;
    struct sockaddr_in dr;
    int isover=1;
    
    int initSocket();   //初始化:创建描述符,绑定IP
    void initUI();   //初始化curses界面
    void destroy();  //清理:释放UI, 关闭网络
    void handle(int s)
    {
        int status;
        wait(&status);
        destroy();
        exit(-1);    
    }
    main()
    {    
        //printf("网络初始化成功!\n");
        initUI();
        r=initSocket();    
        if(r==-1) exit(-1);
        signal(SIGCHLD,handle);
        if(fork())
        {
            //父进程,输入,发送
            char buf[256];
            while(1)
            {
                mvwgetstr(wmsg,1,1,buf);
                //buf[r]=0;
                send(fd,buf,strlen(buf),0);            
                //wclear(wmsg);
                //box(wmsg,0,0);
                refresh();
                wrefresh(wmsg);
                wrefresh(winfo);
            }
        }
        else
        {
            
            //子进程,接收,显示
            char buf[256];
            int line=1;
            while(1)
            {            
                r=recv(fd,buf,255,0);
                if(r==-1) break;
                if(r==0) break;
                buf[r]=0;
                mvwaddstr(winfo,line,1,buf);
                line++;            
                if(line>=(LINES-3))
                {
                    wclear(winfo);
                    line=1;
                    box(winfo,0,0);                
                }
                
                
                wmove(wmsg,1,1);
                touchwin(wmsg);
                refresh();
                wrefresh(winfo);            
                wrefresh(wmsg);
            }
            exit(-1);
        }
            
        destroy();
    }
    void destroy()
    {
        close(fd);
        endwin();
    }
    void initUI()
    {
        initscr();
        winfo=derwin(stdscr,(LINES-3),COLS,0,0);
        wmsg=derwin(stdscr,3,COLS,LINES-3,0);
        keypad(stdscr,TRUE);
        keypad(wmsg,TRUE);
        keypad(winfo,TRUE);
        box(winfo,0,0);
        box(wmsg,0,0);
        refresh();
        wrefresh(winfo);
        wrefresh(wmsg);
    }
    
    int initSocket()
    {
        fd=socket(AF_INET,SOCK_STREAM,0);
        if(fd==-1) return -1;
            
        dr.sin_family=AF_INET;
        dr.sin_port=htons(9989);
        dr.sin_addr.s_addr=inet_addr("192.168.180.92");
        r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
        if(r==-1)
        {
            close(fd);
            return -1;
        }
        return 0;  //fd是全局变量,不用返回。初始化成功,返回0
    }
    View Code
    //chatserver.c
    //聊天程序服务器端
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/mman.h>
    int sfd;
    int *fds;//存放所有客户代理描述符号
    int idx=0;//客户在数组中下标
    struct sockaddr_in dr;
    int r;
    main()
    {
        //1. 建立服务器socket
        //2. 绑定地址
        //3. 监听
        //4. 循环接收客户连接
        //5. 建立一个子进程
        //6. 子进程任务:接收客户数据并且广播
        
        
        //1.建立服务器 socket
        fds=mmap(0,4*100,PROT_READ|PROT_WRITE,
            MAP_ANONYMOUS|MAP_SHARED,0,0);
        bzero(fds,sizeof(fds));
        sfd=socket(AF_INET,SOCK_STREAM,0);
        if(sfd==-1) printf("1:%m\n"),exit(-1);    
        printf("socket OK!\n");
        //2.绑定地址
        dr.sin_family=AF_INET;
        dr.sin_port=htons(9989);
        dr.sin_addr.s_addr=inet_addr("192.168.180.92");
        r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
        if(r==-1) printf("2:%m\n"),exit(-1);
        printf("bind ok!\n");
        //3.监听
        r=listen(sfd,10);
        if(r==-1) printf("3:%m\n"),exit(-1);
        printf("listen ok!\n");
        //4.循环接收客户连接
        while(1)
        {
            fds[idx]=accept(sfd,0,0);
            if(fds[idx]==-1) break;
            printf("有客户连接:%d\n",fds[idx]);
            //5.建立一个子进程
            if(fork())
            {
                idx++;
                continue;
            }
            else
            {        
                //6.子进程任务:接收客户数据并且广播
                char buf[256];
                int i;
                printf("开始接收客户数据:%d\n",fds[idx]);
                while(1)
                {
                    //接收客户数据
                    r=recv(fds[idx],buf,255,0);
                    printf("%d\n",r);
                    if(r==0)
                    {
                        printf("有客户退出\n");
                        close(fds[idx]);
                        fds[idx]=0;
                        break;                    
                    }
                    if(r==-1)
                    {
                        printf("网络故障\n");
                        close(fds[idx]);
                        fds[idx]=0;
                        break;
                    }
                    buf[r]=0;
                    printf("来自客户的数据:%s\n",buf);
                    //广播
                    for(i=0;i<100;i++)
                    {
                        if(fds[i]>0)
                        {
                            send(fds[i],buf,r,0);
                        }    
                    }
                }
                exit(0);
            }
        }
        close(sfd);    
    }        


    总结:
       建立socket
       绑定地址
       监听
       循环接收客户连接
       为客户创建子进程
       在子进程接收该客户的数据,并且广播

    总结:
      1.TCP的四大特点
      2.TCP的数据接收:固定长与变长数据的接收
      3.TCP的服务器多进程处理
         问题:多进程由于进程资源结构独立.
             新进程的文件描述符号的环境在老进程无法访问?

    作业:
      思考:
        有什么编程技巧可以解决进程的文件描述符号的一致?
        
      作业:
        完成TCP的聊天程序.
          1.数据能运行
          2.处理僵死进程
          3.服务器退出,客户也能正常结束
          4.客户退出,服务器也能够正确结束客户连接.

  • 相关阅读:
    垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
    自考感悟,话谈备忘录模式
    [每日一题] OCP1z0-047 :2013-07-26 alter table set unused之后各种情况处理
    Java实现 蓝桥杯 算法提高 p1001
    Java实现 蓝桥杯 算法提高 拿糖果
    Java实现 蓝桥杯 算法提高 拿糖果
    Java实现 蓝桥杯 算法提高 求arccos值
    Java实现 蓝桥杯 算法提高 求arccos值
    Java实现 蓝桥杯 算法提高 因式分解
    Java实现 蓝桥杯 算法提高 因式分解
  • 原文地址:https://www.cnblogs.com/tangzhengyue/p/2615551.html
Copyright © 2011-2022 走看看