Linux 学习记录33(网络编程篇)
本文目录
- Linux 学习记录33(网络编程篇)
- 一、阻塞IO
- 二、非阻塞IO
- 三、信号驱动IO
- 四、`IO多路复用(重点)`
- 1. select (TCP服务器)
- TCP服务器代码
- TCP客户端代码
- 2. poll
一、阻塞IO
1. 最常用,最简单,效率最低的.
2. 创建套接字文件描述符后,默认处于阻塞IO模式
3. read, write, recv, send, recvfrom ,sendto,accept
二、非阻塞IO
1. 防止进程阻塞在IO函数上,但是如果想要获取到有效数据,需要轮询。
2. 当一个程序使用了非阻塞10模式的套接字,那么它需要使用一个循环来不停的判断该文件描述符是否有数据可读,称之为polling
3. 应用程序不停的polling内核监测IO事件是否产生,cpu消耗率高
4. IO中导致函数阻塞的原因是因为文件描述符有阻塞属性
头文件:
#include <fcntl.h> 包含文件的属性
函数:
int fcntl(int fd, int cmd, ... /* arg */ );
功能:获取/设置文件描述符属性
1. 获取原有属性
2. 在原有属性的基础上 加上非阻塞属性
3. 将修改后的属性重新设置到文件描述符上
参数1:指定要设置的文件描述符
参数2:参数
F_GETFD (void) :获取文件属性,数值
F_SETFD (int) :设置文件属性权限,要设置的权限放在第三个参数传入
参数3:要设置的文件描述符属性
===================================================================
/*获取原有文件属性*/
int flags = fcntl(0, F_GETFL);
/*添加非阻塞属性*/
flags |= O_NONBLOCK;
/*设置文件描述符属性*/
fcntl(0, F_SETFL ,flags);
三、信号驱动IO
1. 异步通信方式;
2. 信号驱动IO是指预先告诉内核,使得某个文件描述符发生I0事件的时候,内核会通知相关进程:SIGIO
3. 对于TCP而言,信号驱动IO对TCP没有用。因为信号产生过于频繁,而且不能区分是哪个文件描述符发生的。
四、IO多路复用(重点)
所需头文件:#include <sys/select.h>
中
- 进程中如果同时需要处理多路输入输出流,在使用单进程单线程的情况下,同时处理多个输入输出请求。
- 在无法用多进程多线程,可以选择用IO多路复用;
- 由于不需要创建新的进程和线程,减少系统的资源开销,减少上下文切换的次数
- 上下文: 运行一个进程所需要的所有资源
- 上下文切换: 从A进程切换到B进程,A进程的资源要完全替换成B进程的,是一个耗时操作
- 允许同时对多个IO进行操作,内核一旦发现进程执行一个或多个IO事件,会通知该进程
1. select (TCP服务器)
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:
若集合中有文件描述符准备就绪,则select解除阻塞后,集合中会只剩下触发事件的文件描述符。
参数1:三个集合中最大的文件描述符+1
参数2:读集合
参数3:写集合
参数4:其他集合
参数5:超时间,若不想设置填NULL,则直到文件描述符准备就绪结束阻塞
返回值:
大于0:成功触发的文件描述符的个数
等于0:时间结束
小于0:失败
==============================================================================
操作集合的函数
void FD_CLR(int fd,fd_set *set);将fd从集合中删除
int FD_ISSET(int fd,fd set *set);判断fd是否在集合中,若存在返回真,否则返回假;
void FD_SET(int fd, fd set *set);将fd设置到集合中
void FD_ZERO(fd, set *set);清空集合
TCP服务器代码
int main(int argc, char const *argv[])
{
int i=0;
time_t sys_t = time(NULL);
struct tm *fomattime = localtime(&sys_t);
char time_str[50] = {0};
/*创建流式套接字*/
int sfd = socket(AF_INET, SOCK_STREAM, 0);
const int sfd_const = sfd;
printf("创建流式套接字...\r\n");
if(sfd < 0)
{//如果创建失败则
ERR_MSG("socket");
return -1;
}
printf("创建成功\r\n");
/*允许端口被快速重用*/
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)))
{
ERR_MSG("setsockopt");
return -1;
}
/*绑定服务器的IP和端口*/
struct sockaddr_in sin_addr;//定义本机通用地址信息结构体
char TCP_ADDR[20] = {0};//存放本机IP
int port = 0;
sin_addr.sin_family = AF_INET;//必须填AF_INET
printf("input address : ");
fgets(TCP_ADDR,sizeof(TCP_ADDR),stdin);//获取IP
printf("input port : ");
scanf("%d",&port);//获取端口号
sin_addr.sin_port = htons(port);//端口号
sin_addr.sin_addr.s_addr = inet_addr(TCP_ADDR);//填充IP地址
printf("绑定服务器IP和端口......\r\n");
if(bind(sfd,(const struct sockaddr*)&sin_addr,sizeof(sin_addr))<0)
{
ERR_MSG("bind");
return -1;
}
printf("绑定成功\r\n");
/*将套接字设定为被动监听状态*/
printf("设置监听模式\r\n");
if(listen(sfd,BACKLOG) == -1)
{
ERR_MSG("listen");
return -1;
}
printf("设置完成\r\n");
printf("等待客户端链接......\r\n");
/*获取链接成功后的与客户端通信的套接字文件描述符s*/
struct sockaddr_in addr;//存储客户端的地址信息
socklen_t addr_len;
int newfd;
addr_len = sizeof(addr);
/*创建要监测的集合*/
fd_set readfds;
fd_set buffds;
char buf[1024];
/*接收来自客户端的数据*/
char str[128] = {0};
int res = 0;
int s_res = 0;
int max_fd = sfd;
cilent_t* cilent = (cilent_t*)malloc(sizeof(cilent_t)*1024);
/*清空集合*/
FD_ZERO(&readfds);
FD_ZERO(&buffds);
/*将文件描述符设置到集合内*/
FD_SET(0,&buffds);
FD_SET(sfd,&buffds);
while(1)
{
/*重新赋值IO集合*/
readfds = buffds;
/*阻塞等待IO触发*/
s_res = select(max_fd+1, &readfds,NULL,NULL,NULL);
if(s_res == -1)
{
return -1;
}else if(s_res == 0)
{
return 0;
}
/*运行到此则表示有文件描述符被触发*/
/*需要判断是哪个文件描述符准备就绪*/
/*只需要判断集合中剩下那几个文件描述符,就代表该文件描述符有事件产生*/
/*判断是否有接收客户端数据客户端*/
for(i = 0; i <= max_fd; i++)
{
if(!FD_ISSET(i,&readfds))
continue;
if(i == 0)
{
fgets(buf, sizeof(buf),stdin);
buf[sizeof(buf)-1] = '\0';
GET_TIME(time_str);//获取时间戳
TCP_printf(newfd,"%s : stdin>> %s",time_str,buf);
}else if(i == sfd)
{
if((i = accept(sfd,(struct sockaddr*)&addr,&addr_len)) == -1)
{
ERR_MSG("accept");
return -1;
}
strcpy(cilent[i].cil_IP,inet_ntoa(addr.sin_addr));
cilent[i].port=ntohs(addr.sin_port);
printf("客户端已链接\r\n");
printf("客户端IP : %s\r\n",cilent[i].cil_IP);
printf("客户端端口 : %d\r\n",cilent[i].port);
/*更新文件描述符到集合中*/
FD_SET(i,&buffds);
/*更新最大文件描述符值*/
max_fd = max_fd>i?max_fd:i;
}else
{
bzero(str,sizeof(str));//清空字符串
res = recv(i,str,sizeof(str),0);//阻塞接收字符串
if(res == -1)
{
perror("recv");
}else if(res == 0)
{
printf("客户端关闭{%s:%d}\r\n",cilent[i].cil_IP,cilent[i].port);
close(i);//关闭文件描述符
FD_CLR(i,&readfds);
/*更新最大文件描述符值*/
int j = max_fd;
while(!FD_ISSET(j,&readfds)&&j-->=0);
max_fd = j;
continue;
}else
{
GET_TIME(time_str);//获取时间戳
TCP_printf(i,"%s : cilent{%s:%d}>> %s\r\n",time_str,cilent[i].cil_IP,cilent[i].port,str);
}
}
}
}
close(sfd);//关闭套接字文件
return 0;
}
TCP客户端代码
#include "public.h"
int main(int argc, char const *argv[])
{
time_t sys_t = time(NULL);
struct tm *fomattime = localtime(&sys_t);
char time_str[50] = {0};
/*创建流式套接字*/
int cfd = socket(AF_INET, SOCK_STREAM, 0);
printf("创建流式套接字...\r\n");
if(cfd < 0)
{//如果创建失败则
ERR_MSG("socket");
return -1;
}
printf("创建成功\r\n");
/*连接服务器*/
struct sockaddr_in cin_addr;//定义本机通用地址信息结构体
cin_addr.sin_family = AF_INET;//必须填AF_INET
char TCP_ADDR[20] = {0};//存放服务器IP
int TCP_PORT = 0;
printf("input address : ");
fgets(TCP_ADDR,sizeof(TCP_ADDR),stdin);//获取IP
printf("input port : ");
scanf("%d",&TCP_PORT);//获取端口号
cin_addr.sin_port = htons(TCP_PORT);//端口号
cin_addr.sin_addr.s_addr = inet_addr(TCP_ADDR);//填充IP地址
printf("连接服务器......\r\n");
if(connect(cfd,(const struct sockaddr*)&cin_addr,sizeof(cin_addr)) == -1)//链接服务器
{
ERR_MSG("connect");
return -1;
}
printf("服务器连接完成\r\n");
/*接收来自服务器的数据*/
char str[128] = {0};
char buf[128] = {0};
int flage = 0;
int res = 0;
printf("接收服务器数据\r\n");
int s_res = 0;
/*创建要监测的集合*/
fd_set readfds;
fd_set buffds;
/*集合清零*/
FD_ZERO(&readfds);
FD_ZERO(&buffds);
/*集合设置*/
FD_SET(0, &buffds);//设置终端
FD_SET(cfd, &buffds);//设置通讯文件描述符
while (1)
{
readfds = buffds;
/*阻塞等待IO更新*/
s_res = select(cfd+1, &readfds, NULL,NULL,NULL);
if(s_res == -1)
{
return -1;
}else if(s_res == 0)
{
return 0;
}
/*当终端更新*/
if(FD_ISSET(0, &readfds))
{
fgets(buf, sizeof(buf),stdin);
buf[sizeof(buf)-1] = '\0';
send(cfd,buf,sizeof(buf),0);
}
//当文件更新
if(FD_ISSET(cfd, &readfds))
{
res = recv(cfd,str,sizeof(str),0);
if(res == -1)
{
perror("recv");
return 0;
}else if(res == 0)
{
printf("服务器关闭\r\n");
break;
}else
{
GET_TIME(time_str);//获取时间戳
printf("%s : server{%s:%d}>> %s\r\n",time_str,inet_ntoa(cin_addr.sin_addr),ntohs(cin_addr.sin_port),str);
}
}
}
close(cfd);//关闭套接字文件
return 0;
}
2. poll
所需头文件:#include <poll.h>
函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:阻塞函数,让内核监测集合中是否有文件描述符准备就绪,若准备就绪,则立即解除阻塞;
参数1:文件描述符集合
struct pollfd {
int fd; /* file descriptor */ 指定要监测的文件描述符
short events; /* requested events */ 指定要监测的事件
short revents; /* returned events */ 实际产生的事件
};
事件:
1. POLLIN:读事件
2. POLLOUT:写事件
3. POLLERR:错误事件
参数2:指定要监测的文件描述符个数
参数3:超时时间
>0, 设置超时时间,以ms为单位
=0, 非阻塞,会立即解除阻塞
<0, 阻塞,直到有文件描述符准备就绪,解除阻塞
返回值:
>0, 成功触发事件的文件描述符个数;
=0, 超时;
=-1, 函数运行失败