读写IO
阻塞I/O:
读::
read一个文件,假如文件没有数据则读取会阻塞直到有数据时,返回数据的大小或读错误
写:
write一个文件,写一般不会阻塞,但是当要写入的空间写满时,写就会阻塞,等待空间中有地方可写时,才再次写入,或写失败。
非阻塞I/O:
O_NONBLOCK
读写都不会阻塞,他会立即返回读取或写入的数据,假如没有数据可读,或空间写满,他不会等待,只会立即返回-1.
日常使用的IO都为阻塞IO,因为这样可以确保每次都读写顺利,但是这些阻塞IO数量太多的话,我们就必须进行多任务处理。
select多路复用解决阻塞问题:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数一:需要监听的最大文件描述符+1
for(int i=0;i<nfds+1;i++)
{
fd_set[i]
}
参数二:需要监视的读集合
(用户需要获取的读文件描述符)
参数三:需要监视的写集合 -》一般为NULL
用户需要获取的写文件描述符)
参数四:需要监视的出错集合 -》一般为NULL
用户需要获取的出错文件描述符)
参数五:监视的时长
struct timeval {
long tv_sec; /* seconds */秒
long tv_usec; /* microseconds */微妙
};
返回值:》0,监视的集合中,活跃的文件描述符数量
=0,监视超时,(超过监视的时间后都没有任何,文件描述符活跃)
《0,select函数出错
void FD_ZERO(fd_set *set);//清空文件描述符集合
void FD_SET(int fd, fd_set *set);//把需要监视的文件描述符加到集合中
void FD_CLR(int fd, fd_set *set);//把文件描述符在集合中删除
int FD_ISSET(int fd, fd_set *set);//查看文件描述符是否活跃
FD_ISSET假如判断的文件描述符集合正确则返回 true 或者是 false
注意假如select超时后,他就会清空所有文件描述符集合
--------------------------------------------------------------------------------------------------------------------------------------
练习:使用select函数制作一个服务器 (不用使用线程和进程,处理服务器中的任务)
/*
* 函数功能:实现局域网之间好友之间上线,下线提醒,发消息的功能
* 其中发的消息格式必须是”好友id:消息内容“
* 端口号为6666
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
char status[20],ip[20],id[20];
int port = 0;//用来保存别的客户端发送消息切割后的消息
struct sockaddr_in recv_addr;//用来保存发送方的ip的结构体
int sockfd;
typedef struct cli_node //存储好友的信息
{
char ip[20]; //ip地址
int port; //端口号
char id[20]; //昵称
struct cli_node *next;
}CLI;
typedef struct head_node
{
CLI *head;
}H_NODE;
H_NODE *cli_list = NULL;//用来存储客户端信息的链表
H_NODE *creat_list()
{
H_NODE *list;
list = (H_NODE *)malloc(sizeof(H_NODE));
if (NULL == list)
return NULL;
list->head = NULL;
return list;
}
/*
* 头结点保存的是自己的信息
*/
int wait_cli()//接收信息并存到链表中
{
CLI *node = (CLI *)malloc(sizeof(CLI));
if(node == NULL){
perror("malloc error\n");
return 0;
}
strcpy(node->ip,ip);
node->port = port;
strcpy(node->id,id);
node->next = NULL;
if(cli_list->head == NULL){
cli_list->head = node;
return 0;
}
CLI *tmp = cli_list->head;
while(tmp->next != NULL){
tmp = tmp->next;
}
tmp->next = node;
return 0;
}
void del_node()//删除指定ip的节点
{
CLI *tmp = cli_list->head->next;
CLI *pre = cli_list->head;
while(tmp!= NULL){
//printf("%s----%s\n",ip,tmp->ip);
if(strcmp(tmp->ip,ip) == 0)
break ;
tmp = tmp->next;
pre = pre->next;
}
if(tmp->next == NULL){//最后一个节点需要删除
pre->next = NULL;
free(tmp);
tmp = NULL;
return ;
}
//中间节点
pre->next = tmp->next;
tmp->next = NULL;
free(tmp);
tmp = NULL;
return ;
}
void print_list()
{
printf("******************friend list************************\n");
//printf("port \t ip \t id");
CLI *tmp = cli_list->head;
while(tmp)
{
printf("friend msg:port = %d,ip = %s,id = %s\n",tmp->port,tmp->ip,tmp->id);
tmp = tmp->next;
}
}
CLI *serach_ip(char *id1)//找到指定id的节点
{
CLI *tmp = cli_list->head;//遍历链表找到对应的ip和昵称
while(tmp->next != NULL){
if(strcmp(tmp->id,id1) == 0)
break ;
tmp = tmp->next;
}
return tmp;
}
//
void *answer_func(void *argc)
{
struct sockaddr_in answer_addr;//回复消息的结构体
char answer_msg[50] = {0};//用来保存回复的消息内容
char msg[50] = {0};
char id1[10] = {0};
//printf("please input the msg to answer:id-xianxo\n");
while(1){
scanf("%s",answer_msg);
if(strstr(answer_msg,":") == NULL){
printf("please input id:msg\n");
continue;
}
sscanf(answer_msg,"%[^:]:%s",id1,msg);
printf("%s,%s\n",id1,msg);
CLI *tmp = serach_ip(id1);
answer_addr.sin_family = AF_INET;
answer_addr.sin_port = htons(6666); //接收端的端口号
answer_addr.sin_addr.s_addr = inet_addr(tmp->ip); //发送广播数据到7段的IP里面
printf("reply ip = %s,port = %d\n",inet_ntoa(answer_addr.sin_addr),ntohs(answer_addr.sin_port));
//printf("%s :%s\n",tmp->id,buf);
int ret = sendto(sockfd,msg,strlen(msg),0,(struct sockaddr *)&answer_addr,sizeof(answer_addr));
if(ret > 0){
printf("send msg OK to %s\n",tmp->id);
}
else{
perror("answer msg error\n");
}
}
}
//下线广播
void off_func(int arc)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6666); //接收端的端口号
addr.sin_addr.s_addr = inet_addr("192.168.7.255"); //发送广播数据到7段的IP里面
char off_buf[50] = {"OFFLINE-192.168.7.8-6666-czj"};
int on = 1; //使能标记位 1 开启 0关闭
int ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(ret<0){
perror("setsocketopt fail");
}
else{
printf("set sock ok\n");
}
ret=sendto(sockfd,off_buf,strlen(off_buf),0,(struct sockaddr *)&addr,sizeof(addr)); //发送广播
if(ret>0){
printf("buf=%s\tsize=%d\n",off_buf,ret);
}
exit(0);
}
//下线等待ctrl+c线程
void *wait_signal(void *arc)
{
signal(SIGINT,off_func);//捕捉到信号发送离线广播
pause();
}
int main(int argv,char **argc)
{
cli_list = creat_list();//创建头结点
//1.创建socket
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
perror("create sock fail\n");
}
//设置接收端的信息
struct sockaddr_in addr,cli_addr,serv_addr;
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argc[1])); //接收端的端口号
addr.sin_addr.s_addr = inet_addr("192.168.7.255"); //发送广播数据到7段的IP里面
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argc[1])); //接收端的端口号
serv_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = 0;
char send_buf[50]={"LOGIN_IN-192.168.7.8-6666-czj"};// 登录客户端的时候,发送的广播消息
char reply_buf[50]={"ONLINE-192.168.7.8-6666-czj"};//别的客户端上线时,接收到广播后,回复的消息r
//设置广播属性
int on = 1; //使能标记位 1 开启 0关闭
ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(ret<0){
perror("setsocketopt fail");
}
else{
printf("set sock ok\n");
}
bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
ret=sendto(sockfd,send_buf,strlen(send_buf),0,(struct sockaddr *)&addr,sizeof(addr)); //发送广播
if(ret>0){
printf("buf=%s\tsize=%d\n",send_buf,ret);
}
//关闭广播
int off = 0;
ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&off,sizeof(off));
if(ret<0){
perror("close socketopt fail");
}
else{
printf("close sock ok\n");
}
//用来接收终端信号的线程
pthread_t tid1;
pthread_create(&tid1,NULL,wait_signal,NULL);
//用来回复消息的线程
pthread_t tid;
pthread_create(&tid,NULL,answer_func,NULL);
socklen_t recv_addr_len = sizeof(recv_addr);
char buf[50] = {0};
while(1){
bzero(buf,50);
bzero(status,20);
bzero(ip,20);
bzero(id,20);
port = 0;
int size = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&recv_addr,&recv_addr_len);
printf("size = %d,buf = %s\n",size,buf);
if(strstr(buf,"LOGIN_IN") != NULL || strstr(buf,"ONLINE") != NULL || strstr(buf,"OFFLINE") != NULL){//如果是广播消息就加入链表
sscanf(buf,"%[^-]-%[0-9,.]-%d-%s",status,ip,&port,id);
printf("your friend %s is online\n",id);
//假如是登陆信息则添加到链表中
if(strcmp(status,"LOGIN_IN") == 0){
CLI *tmp1 = cli_list->head;//遍历链表,如果此ip已经在链表里面了,就继续接收广播,而不添加链表
while(tmp1){
if(strcmp(inet_ntoa(recv_addr.sin_addr),tmp1->ip) == 0)
goto cat;
tmp1 = tmp1->next;
}
//加入链表,并回复消息
wait_cli();
cat: print_list();
//接收到广播信息后回馈给对方
ret = sendto(sockfd,reply_buf,strlen(reply_buf),0,(struct sockaddr *)&recv_addr,sizeof(recv_addr));
if(ret > 0){
printf("send msg ok\n");
}
else{
perror("send error\n");
}
}
else if(strcmp(status,"ONLINE") == 0){
//加入链表,不回复消息
if(strcmp(inet_ntoa(recv_addr.sin_addr),"192.168.7.8") == 0)//如果ip是自己的ip就不加入链表
continue ;
printf("your friend %s is online\n",id);
wait_cli();
print_list();
}
else if(strcmp(status,"OFFLINE") == 0){
//删除下线的节点
del_node();
printf("your friend %s is offline\n",id);
print_list();
}
}
else{
CLI *tmp = serach_ip(inet_ntoa(recv_addr.sin_addr));
printf("friend %s say:buf = %s\n",tmp->id,buf);
}
}
close(sockfd);
}