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.客户退出,服务器也能够正确结束客户连接.

  • 相关阅读:
    vim插件:显示树形目录插件NERDTree安装 和 使用【转】
    CMake 入门实战【转】
    在 linux 下使用 CMake 构建应用程序【转】
    Buildroot构建指南——根文件系统(Rootfs)【转】
    Buildroot构建指南--快速上手与实用技巧【转】
    Vim升华之树形目录插件NERDTree安装图解【转】
    【转】Android端与Android端利用WIFI进行FTP通信
    【转】Android 服务器之SFTP服务器上传下载功能 -- 不错
    【转】session setup failed: NT_STATUS_LOGON_FAILURE -- 不错
    【转】Mac OS X开机启动Path had bad permissions错误解决方案
  • 原文地址:https://www.cnblogs.com/tangzhengyue/p/2615551.html
Copyright © 2011-2022 走看看