文章目录
- 1.1 信号灯集函数接口
- 1.semget
- 2.semctl
- 3.封装初始化函数
- 4. semop
- 二 网络编程
- 2.1 为什么要学习网络编程
- 2.2 发展
- 2.2.1 ARPnet
- 2.2.2 TCP/IP协议
- 2.3 网络体系结构以及OSI开放系统互联模型
- 2.4 TCP/IP协议族
- 2.5 五层模型
- 2.6 TCP和UDP的异同点
- 2.7 函数讲解
- 2.7.1 socket
- 2.7.2 bind()
- 2.7.3 listen
- 2.7.4 accept
- 2.7.5 connect
- 2.8 TCP服务器
- 2.9 TCP客户端
- 2.10 TCP并发服务器
- 2.11 UDP 网络编程
- 函数接口recvfrom/sendto
一 IPC通信之 信号灯集
信号灯集:是在内核空间的信号灯的集合
1.1 信号灯集函数接口
1.semget
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:int semget(key_t key, int nsems, int semflg);
功能:创建或者打开一个信号灯集
参数:
key:信号灯集的秘钥(和共享内存,消息队列类似)
nsems:创建的信号灯集中存在几个信号灯
semflg:打开的方式
IPC_CREAT:如果共享内存存在,则打开,不存在则创建
例如:IPC_CREAT | 0664
IPC_EXCL:如果存在则报错返回,如果不存在配合IPC_CREAT创建
返回值:
成功返回一个信号灯集的ID
失败返回-1
2.semctl
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:int semctl(int semid, int semnum, int cmd, ...);
功能:控制信号灯集
参数:
semid:要控制的信号灯集的ID号
semnum:信号灯的编号
cmd:控制方式
IPC_RMID:删除信号灯集,不考虑第二个参数
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
..:可变参数:是一个联合体
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
返回值:
成功返回 0
GETVAL:返回一个信号灯的值
失败返回-1
3.封装初始化函数
int sem_init_val(int semid,int semnum,int val)
{
union semun myval; //需要自己定义
myval.val = val;
if(-1 ==semctl(semid,semnum,SETVAL,myval))
{
printf("初始化信号灯%d失败\n",semnum);
return -1;
}
return 0;
}
4. semop
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:int semop(int semid, struct sembuf *sops, size_t nsops);
功能:操作方式
参数:
semid:信号灯集的ID号
sops:操作方法结构体的地址
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
使用案例:
struct sembuf sops[2];
int semid;
/* Code to set semid omitted */
sops[0].sem_num = 0; /* Operate on semaphore 0 */
sops[0].sem_op = 0; /* Wait for value to equal 0 */
sops[0].sem_flg = 0;
sops[1].sem_num = 0; /* Operate on semaphore 0 */
sops[1].sem_op = 1; /* Increment value by one */
sops[1].sem_flg = 0;
if (semop(semid, sops, 2) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
nsops:同时操作的个数
如果说同时操作很多信号灯,填写一个结构体数组
返回值:
成功返回0
失败返回-1
//write
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
union semun {
int val; /* Value for SETVAL */
};
int sem_init_val(int semid,int semnum,int val)
{
union semun myval; //需要自己定义
myval.val = val;
if(-1 ==semctl(semid,semnum,SETVAL,myval))
{
printf("初始化信号灯%d失败\n",semnum);
return -1;
}
return 0;
}
int sem_p(int semid,int semnum) //参数1:信号灯集 2.信号灯的编号
{
struct sembuf mybuf;
mybuf.sem_num = semnum; /* Operate on semaphore 0 */
mybuf.sem_op = -1; /* Wait for value to equal 0 */
mybuf.sem_flg = 0;
if(-1 == semop(semid,&mybuf,1))
{
printf("p操作失败\n");
return -1;
}
return 0;
}
int sem_v(int semid,int semnum) //参数1:信号灯集 2.信号灯的编号
{
struct sembuf mybuf;
mybuf.sem_num = semnum; /* Operate on semaphore 0 */
mybuf.sem_op = 1; /* Wait for value to equal 0 */
mybuf.sem_flg = 0;
if(-1 == semop(semid,&mybuf,1))
{
printf("v操作失败\n");
return -1;
}
return 0;
}
int main(int argc, char const *argv[])
{
//生成一个共享内存使用的key值
key_t mykey1 = ftok("/home/jsetc/jsetc/208/",'a');
if(-1 == mykey1)
{
perror("生成键值失败");
return -1;
}
//生成一个自定义key值
key_t mykey = ftok("/home/jsetc/jsetc/208/day17/",'a');
if(-1 == mykey)
{
perror("ftok");
return -1;
}
//创建信号灯集
int semid = semget(mykey,2,IPC_CREAT | 0664);
if(-1 == semid)
{
perror("semget");
return -1;
}
//初始化信号灯集
sem_init_val(semid,0,1);
sem_init_val(semid,1,0);
//创建共享内存
int shmid = shmget(mykey1,4096,IPC_CREAT | 0664);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("创建或者打开共享内存成功\n");
//地址映射
char *buf = (char *)shmat(shmid,NULL,0);
if((char *)-1 == buf)
{
perror("shmat");
return -1;
}
while(1)
{
sem_p(semid,0); //p操作
printf("请输入\n");
scanf("%s",buf);
sem_v(semid,1);
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
union semun {
int val; /* Value for SETVAL */
};
int sem_init_val(int semid,int semnum,int val)
{
union semun myval; //需要自己定义
myval.val = val;
if(-1 ==semctl(semid,semnum,SETVAL,myval))
{
printf("初始化信号灯%d失败\n",semnum);
return -1;
}
return 0;
}
int sem_p(int semid,int semnum) //参数1:信号灯集 2.信号灯的编号
{
struct sembuf mybuf;
mybuf.sem_num = semnum; /* Operate on semaphore 0 */
mybuf.sem_op = -1; /* Wait for value to equal 0 */
mybuf.sem_flg = 0;
if(-1 == semop(semid,&mybuf,1))
{
printf("p操作失败\n");
return -1;
}
return 0;
}
int sem_v(int semid,int semnum) //参数1:信号灯集 2.信号灯的编号
{
struct sembuf mybuf;
mybuf.sem_num = semnum; /* Operate on semaphore 0 */
mybuf.sem_op = 1; /* Wait for value to equal 0 */
mybuf.sem_flg = 0;
if(-1 == semop(semid,&mybuf,1))
{
printf("v操作失败\n");
return -1;
}
return 0;
}
int main(int argc, char const *argv[])
{
//生成一个共享内存使用的key值
key_t mykey1 = ftok("/home/jsetc/jsetc/208/",'a');
if(-1 == mykey1)
{
perror("生成键值失败");
return -1;
}
//生成一个自定义key值
key_t mykey = ftok("/home/jsetc/jsetc/208/day17/",'a');
if(-1 == mykey)
{
perror("ftok");
return -1;
}
//创建信号灯集
int semid = semget(mykey,2,IPC_CREAT | 0664);
if(-1 == semid)
{
perror("semget");
return -1;
}
//初始化信号灯集
sem_init_val(semid,0,1);
sem_init_val(semid,1,0);
//创建共享内存
int shmid = shmget(mykey1,4096,IPC_CREAT | 0664);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("创建或者打开共享内存成功\n");
//地址映射
char *buf = (char *)shmat(shmid,NULL,0);
if((char *)-1 == buf)
{
perror("shmat");
return -1;
}
while(1)
{
sem_p(semid,1); //p操作
printf("buf = %s\n",buf);
sem_v(semid,0);
}
return 0;
}
二 网络编程
2.1 为什么要学习网络编程
网络编程就是最后一种进程间通信的方式-----》套接字通信
套接字通信:前六种进程间通信只能实现同一台主机的多个进程通信,但是套接字通信可以实现不同主机的多个进程间通信。
2.2 发展
Arpanet
TCP/IP协议:一共两个协议
网络体系结构:发明了osi开放系统互联模型
TCP/IP协议族:有很多个协议组成
2.2.1 ARPnet
是网络的最早雏形
不能互联不同类型的计算机和不同类型的操作系统,没有纠错功能。
2.2.2 TCP/IP协议
TCP/IP协议分成了两个不同的协议:
用来检测网络传输中差错的传输协议:TCP
专门分则对不同网络进行互联的互联网协议:IP
2.3 网络体系结构以及OSI开放系统互联模型
OSI(国际标准化组织)提供的一个网络体系结构
OSI因为层次结构比较复杂,所以到目前也没有使用,但是他是最早提出的网络体系结构
OSI七层模型:
应用层: 面向用户的,应用程序
表示层:对数据进行加密和解密
会话层:建立逻辑名字和物理名字之间的关系
运输层:用于控制数据的有效传输
网络层:数据分组,路由选择
数据链路层:将数据组成发送或者接收的帧数据
物理层:选择物理介质
注意:每个层次之间的顺序是不能改变的
2.4 TCP/IP协议族
后期基于OSI发明了TCP/IP协议族,这个协议族由很多协议组成:
TCP/IP协议族一共四层:
应用层:telnet,www,FTP,TFTP,SIP
传输层:tcp udp
网络层:IP,ICMP
网络接口与物理层:网卡驱动,物理接口
2.5 五层模型
应用层:
传输层:
网络层:
数据链路层:
物理层:
2.6 TCP和UDP的异同点
相同点:
同为传输协议
不同点:
TCP是面向连接的,可靠的,基于字节流的传输协议
UDP是面向无连接,不可靠的,基于数据报的传输协议
Tcp的概念
是一种面向链接的传输协议,它能提供高可靠性的通信(即数据无误,数据不丢失,数据无失序,数据无重复的到达),TCP有回传机制
使用情况:
适合对于传输质量要求较高,以及传输大量数据的通信,比图MSN/QQ登录,账号管理时需要使用TCP协议
UDP的概念
是不可靠的,无连接的传输协议,在数据发送前,因为不需要进行链接,所以可以高效率的数据传输
使用情况:
适用于对于实时性要求较高的场景,比如:流媒体
2.7 函数讲解
2.7.1 socket
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:int socket(int domain, int type, int protocol);
功能:创建套接字,返回一个文件描述符
参数:
domain:通信域
AF_UNIX, AF_LOCAL Local communication unix(7) //本地通信
AF_INET IPv4 Internet protocols ip(7) //ipv4网络协议
AF_INET6 IPv6 Internet protocols ipv6(7) //ipv6网络协议
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7) //底层协议通信
AF_ALG Interface to kernel crypto API
type:套接字类型
SOCK_STREAM :流式套接字 --->tcp
SOCK_DGRAM : 数据报套接字 --->udp
SOCK_RAW : 原始套接字
protocol:附加协议,传0表示不需要其他协议
返回值:
成功:文件描述符
失败: -1
2.7.2 bind()
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
通用结构体:一般不用
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
网络信息结构体:
stuct sockaddr_in{
sa_family_t sin_family; //地址族:AF_INET
in_port_t sin_port; //网络字节序的端口号
struct in_addr sin_addr //ip地址
---》struct in_addr
{
uint32_t s_addr; //网络字节序的无符号4字节整数Ip地址
}
}
addrlen:addr的大小
返回值:
成功:返回0
失败:返回-1
2.7.3 listen
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:int listen(int sockfd, int backlog);
功能:将套接字设置为被动监听状态
参数:
sockfd:文件描述符,socket的返回值
backlog:允许同时连接的客户端的个数,一般设置为5,10
返回值:
成功:0
失败:-1
2.7.4 accept
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待客户端的连接
参数:
sockfd:文件描述符,socket的返回值
addr:被填充的网络信息结构体,如果由客户端连接服务器,服务器可以通过这个参数获取客户端的信息
addrlen:addr的大小
返回值:
成功:返回文件描述符
失败:-1
2.7.5 connect
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
原型:int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:给服务器发送连接请求
参数:
sockfd:文件描述符,socket的返回值
addr:要连接的服务器的网络信息结构体,需要客户端自己填充
addrlen:addr的大小
返回值:
成功:返回0
失败:-1
2.8 TCP服务器
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0); //IPV4协议,流式套接字,具体的协议类型
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in server_addr; //保存服务器的信息
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
//绑定信息
int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(-1 == ret)
{
perror("bind");
return -1;
}
//设置监听队列
ret = listen(sockfd,10);
if(-1 == ret)
{
perror("listen");
return -1;
}
printf("等待客户端进行连接...、\n");
struct sockaddr_in Client_addr; //用于保存客户端的信息
int length = sizeof(Client_addr);
int fd = accept(sockfd,(struct sockaddr *)&Client_addr,&length);
if(-1 == fd)
{
perror("accept");
return -1;
}
printf("接收客户端的连接 %d\n",fd);
char buf[32] = {0};
while(1)
{
ret = recv(fd,buf,sizeof(buf),0);
if(-1 == ret)
{
perror("recv");
return -1;
}
if(strcmp(buf,"bye") == 0)
{
break;
}
printf("%s\n",buf);
memset(buf,0 ,sizeof(buf));
}
close(fd);
close(sockfd);
return 0;
}
2.9 TCP客户端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
//向服务器发起连接
struct sockaddr_in server_addr; //保存服务器的信息
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
int ret = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(-1 == ret)
{
perror("connect");
return -1;
}
char buf[32] = {0};
while(1)
{
scanf("%s",buf);
ret = send(sockfd,buf,strlen(buf),0);
if(-1 == ret)
{
perror("send");
return -1;
}
if(strcmp(buf,"bye") == 0)
{
break;
}
memset(buf,0,sizeof(buf));
}
close(sockfd);
return 0;
}
2.10 TCP并发服务器
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <pthread.h>
void * ClientHandler(void *arg)
{
int ret;
int fd = *(int *)arg;
char buf[32] = {0};
pthread_detach(pthread_self()); //线程结束,自动释放资源
while(1)
{
ret = recv(fd,buf,sizeof(buf),0);
if(-1 == ret)
{
perror("recv");
return (void *)-1;
}
else if(0 == ret)
{
break; //客户端异常退出
}
if(strcmp(buf,"bye") == 0)
{
break;
}
printf("接收%d客户端 %s\n",fd,buf);
memset(buf,0 ,sizeof(buf));
}
printf("%d 客户端退出!\n",fd);
close(fd);
}
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0); //IPV4协议,流式套接字,具体的协议类型
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in server_addr; //保存服务器的信息
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
//绑定信息
int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(-1 == ret)
{
perror("bind");
return -1;
}
//设置监听队列
ret = listen(sockfd,10);
if(-1 == ret)
{
perror("listen");
return -1;
}
printf("等待客户端进行连接...、\n");
struct sockaddr_in Client_addr; //用于保存客户端的信息
int length = sizeof(Client_addr);
while(1)
{
int fd = accept(sockfd,(struct sockaddr *)&Client_addr,&length);
if(-1 == fd)
{
perror("accept");
return -1;
}
printf("接收客户端的连接 %d\n",fd);
//为每一个客户端创建新的线程
pthread_t tid;
ret = pthread_create(&tid,NULL,ClientHandler,&fd);
if(ret != 0)
{
perror("pthread_create");
return -1;
}
}
close(sockfd);
return 0;
}
#!/bin/bash
for ((i=0;i<100;i++))
do
./4-Tcp客户端 &
sleep 0.1
done
练习:实现客户端下载服务器所在目录文件
客户端发送要下载的文件名给服务器
服务器判断文件是否存在,将结果告知客户端
如果文件存在,服务器读取文件内容,并发送给客户端
客户端收到文件内容并写入指定的文件
2.11 UDP 网络编程
服务器:
创建套接字 socket
填充服务器网络信息结构体
将套接字和网络信息结构体绑定bind
进行通信 recvfrom /sendto()
客户端:
创建套接字 socket
填充网络信息结构体
进行通信 recvfrom/sendto
函数接口recvfrom/sendto
------recvfrom
1.recvfrom/sendto
------recvfrom
头文件:#include <sys/types.h>
#include <sys/socket.h>
原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
前四个和recv一样
src_addr:源的地址,接收谁的数据,他的信息会自动填充到这个参数
addrlen:src_addr的大小
返回值
成功:实际接收的字节数
失败: 返回-1
------sendto
头文件:#include <sys/types.h>
#include <sys/socket.h>
原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:接收数据
参数:
前四个和send一样
dest_addr:目的地址,需要自己指定
addrlen:dest_addr的大小
返回值
成功:发送的字节数
失败: 返回-1
//udp服务器
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n",__FILE__,__func__,__LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
int sockfd;
//第一步:创建套接字
if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
{
ERRLOG("socket error");
}
struct sockaddr_in serveraddr,clientaddr;
socklen_t addrlen = sizeof(serveraddr);
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("192.168.98.147");
serveraddr.sin_port = 8888;
//第三步:将套接字与服务器网路信息结构体绑定
int ret = bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret == -1)
{
ERRLOG("bind");
}
//进行通信
char buf[32] = {0};
while(1)
{
NEXT:
if(recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&clientaddr,&addrlen) == -1)
{
ERRLOG("recvfrom");
}
if(strcmp(buf,"bye") == 0)
{
printf("客户端%s-%d退出了\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
goto NEXT;
}
printf("%s-%d: %s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buf);
}
return 0;
}
//udp客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n",__FILE__,__func__,__LINE__);\
exit(1);\
}while(0)
int main(int argc, char const *argv[])
{
int sockfd;
//第一步:创建套接字
if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
{
ERRLOG("socket error");
}
struct sockaddr_in serveraddr,clientaddr;
socklen_t addrlen = sizeof(serveraddr);
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("192.168.98.147");
serveraddr.sin_port = 8888;
//进行通信
char buf[32] = {0};
while(1)
{
scanf("%s",buf);
if(sendto(sockfd,buf,32,0,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
ERRLOG("sendto");
}
if(strcmp(buf,"bye") == 0)
{
printf("客户端退出了\n");
}
memset(buf,0,sizeof(buf));
}
return 0;
}