一、并发服务器模型
【1】 循环服务器
1>一次只能处理一个客户端的请求,等待这个客户端退出后,才能处理下一个客户端
2>缺点:循环服务器所处理的客户端不能有耗时操作
//*****模型******
sfd = socket();
bind();
listen();
while(1)
{
newfd = accept();
while(1)
{
recv();
send();
}
close(newfd);
}
close(sfd);
【2】并发服务器
1>可以同时处理多个客户端请求
2>父进程 / 主线程专门用于负责连接,创建子进程 / 分支线程用来与客户端交互
~多线程:
//*****模型******
sfd = socket();
bind();
listen();
while(1)
{
newfd = accept();pthread_create();
pthread_detach(tid);
}
close(sfd);
void * callBack(void * arg)
{
recv();
send();
close(newfd);pthread_exit(NULL);
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/wait.h>
#include<signal.h>
#include<stdlib.h>
#include<pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.79" //本机IP
#define PORT 6666 // 1024-49151
void* deal_cli_msg(void *arg);
//需要传递给线程处理函数的参数
struct cli_msg
{
int newfd;
struct sockaddr_in cin;
};
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("sfd=%d\n",sfd);
//允许端口快速重用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体
//真是的地址信息结构体根据地址族制定 AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;//必须填 AF_INET
sin.sin_port = htons(PORT);//端口号, 1024-49151
sin.sin_addr.s_addr = inet_addr(IP);//本机IP,ifconfig
//将IP和端口号绑定到套接字上
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
pthread_t tid;
int newfd=-1;
struct cli_msg info;
while(1)
{
//阻塞函数,从已完成连接的队列头中获取一个客户端信息
//该文件描述符才是与客户端通信的文件描述符
newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
info.newfd=newfd;
info.cin=cin;
//能运行到当前位置
if(pthread_create(&tid,NULL,deal_cli_msg,&info)!=0)
{
fprintf(stderr,"line:%d pthread_createfailed\n",__LINE__);
return -1;
}
pthread_detach(tid); //分离线程
}
close(sfd);
return 0;
}
void* deal_cli_msg(void *arg)
{
int newfd=((struct cli_msg*)arg)->newfd;
struct sockaddr_in cin=((struct cli_msg*)arg)->cin;
char buf[128]="";
ssize_t res=0;
while(1)
{
//接收
res=recv(newfd,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
break;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
break;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//发送
strcat(buf,"*_*");
if(send(newfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
break;
}
printf("send sucess\n");
}
close(newfd);
pthread_exit(NULL);
}
二、IO模型
【1】阻塞IO
创建套接字文件描述符后,默认处于阻塞IO模式 (read, write, recv, send, recvfrom, sendto)
【2】阻塞IO
1>防止进程阻塞在IO函数上,但是如果想要获取到有效数据,需要轮询
2>当一个程序使用了非阻塞IO模式的套接字,那么它需要使用一个循环来不停地判断该文件描述符是否有数据可读,称之为 polling
3>应用程序不停地polling 内核监测IO 事件是否产生, CPU消耗高
1)fcntl 函数
【3】信号驱动IO
1>异步通信方式
2>信号驱动IO是指:预先告诉内核,某个文件描述符发生IO事件的时候,内核会通知相关进程: SIGIO
3> 对于TCP而言,信号驱动IO对TCP没有用,因为信号产生过于频繁,而且不能区分是哪个文件描述符发送的
【4】IO 多路复用(重点)
1>进程中如果同时需要处理多路输入输出流,在使用单进程单线程的情况下,同时处理多个输入输出请求
2>在无法用多进程多线程,可以选择用IO多路复用
3>由于不需要创建建新的进程和线程,减少系统的资源开销,减少上下文切换的次数
4>允许同时对多个IO进行操作,内核一旦发现进程执行一个或多个IO 事件,会通知该进程。
1)select
模型:
代码:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.211" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//允许端口快速使用
int reuse=-1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速重用成功\n");
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port=htons(PORT);//端口号
sin.sin_addr.s_addr=inet_addr(IP);//本机号
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess__%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
//设置一个读集合
fd_set readfds,tmpfds;
//由于readfds中需要防止要检测的文件描述符,所以不能让他是随机值
//所以需要将readfds清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//将需要的文件描述符添加到集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int s_res=0;
char buf[128]="";
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
struct sockaddr_in saveCin[1024-4];//另存客户端地址信息,0,1,2,sfd不可能有对应的客户端
int newfd =-1;
ssize_t res=0;
int maxfd=sfd;//最大文件描述符
while(1)
{
tmpfds=readfds;
s_res=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res<0)
{
ERR_MSG("select");
return -1;
}
else if(0==res)
{
printf("time out\n");
return -1;
}
//能运行到当前位置,则代表有文件描述符准备就绪
//走触发事件的文件描述符对应的处理函数
//当前集合中有文件描述符准备就绪了
//当准备就绪,集合会只保留0
//当sfd准备就绪,集合就会只保留sfd
//当0和sfd都准备就绪,集合中保留0和sfd
for(int i=0;i<=maxfd;i++)
{
if(!FD_ISSET(i,&tmpfds))//如果不在集合中,则直接往后继续变量
{
continue;
}
//能运行到当前位置,则说明i代表的文件描述符在tmpfds中
//要判断i所代表的文件描述符需要走什么对应的函数
if(0==i)
{
printf("触发键盘输入事件>>");
int sndfd=-1;
res=scanf("%d %s",&sndfd,buf);
while(getchar()!=10);
if(res!=2)
{
fprintf(stderr,"请输入正确格式:int string\n");
continue;
}
//能运行到当前位置,则代表输入的格式整数
if(sndfd<=sfd||sndfd>=1024||!FD_ISSET(sndfd,&readfds))
{
fprintf(stderr,"sndfd=%d文件描述符\n",sndfd);
continue;
}
if(send(sndfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
}
else if(sfd==i)
{
printf("触发客户端连接事件>>");
fflush(stdout);
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
saveCin[newfd-4]=cin;
//将newfd添加到读集合中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd=maxfd>newfd?maxfd:newfd;
}
else
{
bzero(buf,sizeof(buf));
//接收
res=recv(i,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//关闭文件描述符
close(i);
//从集合中剔除该文件描述符
FD_CLR(i,&readfds);
//更新maxfd
//从目前最大的文件描述符中往小的判断
int j=0;
for(int j=maxfd;j>=0;j--)
{
if(FD_ISSET(j,&readfds))
{
maxfd=j;
break;
}
}
if(j<0)
maxfd=-1;
continue;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
strcat(buf,"*_*");
if(send(i,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
}
}
}
close(sfd);
return 0;
}
2)poll
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#include<poll.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.211" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
//设置一个读集合
fd_set readfds,tmpfds;
//由于readfds中需要防止要检测的文件描述符,所以不能让他是随机值
//所以需要将readfds清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//将需要的文件描述符添加到集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int s_res=0;
char buf[128]="";
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
int newfd =-1;
ssize_t res=-1;
int maxfd=sfd;
while(1)
{
tmpfds=readfds;
s_res=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res<0)
{
ERR_MSG("select");
return -1;
}
if(0==res)
{
printf("time out\n");
return -1;
}
//能运行到当前位置,则代表有文件描述符准备就绪
//走触发事件的文件描述符对应的处理函数
for(int i=0;i<=maxfd;i++)
{
if(!FD_ISSET(i,&tmpfds))//如果不在集合中,则直接往后继续变量
{
continue;
}
//能运行到当前位置,则说明i代表的文件描述符在tmpfds中
//要判断i所代表的文件描述符需要走什么对应的函数
if(0==i)
{
printf("触发键盘输入事件>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
printf("%s\n",buf);
}
else if(sfd==1)
{
printf("触发客户端连接事件>>");
fflush(stdout);
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//将newfd添加到读集合中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd=maxfd>newfd?maxfd:newfd;
}
else
{
bzero(buf,sizeof(buf));
//接收
res=recv(i,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//关闭文件描述符
close(i);
//从集合中剔除该文件描述符
FD_CLR(i,&readfds);
//更新maxfd
//从目前最大的文件描述符中往小的判断
int j=0;
for(int j=maxfd;j>=0;j--)
{
if(FD_ISSET(j,&readfds))
{
maxfd=j;
break;
}
}
if(j<0)
maxfd=-1;
continue;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
strcat(buf,"*_*");
if(send(i,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
}
}
}
return 0;
}
三、函数库
【1】库的概念
1>库是一个二进制可执行文件,相比较于二进制可执行程序,库是不能单独运行的。
a. 库中存放的都是功能函数,只能放功能函数
b. 库中不允许存放main函数
c.库是写好的,可以被服用的功能代码
2>库需要被载入到内存中使用
3>每个操作系统都有自己得库,不兼容
库的分类:
静态库、动态库
【2】静态库
1)静态库原理:通过静态库封装的函数,在程序编译到链接库步骤的时候,会将函数继承到可执行程序中。
优点: 1>程序运行的时候,与静态库没有任何关系,方便移植
2>静态库的运行效率会更快
缺点:1>程序的更新部署比较麻烦
2>大、存储的时候浪费磁盘空间,加载运行的时候浪费内存空间
2)静态库地 制作指令
步骤:
1>分文件,将功能函数与主函数分离,func.c main.c
2>给功能函数写头文件:func.h
3>将main.c 和 func.c 联合编译,测试分文件是否成功
4>用上述指令将func.c 封装成静态库
5>注意:libxxx.a , xxx才是库的名字
3)静态库的使用
【3】动态库(共享库)
1)动态库的原理:动态库把库函数的链接步骤推迟到程序运行的时候,当程序执行到库函数的时候,会去找动态库函数
1>如果在内存中不存在该动态库函数,则会将动态库函数加载到内存中执行
2>如果在内存中存在该动态库函数,则直接调用,不会加载第二份
3>所以内存中会仅存在一份动态库函数
优点:
1>小、存储的时候节省磁盘空间,运行的时候节省内存空间
2>程序的更新部署比较方便,不需要重新编译可执行程序
缺点:
1>程序运行的时候,如果没有找到动态库,则会导致程序崩溃,所以用动态库生成二进制程序移植性低
2>运行效率低
2)动态库的制作指令
步骤:
1>分文件,将功能函数与主函数分离,func.c main.c
2>给功能函数写头文件:func.h
3>将main.c 和 func.c 联合编译,测试分文件是否成功
4>用上述指令将func.c 封装成静态库
5>注意:libxxx.so , xxx才是库的名字
3)动态库的使用
4)动态库的环境变量配置
1> 将动态库移动到 / lib/或者 /usr/lib 目录下
a. sudo mv ./libmyfunc.so /usr/lib/
b. sudo mv libmyfunc.so/lib/
2>配置环境变量:LD_LIBRARY_PATH
3>修改环境变刘昂的配置文件
a. cd / ec/ld.so.conf.d/
b. sudo touch my.conf 创建一个 .conf结尾的文件
c. sudo vim my.conf
d. 将动态库所在的文件夹的绝对路径填写进去,注意一行只能填写一个动态库的绝对路径
e. 保存退出后,执行sudo ldconfig 刷新环境变量