C++学习:第六章Linux高级编程 - (十一)select、TCP编程模型、Socket选项、OOB、HTTP协议

回顾:

多进程的问题:数据共享。

多进程的问题:进程的上下文环境(context)

文件描述符号是整数以及对应上下文环境

多进程的问题:上下文环境共享

 

一、 SELECT TCP服务器编程模式

   1. select函数

int select(

int fds, //建议是监控的文件描述符号的最大值+1

fd_set *readfds, //读文件描述符号集合

//该参数既是输入,也是输出

//输入:被监控的描述符号

//输出:有数据的描述符号

fd_set *writefds, //写描述符

fd_set *errfds, //错误描述符

struct timeval*timeout);//指定阻塞时间限制

//为NULL,永久

返回:

>0:发生改变的文件描述符号个数

=0:时间限制过期

=-1:异常

IO能否发出信号?

异步IO就是通过信号工作。

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

void handle(int s)
{
    char buf[200];
    int r;
    r=read(0,buf,199);
    buf[r]=0;
    printf("::%s",buf);
}

main()
{
    fcntl(0,F_SETFL,O_ASYNC);//O_ASYNC 异步
    fcntl(0,F_SETOWN,getpid());//告诉异步处理后信号发送给谁
    signal(SIGIO,handle);
    while(1);
}
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>

main()
{
    fd_set fds;
    int r;
    char buf[100];

    while(1)
    {
        FD_ZERO(&fds);
        FD_SET(0,&fds);
        r=select(1,&fds,0,0,0);
        printf("有数据输入!\n");
        r=read(0,buf,99);
    }
}

 

   2. 应用使用select

   3. 使用select实现TCP的多客户连接与处理

 

chatServer - 2.IO的异步模式(select模式/poll模式)

#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 <sys/select.h>

main()
{
    int sfd;//服务器描述符号
    int fdall[100];//客户描述符号
    int count;//客户个数
    int r;//返回值(异常处理)
    struct sockaddr_in dr;//IP地址与端口
    fd_set fds;//被select监控的描述符号集合
    int maxfd;//最大文件描述符号
    int i,j;//循环变量
    char buf[1024];//客户聊天数据

    //1.建立socket
    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(8866);
    inet_aton("192.168.180.92",&dr.sin_addr);
    r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("2:%m\n"),close(sfd),exit(-1);
    printf("bind ok!\n");

    //3.监听
    r=listen(sfd,10);
    if(r==-1) printf("3:%m\n"),close(sfd),exit(-1);
    printf("listen ok!\n");

    //初始化
    count=0;
    maxfd=0;
    FD_ZERO(&fds);
    for(i=0;i<100;i++)
    {
         fdall[i]=-1;
    }
    while(1)
    {
        //4.构造监听的描述符号集合

        //4.1.清空
        FD_ZERO(&fds);
        maxfd=0;

        //4.2.加入服务器描述符号
        FD_SET(sfd,&fds);
        maxfd=maxfd>=sfd?maxfd:sfd;

        //4.3.加入客户描述符号
        for(i=0;i<count;i++)
        {
            if(fdall[i]!=-1)
            {
                FD_SET(fdall[i],&fds);
                maxfd=maxfd>=fdall[i]?maxfd:fdall[i];
            }
        }

        //5.使用select循环控制描述符号集合
        r=select(maxfd+1,&fds,0,0,0);
        if(r==-1)
        {
            printf("服务器崩溃!\n");
            break;
        }

        //6.分两种情况处理:

        //6.1.有客户连接:服务器描述符号
        if(FD_ISSET(sfd,&fds))
        {
            fdall[count]=accept(sfd,0,0);
            if(fdall[count]==-1)
            {
                printf("服务器崩溃!\n");

                //释放所有客户
                break;
            }

            printf("有客户连接!\n");
            count++;
        }

        //6.2.有客户发送数据:客户描述符号
        for(i=0;i<count;i++)
        {
            //判定改变描述符号是否存在
            if( fdall[i]!=-1 &&
                FD_ISSET(fdall[i],&fds))
            {
                //读取数据
                r=recv(fdall[i],buf,1023,0);
                if(r==0)
                {
                    printf("有客户退出!\n");
                    close(fdall[i]);
                    fdall[i]=-1;
                }
                if(r==-1)
                {
                    printf("网络故障!\n");
                    close(fdall[i]);
                    fdall[i]=-1;
                }
                if(r>0)
                {
                    //广播数据
                    buf[r]=0;
                    printf("广播数据:%s\n",buf);
                    for(j=0;j<count;j++)
                    {
                        if(fdall[j]!=-1)
                        {
                            send(fdall[j],buf,r,0);
                        }
                    }
                }
            }
        }
    }
}

 

chatClient - 2.IO的异步模式(select模式/poll模式)

#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();
void initUI();
void destroy();
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(8866);
    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;
}

 

   4. poll模式

int poll(

struct pollfd *fds, //监控的描述符号

int nfds, //监控的描述符号的个数

int timeout); //阻塞超时

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/poll.h>

main()
{
    struct pollfd fds[1];
    int r;
    char buf[100];

    fds[0].fd=0;
    fds[0].events=POLLIN;
    while(1)
    {
        r=poll(fds,1,-1);
        if(fds[0].revents & POLLIN)
        {
            printf("有数据输入!\n");
            r=read(0,buf,99);
        }
    }
}


 

二、 Socket选项设置

   1. socket有哪些选项可以设置

ARP

   |

  IP

   |

|---------------------|

UDP               TCP

Socket通用选项:(man 7 socket)

SOL_SOCKET

SO_BROADCAST 广播

SO_RCVBUF 描述符号的缓冲的大小

SO_SNDBUF 描述符号的缓冲的大小

SO_REUSEADDR 地址反复绑定

SO_TYPE 描述符号类型SOCK_STREAM SOCK_DGRAM?

ICMP选项(man 7 icmp)

IPPTOTO_ICMP

ICMP_FILTER

IP选项(干预系统生成IP头)

IPPROTO_IP

......

......

UDP选项

IPPROTO_UDP

......

TCP选项

IPPROTO_TCP

......

setsockopt设置选项

getsockopt获取选项

案例:

判定一个socket的数据类型AF_INET:SOCK_STREAM  SOCK_DGRAM SOCK_RAW

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

main()
{
    int fd;
    int type;
    int len;
    len=sizeof(type);

    fd=socket(AF_INET,SOCK_DGRAM,0);
    getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&type,&len);//SOL_SOCKET就是你想获得的类型宏,直接替换就行,比如IPPROTO_IP
    printf("缓冲大小:%u\n",type);

    /*
        getsockopt(fd,SOL_SOCKET,SO_TYPE,&type,&len);
        printf("%u:%u\n",SOCK_STREAM,type);

        if(type & SOCK_STREAM)
        {
            printf("流!\n");
        }

        if(type & SOCK_DGRAM)
        {
            printf("报文!\n");
        }
    */

}

 

案例:

使用选项进行数据广播.

cast_A发送

建立socket

设置广播选项

发送数据(广播方式发送)

              case_B接收

建立socket

设置地址可重用选项

绑定地址

接收数据

 

cast_A发送

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

main()
{
    int fd;
    int opt=1;
    int r;
    struct sockaddr_in dr;

    //1.选项设置
    fd=socket(PF_INET,SOCK_DGRAM,0);
    if(fd==-1) printf("1:%m\n"),exit(-1);
    r=setsockopt(fd,SOL_SOCKET,SO_BROADCAST,//设置广播
        &opt,sizeof(opt));
    if(r==-1) printf("2:%m\n"),exit(-1);
    dr.sin_family=AF_INET;
    dr.sin_port=htons(9999);

    //2.使用广播IP地址,可以查到
    dr.sin_addr.s_addr=inet_addr("192.168.180.255");
    r=sendto(fd,"Hello",5,0,(struct sockaddr*)&dr,sizeof(dr));
    if(fd==-1) printf("3:%m\n");

    close(fd);
}

 

case_B接收

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

main()
{
    int fd;
    int opt=1;
    char buf[100];
    int r;
    struct sockaddr_in dr;

    fd=socket(PF_INET,SOCK_DGRAM,0);
    if(fd==-1) printf("1:%m\n"),exit(-1);

    //1.选项
    r=setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,//设置重用IP
        &opt,sizeof(opt));
    if(r==-1) printf("2:%m\n"),exit(-1);
    dr.sin_family=AF_INET;
    dr.sin_port=htons(9999);

    //2.广播地址
    dr.sin_addr.s_addr=inet_addr("192.168.180.255");
    r=bind(fd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("3:%m\n"),exit(-1);
    r=recv(fd,buf,100,0);
    if(r>0)
    {
        buf[r]=0;
        printf("广播数据:%s\n",buf);
    }
    close(fd);
}

 

三、 OOB数据(TCP)(带外数据)

优先数据

send(,MSG_OOB);

recv(,MSG_OOB);

案例:

oob_server.c

recv MSG_OOB

oob_client.c

send MSG_OOB

  1. OOB数据只能一个字符
  2. 普通数据使用一般方式接收与发送,OOB数据使用MSG_OOB接收与发送
  3. 一个数据使用MSG_OOB,则最后一个是OOB,其他非OOB数据
  4. 问题:OOB数据是优先数据。优先体现在什么地方?

所有的事情会停下来优先处理带外数据,而且文件的描述符会指在带外数据的位置。

我理解,带外数据的优先级是依托于信号的机制。由于带外数据是和SIGURG信号绑在一起的,所以优先级较高。带外数据过多,原来的带外数据会失效。

 

oob_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>

int fd,cfd;
void handle(int s)
{
    char data[100];
    int r;
    if(s==SIGURG)
    {
        r=recv(cfd,data,100,MSG_OOB);
        data[r]=0;
        printf("$$%s\n",data);
    }
}

main()
{
    int opt=1;
    char buf[100];
    int r;
    struct sockaddr_in dr;
    fd=socket(PF_INET,SOCK_STREAM,0);
    if(fd==-1) printf("1:%m\n"),exit(-1);
    printf("1\n");

    dr.sin_family=AF_INET;
    dr.sin_port=htons(10000);
    dr.sin_addr.s_addr=inet_addr("192.168.180.92");
    r=bind(fd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("2:%m\n"),exit(-1);
    printf("2\n");

    r=listen(fd,10);
    if(r==-1) printf("3:%m\n"),exit(-1);
    printf("3\n");

    signal(SIGURG,handle);//带外数据会带一个信号,所以正常接收不到
    cfd=accept(fd,0,0);
    fcntl(cfd,F_SETOWN,getpid());//只要cfd文件有信号,就会马上获取到,发送给当前进程。默认是发送给跟进程的
    if(cfd==-1) printf("4:%m\n"),exit(-1);
    printf("4\n");

    while(1)
    {
        r=recv(cfd,buf,100,0);
        //r=recv(cfd,buf,100,MSG_OOB);
        if(r>0)
        {
            buf[r]=0;
            printf("接收数据%s\n",buf);
        }
        else
        {
            break;
        }
    }
    close(cfd);
    close(fd);
}

 

oob_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

main()
{
    int fd;
    int opt=1;
    char buf[100];
    int r;
    struct sockaddr_in dr;
    fd_set fds;

    fd=socket(PF_INET,SOCK_STREAM,0);
    if(fd==-1) printf("1:%m\n"),exit(-1);
    printf("1\n");
    dr.sin_family=AF_INET;
    dr.sin_port=htons(10000);
    dr.sin_addr.s_addr=inet_addr("192.168.180.92");


    r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("2:%m\n"),exit(-1);
    while(1)
    {
        FD_ZERO(&fds);
        FD_SET(fd,&fds);
        select(fd+1,0,&fds,0,0);//清空缓冲,保证带外数据接收
        send(fd,"Hello",5,MSG_OOB);
    }

    close(fd);
}

 

四、 HTTP协议以及应用

  1. HTTP协议版本HTTP1.0 HTTP1.1
  2. HTTP是应用协议
  3. HTTP协议分成:

请求协议

响应协议

  1. 请求协议的格式:

请求行(请求方法 请求资源 协议版本)  

请求体(请求头:请求值)

空行

数据(querystring:key=value&key=value)

  1. 响应协议的格式

响应行(协议版本 响应码 响应码的文本描述)

响应体(响应头: 响应值)

空行

数据(普通数据/分块数据)

 

1XX 正在处理

2XX 响应成功200

3XX 继续处理

4XX 客户错误

5XX 服务器错误

 

 

http___Client 打开一个网页,网址就是服务器那块

#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 fd;
    struct sockaddr_in dr;
    char strreq[1024];
    char buf[10*1024];
    int r;

    //建立socket
    fd=socket(AF_INET,SOCK_STREAM,0);

    //连接服务器192.168.0.72
    dr.sin_family=AF_INET;
    dr.sin_port=htons(80);
    dr.sin_addr.s_addr=inet_addr("192.168.0.72");
    r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));

    //构建http请求字符串
    sprintf(strreq,
        "GET /index.php HTTP/1.1\r\n"
        "Host: 192.168.0.72:80\r\n"
        "User-Agent: Tarena5.0\r\n"
        "Accept: text/html,image/png\r\n"
        "Accept-Language: zh-cn\r\n"
        "Accept-Charset: gb2312,utf-8\r\n"
        "Keep-Alive: 300\r\n"
        "Connection: keep-alive\r\n"
        "\r\n");

    //发送http请求字符串
    r=send(fd,strreq,strlen(strreq),0);

    //等待服务器响应
    //while(1)
    //{
        r=recv(fd,buf,1024,0);
        //if(r<=0) break;
        printf("========================\n");
        printf("%s\n",buf);
        printf("========================\n");
    //}

    close(fd);
}

 

http_Server 通过网页访问该服务器,并接收该服务器的值,注意网址

http://192.168.180.92:10000/index.html
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>

int fd,cfd;

main()
{
    char buf[1024];
    int r;
    struct sockaddr_in dr;
    char strres[1024];

    fd=socket(PF_INET,SOCK_STREAM,0);
    if(fd==-1) printf("1:%m\n"),exit(-1);
    printf("1\n");
    dr.sin_family=AF_INET;
    dr.sin_port=htons(10000);
    dr.sin_addr.s_addr=inet_addr("192.168.180.92");


    r=bind(fd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("2:%m\n"),exit(-1);
    printf("2\n");
    
    r=listen(fd,10);
    if(r==-1) printf("3:%m\n"),exit(-1);
    printf("3\n");

    cfd=accept(fd,0,0);
    if(cfd==-1) printf("4:%m\n"),exit(-1);
    printf("4\n");

    sprintf(strres,
        "HTTP/1.1 200 OK\r\n"
        "Server: tarena2.0\r\n"
        "Content-Type: text/html\r\n"
        "Content-Length: 28\r\n"
        "Connection: keep-alive\r\n"
        "\r\n"
        "<font color=red>Hello!</font>");
    while(1)
    {
        r=recv(cfd,buf,1024,0);
        if(r>0)
        {
            buf[r]=0;
            printf("接收数据 %s\n",buf);
            send(cfd,strres,strlen(strres),0);
        }
        else
        {
            break;
        }
    }
    close(cfd);
    close(fd);
}

 

五、 ioctl函数

实现ifconfig工具

 

总结:

重点:

select

广播

 

了解:

OOB数据

HTTP协议

 

应用:

独立编写TCP服务器端的select模式

编写广播

能够请求一个网页,并且解析响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值