C++学习:第六章Linux高级编程 - (九)信号量同步、socket网络编程基础、TCP、UDP

一、信号量(同步)

 

  1. 回顾:

一个进程控制另外一个进程.

逻辑变量+pause/sleep+信号

  2. 信号量(semaphore)信号灯

三个数据:红灯/绿灯/黄灯

60   90   10

信号量是共享内存整数数组,根据需要定义指定的数组长度

信号量就是根据数组中的值,决定阻塞还是解除阻塞

  3. 编程

3.1 创建或者得到信号量 semget

3.2 初始化信号量中指定下标的值 semctl

3.3 根据信号量阻塞或者解除阻塞 semop

3.4 删除信号量

 

案例:

A:                             B:

创建信号量                 得到信号量

初始化信号量

根据信号量阻塞           解除阻塞

             删除信号量

 

semget函数说明

int semget(key_t key,
    int nums, //信号量数组个数
    int flags); //信号量的创建标记

    //创建IPC_CREAT|IPC_EXCL|0666
    //打开0
    返回: -1:失败
        >=0:成功返回信号量的ID
int semop(
    int semid, //信号量ID
    struct sembuf *op, //对信号量的操作,操作可以是数组多个
    size_t nums, //第二个参数的个数
    );
    返回:
        -1:时失败
         0:成功
int semctl(
    int semid, //对IPC_RMID无意义
    int nums,  //信号量数组下标,第一个变量为0,以此类推          
    int cmd,   //SETVAL  IPC_RMID
    ...);      //对IPC_RMID无意义
struct  sembuf
{
    int sem_num;//下标
    int sem_op;
    int sem_flg;//建议为0.
}

sem_op:

前提条件信号量是unsigned short int;

不能<0.

-:够减,则semop马上返回,不够减,则阻塞.

+:执行+操作

0:判定信号量>0,则阻塞,直到为0

 

控制进程的搭配方式:

+(解除阻塞) -(阻塞)

0(阻塞) -(解除阻塞)

 

/*arg for semctl systemcalls.*/

union semun{

int val;/*value for SETVAL*/

struct semid_ds *buf;/*buffer for IPC_STAT&IPC_SET*/

ushort *array;/*array for GETALL&SETALL*/

struct seminfo *__buf;/*buffer for IPC_INFO*/

 

https://blog.csdn.net/xiajun07061225/article/details/8475738

 

文件A

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//2.1.定义一个联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short  *array;
    struct seminfo  *__buf;
};


main()
{
    key_t key;
    int semid; //信号量ID
    union  semun v;//2.2.定义初始化值
    int r;
    struct sembuf op[1];

    //1.创建信号量
    key=ftok(".",99);
    if(key==-1) printf("ftok err:%m\n"),exit(-1);


    //semid=semget(key,1/*信号量数组个数*/,
    // IPC_CREAT|IPC_EXCL|0666);
    semid=semget(key,1,0);//得到信号量
    if(semid==-1) printf("get err:%m\n"),exit(-1);
    printf("id:%d\n",semid);

    //2.初始化信号量
    v.val=2;
    r=semctl(semid,0,SETVAL,v);//2.3设置信号量的值
    if(r==-1) printf("初始化失败!\n"),exit(-1);

    //3.对信号量进行阻塞操作
    //3.1.定义操作
    op[0].sem_num=0;//信号量下标
    op[0].sem_op=-1;//信号量操作单位与类型
    op[0].sem_flg=0;//操作标记
    while(1)
    {
        r=semop(semid,op,1);
        printf("解除阻塞!\n");
    }

    //4.删除(可以不删除)
}

 

文件B

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//2.1.定义一个联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short  *array;
    struct seminfo  *__buf;
};


main()
{
    key_t key;
    int semid; //信号量ID
    union  semun v;//2.2.定义初始化值
    int r;
    struct sembuf op[2];

    //1.创建信号量
    key=ftok(".",99);
    if(key==-1) printf("ftok err:%m\n"),exit(-1);

    semid=semget(key,1,0);//得到信号量
    if(semid==-1) printf("get err:%m\n"),exit(-1);
    printf("id:%d\n",semid);

    //3.对信号量进行阻塞操作
    //3.1.定义操作
    op[0].sem_num=0;//信号量下标
    op[0].sem_op=1;//信号量操作单位与类型
    op[0].sem_flg=0;
    op[1].sem_num=0;//信号量下标
    op[1].sem_op=1;//信号量操作单位与类型
    op[1].sem_flg=0;
    while(1)
    {
        r=semop(semid,op,2);
        sleep(1);
    }

    //4.删除(可以不删除)
    //semctl(semid,0,IPC_RMID);

}

 

自己找的信号量代码

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
#include<unistd.h>
#include<sys/wait.h>

#define MAX_SEMAPHORE 10
#define FILE_NAME "test2.c"

union semun{
    int val ;
    struct semid_ds *buf ;
    unsigned short *array ;
    struct seminfo *_buf ;
}arg;


struct semid_ds sembuf;

int main()
{
    key_t key ;
    int semid ,ret,i;
    unsigned short buf[MAX_SEMAPHORE] ;
    struct sembuf sb[MAX_SEMAPHORE] ;
    pid_t pid ;
    pid = fork() ;

    if(pid < 0)
    {
        /* Create process Error! */
        fprintf(stderr,"Create Process Error!:%s\n",strerror(errno));
        exit(1) ;
    }


   if(pid > 0)
   {
        /* in parent process !*/
        key = ftok(FILE_NAME,'a') ;
        if(key == -1)
        {
             /* in parent process*/
             fprintf(stderr,"Error in ftok:%s!\n",strerror(errno));
             exit(1) ;
        }

        semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); //创建信号量集合
        if(semid == -1)
        {
            fprintf(stderr,"Error in semget:%s\n",strerror(errno));
            exit(1) ;
        }

        printf("Semaphore have been initialed successfully in parent process,ID is :%d\n",semid);
        sleep(2) ;
        printf("parent wake up....\n");

        /* 父进程在子进程得到semaphore的时候请求semaphore,此时父进程将阻塞直至子进程释放掉semaphore*/
        /* 此时父进程的阻塞是因为semaphore 1 不能申请,因而导致的进程阻塞*/

        for(i=0;i<MAX_SEMAPHORE;++i)
        {
            sb[i].sem_num = i ;
            sb[i].sem_op = -1 ; /*表示申请semaphore*/
            sb[i].sem_flg = 0 ;
        }

        printf("parent is asking for resource...\n");
        ret = semop(semid , sb ,10); //p()

        if(ret == 0)
        {
            printf("parent got the resource!\n");
        }

        /* 父进程等待子进程退出 */
        waitpid(pid,NULL,0);
        printf("parent exiting .. \n");
        exit(0) ;
    }
    else
    {
        /* in child process! */

        key = ftok(FILE_NAME,'a') ;
        if(key == -1)
        {
             /* in child process*/
             fprintf(stderr,"Error in ftok:%s!\n",strerror(errno));
             exit(1) ;
        }

        semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666);
        if(semid == -1)
        {
              fprintf(stderr,"Error in semget:%s\n",strerror(errno));
              exit(1) ;
        }

        printf("Semaphore have been initialed successfully in child process,ID is:%d\n",semid);

        for(i=0;i<MAX_SEMAPHORE;++i)
        {
             /* Initial semaphore */
             buf[i] = i + 1;
        }
   

        arg.array = buf;
        ret = semctl(semid , 0, SETALL,arg);
        if(ret == -1)
        {
             fprintf(stderr,"Error in semctl in child:%s!\n",strerror(errno));
             exit(1) ;
        }
        printf("In child , Semaphore Initailed!\n");

        /* 子进程在初始化了semaphore之后,就申请获得semaphore*/
        for(i=0;i<MAX_SEMAPHORE;++i)
        {
            sb[i].sem_num = i ;//注意这里的下标运用
            sb[i].sem_op = -1 ;
            sb[i].sem_flg = 0 ;
        }

        ret = semop(semid , sb , 10);//信号量0被阻塞
        if( ret == -1 )
        {
            fprintf(stderr,"子进程申请semaphore失败:%s\n",strerror(errno));
            exit(1) ;
        }

        printf("child got semaphore,and start to sleep 3 seconds!\n");
        sleep(3) ;
        printf("child wake up .\n");
        for(i=0;i < MAX_SEMAPHORE;++i)
        {
            sb[i].sem_num = i ;
            sb[i].sem_op = +1 ;
            sb[i].sem_flg = 0 ;
        }

        printf("child start to release the resource...\n");
        ret = semop(semid, sb ,10) ;
        if(ret == -1)
        {
            fprintf(stderr,"子进程释放semaphore失败:%s\n",strerror(errno));
            exit(1) ;
        }
   

        ret = semctl(semid ,0 ,IPC_RMID);
        if(ret == -1)
        {
            fprintf(stderr,"semaphore删除失败:%s!\n",strerror(errno));
            exit(1) ;
        }

        printf("child exiting successfully!\n");
        exit(0) ;
    }
    return 0;
}

 

二、网络

1. 基础(ip)

1.1 网络工具

ping

ping ip //地址

ping -b ip //广播地址

ifconfig -a //查看所有网络口信息

 

netstat -a //显示所有正在使用的网络状态

netstat -u //支持udp协议

netstat -t //支持tcp协议

netstat -x //支持本地unix协议

netstat -n //数字方式显示

 

route

lsof

1.2 网络的基本概念

网络编程采用socket模型.

网络通信本质也是进程之间的IPC,是不同主机之间。

 

识别主机:4字节整数:IP地址

识别进程:2字节整数:端口号

 

IP地址的表示方法: 内部表示:4字节整数

外部表示:数点字符串

结构体

1  2  3  4 分段表示,每个段使用.分割

"192.168.0.26"

 

ip地址的转换:

sockaddr是在头文件 /usr/include/bits/socket.h 中定义的

struct sockaddr
{
    sa_family_t sin_family; //地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};

 

sockaddr_in是在头文件 /usr/include/netinet/in.h 中定义的

struct  sockaddr_in
{
    int sin_family; //地址族
    in_port_t sin_port; //16位TCP/UDP端口号
    struct in_addr sin_addr; //32位ip地址
    char sin_zero[8]; //留用
}
struct in_addr
{
    in_addr_t s_addr; //32位IPv4地址
}

 

//总结:

IP地址的表示

字符串表示"192.168.0.26"

整数表示:in_addr_t;

字结构表示struct in_addr;

连接点:endpoint

1.3 IP地址的转换

inet_addr //把字符串转换为整数(网络字节序)

inet_aton //把字符串转换为struct in_addr;(网络字结序)

inet_network //把字符串转换为整数(本地字节序)

inet_ntoa //把结构体转换为字符串

 

htons //Host to Network Short

htonl //Host to Network Long

 

ntohs //Network to Host Short

ntohl //Host to Network Long

 

补充:关于字节序

网络字节顺序NBO(Network Byte Order)

按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO,Host Byte Order):

不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。

如 Intelx86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12如IBM power PC结构下,short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同powerpc那样的顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。   

 

主机的字节序不同,注意下列代码

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

main()
{
    /*
    in_addr_t  nip=192<<24 | 168 <<16 | 0<<8  | 26;
    char  *ip="192.168.0.26";

    //把整数转换为字符串inet_ntoa
    struct in_addr sip;
    int myip;
    sip.s_addr=nip;
    printf("nip:%u\n",nip);
    printf("%s\n",inet_ntoa(sip));

    myip=inet_addr(ip);
    printf("%u\n",myip);
    printf("%hhu.%hhu.%hhu.%hhu\n", myip>>24 & 255,
    myip>>16 & 255,
    myip>>8  & 255,
    myip>>0  & 255);
    */

    /*
    char ip[4]={192,168,0,26};
    printf("%d\n",*(int*)ip);
    */

    char *ip="10.45.8.1";
    struct in_addr addr;
    in_addr_t net;
    in_addr_t host;
    struct in_addr tmp;

    inet_aton(ip,&addr);
    net=inet_lnaof(addr);
    host=inet_netof(addr);

    tmp.s_addr=net;//网络表示
    printf("%s\n",inet_ntoa(tmp));

    tmp.s_addr=host;//主机表示
    printf("%s\n",inet_ntoa(tmp));

}

 

1.4 IP地址的意义

IP地址的位表达不同意义:

IP地址组建网络:网络标识/主机标识

              网络                   主机

A类              前7位表示          后24位表示           网络少主机多

B类              14                        16

C类              2                           1 8

D类              组播

E类              没有使用

1.5 计算机系统中的网络配置

   /etc/hosts 文件 配置IP,域名,主机名

gethostbyname

gethostbyaddr

/etc/protocols 文件 配置系统支持的协议

/etc/services 文件 配置服务

get***by***;

gethostbyname

getprotobyname

 

#include <stdio.h>
#include <netdb.h>

main()
{
    struct hostent *ent;
    /*打开主机配置数据库文件*/
    sethostent(1);//1一直打开,0单次打开

    while(1)
    {
        ent=gethostent();
        if(ent==0) break;

        printf("主机名:%s\t",ent->h_name);
        printf("IP地址:%hhu.%hhu.%hhu.%hhu\t",
        ent->h_addr[0],
        ent->h_addr[1],
        ent->h_addr[2],
        ent->h_addr[3]);
        printf("别名:%s\n",ent->h_aliases[0]);
    }

    endhostent();
}
#include <stdio.h>
#include <netdb.h>

main()
{

    struct hostent *ent;
    ent=gethostbyname("bbs.tarena.com.cn");
    
    //printf("%s\n",ent->h_aliases[0]);
    printf("%hhu.%hhu.%hhu.%hhu\n",
    ent->h_addr_list[0][0],
    ent->h_addr_list[0][1],
    ent->h_addr_list[0][2],
    ent->h_addr_list[0][3]);
}

 

编译时 gcc protocol.c -omain -D_GNU_SOURCE 最后一个是宏,编译时需要加上

#include <stdio.h>
#include <netdb.h>
#include <sys/utsname.h>

main()
{
    struct protoent *ent;
    struct utsname name;
    ent=getprotobyname("tcp");
    printf("%d\n",ent->p_proto);

    uname(&name);
    printf("%s\n",name.machine);
    printf("%s\n",name.nodename);
    printf("%s\n",name.sysname);
    printf("%s\n",name.domainname);
}

 

三、UDP编程模型

对等模型            AF_INET           SOCK_DGRAM           0:UDP

C/S 模型             AF_INET           SOCK_STREAM         0:TCP

 

2.1 网络编程

ISO的7层模型:

      物理层

数据链路层        数据链路层         (数据物理怎么传输)

网络层               IP层                     (数据的传输方式)

传输层                传输层                 (数据传输的结果)

会话层                应用层                 (数据传递的含义)

表示层

应用层

 

2.2 UDP编程的数据特点

UDP采用对等模型SOCK_DGRAM

socket                           socket:socket

绑定IP地址bind            连接目标(可选) conncect

read/recv/recvfrom       发送数据 write/send/sendto

关闭close

案例:

A                                                   B

接收用户的数据                             发送数据

打印数据与发送者IP                      接收数据并打印

返发一个信息

 

udpA:

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

//服务器
main()
{
    int fd;//socket描述符号
    struct sockaddr_in ad;//本机的IP地址
    char buf[100];//接收数据缓冲
    struct sockaddr_in ad_snd;//发送者IP地址
    socklen_t len;//发送者IP的长度
    int r;

    fd=socket(AF_INET,SOCK_DGRAM,17);//udp协议17 可以自己查看 protocols 中
    if(fd==-1) printf("socket:%m\n"),exit(-1);
    printf("建立socket成功!\n");

    ad.sin_family=AF_INET;
    ad.sin_port=htons(11111);
    inet_aton("192.168.180.92",&ad.sin_addr);
    r=bind(fd,(struct sockaddr*)&ad,sizeof(ad));
    if(r==-1) printf("bind err:%m\n"),exit(-1);
    printf("绑定成功!\n");

    while(1)
    {
        len=sizeof(ad_snd);
        r=recvfrom(fd,buf,sizeof(buf)-1,0,
            (struct sockaddr*)&ad_snd,&len);
        
        if(r>0)
        {
            buf[r]=0;
            printf("发送者IP:%s,端口:%hu,数据:%s\n",
            inet_ntoa(ad_snd.sin_addr),
                ntohs(ad_snd.sin_port),buf);
            sendto(fd,"古怪!",strlen("古怪!"),0,
                (struct sockaddr*)&ad_snd,sizeof(ad_snd));
        }

        if(r==0)
        {
            printf("关闭!\n");
            break;
        }

        if(r==-1)
        {
            printf("网络故障!\n");
            break;
        }
    }
    close(fd);
}

 

udpB

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

//客户端
main()
{
    int fd;
    struct sockaddr_in ad;
    char buf[101];
    int r;


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


    ad.sin_family=AF_INET;
    ad.sin_port=htons(11111);
    ad.sin_addr.s_addr=inet_addr("192.168.180.92");
    //connect(fd,(struct sockaddr*)&ad,sizeof(ad));

    while(1)
    {
        r=read(0,buf,sizeof(buf)-1);//从屏幕读取一行,0:数据流输入,1:数据流输出,2:数据流错误
        if(r<=0) break;

        buf[r]=0;
        r=sendto(fd,buf,r,0,
            (struct sockaddr*)&ad,sizeof(ad));

        bzero(buf,sizeof(buf));
        r=recv(fd,buf,sizeof(buf),0);//这个0 后面会专门讲

        buf[r]=0;
        printf("来自接收方的数据:%s\n",buf);

        //r=send(fd,buf,r,0);
        if(r==-1) break;
    }

    close(fd);
}

 

总结:

  1. 问题:

      connect + send + close ==  sendto

      sendto 是连接+发送+关闭,每次都是这个循环

      connect是一直连接

  2. 问题:

      recvfrom的作用不是专门从指定IP接收

      而是从任意IP接收数据,返回发送数据者的IP

  3. 问题:

      为什么要bind,bind主要目的告诉网络发送数据的目标.

      是否一定绑定才能发送数据?

      否:只要知道你的IP与PORT,就能发送数据.

  4. 问题:

      为什么发送者没有绑定IP与端口,他也有端口?

      底层网络驱动,帮我们自动生成IP与端口.

  5. 缺陷:

      接收方不区分发送者的,这是UDP的一大缺陷的,在TCP中改善了这个问题

 

send函数

sendto函数

int sendto(
    int fd, //socket描述符号
    const void *buf, //发送的数据缓冲
    size_t size, //发送的数据长度
    int flags, //发送方式MSG_NOWAIT MSG_OOB
    const struct sockaddr *addr, //发送的目标的IP与端口
    socklen_t len //sockaddr_in的长度
);

返回:
     -1:发送失败
    >=0:发送的数据长度

recv函数

recvfrom函数

int recvfrom(
    int fd,
    void *buf,
    size_t size,
    int flags,
    struct sockaddr*addr, //返回发送者IP与端口
    socklen_t *len); //输入返回IP的缓冲大小,返回实际IP的大小

 

2.3 TCP编程的数据特点

2.4 TCP服务器的编程

  1. TCP的服务器编程模型
  2. IP协议与处理(SOCK_RAW,SOCK_PACKET)
  3. pcap编程
  4. HTTP协议与网页搜索
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值