c++ 学习笔记(高级linux编程) day10

本文详细介绍了TCP编程的基础知识,包括TCP的编程模型、通信特点及服务器编程模式。通过具体案例讲解了如何使用TCP进行文件传输及实现多进程聊天程序。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值