linux高级编程day10 笔记

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

// 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(必须等待得到指定缓存长度recv才返回)
  
 案例5:
  TCP数据发送的分析:
    定长数据:
      基本数据int short long float  double
      结构体数据struct
      建议使用MSG_WAITALL
    
    不定长数据:
      字符串数据以及文件数据等不固定长度的数据怎么发送?   
      制定数据包:
        头:大小固定(数据大小)
        体:大小变化(数据)  
 案例6:
   使用TCP传送文件
   定义文件数据包.
     int 数据大小;
     char[]数据
       
   传递文件名
   传递数据(循环)
   传递0长度的数据表示文件结束
代码如下:


// 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");
}

// 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.在子进程中,接收服务器传递其他客户聊天信息


// 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
}

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

你可能感兴趣的:(linux高级编程day10 笔记 )