一、信号量(同步)
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服务器的编程
- TCP的服务器编程模型
- IP协议与处理(SOCK_RAW,SOCK_PACKET)
- pcap编程
- HTTP协议与网页搜索