前言
和之前的udp聊天室有异曲同工之处,这次我们客户端send的是一个封装好了的数据包,recv的是一个字符串,服务器recv的是一个数据包,send的是一个字符串,在用户连接的时候发送一个login请求,然后服务器端处理,并广播到其他客户端去
多路复用的原理-
多路复用指的是:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。其实就是一种异步处理的操作,等待可运行的描述符。
-
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
多路复用大体有三种实现方式分别是:
- select
- poll
- epoll 本次代码主要是展示select的用法:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
这个是Linux的man手册给出的select的声明
- 第一个参数
ndfs
第一个参数是nfds表示的是文件描述集合中的最大文件描述符+1,因为select的遍历使用是[0,nfds)的 - 第二个参数
readfds
readfds表示的是读事件的集合 - 第三个参数
writefds
writefds表示的是读事件的集合 - 第四个参数
exceptfds
exceptfds表示的是异常参数的集合 - 第五个参数
timeout
表示的是超时时间,timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval{
long tv_sec; //second
long tv_usec; //microseconds
}
fd_set
fd_set结构体的定义实际包含的是fds_bits位数组,该数组的每个元素的每一位标记一个文件描述符其大小固定,由FD_SETSIZE指定,一般而言FD_SETSIZE的大小为1024 我们只用关心怎么使用即可: 下面几个函数就是操作fd_set的函数
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
服务器Code
实现的功能是:
- 客户端连接到客户端时,服务器向其他客户端进行广播上线
- 向服务器发送消息,然后服务器向其他客户端广播上线
- 客户端退出,服务器向其他客户端广播
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 1024
int fd[FD_SETSIZE];//用户集合,最大承受量
typedef struct Msg{//消息的结构
char type;//消息类型
char name[20];
char text[N];//消息内容
}MSG;
typedef struct User{
int fd;
struct User *next;
}USE;
USE *head;
USE *init() {
USE *p = (USE *)malloc(sizeof(USE));
memset(p,0,sizeof(USE));
p->next = NULL;
return p;
}
void Link(int new_fd) {//将新连接加入用户列表里面
USE *p = head;
while(p->next) {
p=p->next;
}
USE *k = (USE*)malloc(sizeof(USE));
k->fd = new_fd;
k->next = NULL;
p->next = k;
}
void login(int fd,MSG msg) {
USE *p = head;
char buf[N+30];
strcpy(buf,msg.name);
strcat(buf,"上线啦!快来找我玩叭!");
printf("fd = %d %s\n",fd,buf);
while(p->next) {//给其他用户发上线信息
if(fd != p->next->fd)
send(p->next->fd,&buf,sizeof(buf),0);
p = p->next;
}
// puts("Over login");
}
void chat(int fd,MSG msg) {
// printf("%d\n",msg.text[0]);
if(strcmp(msg.text,"\n") == 0) return;
USE *p = head;
char buf[N+30];
strcpy(buf,msg.name);
strcat(buf,": ");
strcat(buf,msg.text);
printf("%s\n",buf);
while(p->next) {//给其他用户发信息
if(fd != p->next->fd)
send(p->next->fd,&buf,sizeof(buf),0);
p = p->next;
}
}
void quit(int fd,MSG msg) {
USE *p = head;
char buf[N+30];
strcpy(buf,msg.name);
strcat(buf,"伤心的退出群聊!");
printf("%s\n",buf);
while(p->next) {//给其他用户发上线信息
if(fd != p->next->fd)
send(p->next->fd,&buf,sizeof(buf),0);
p = p->next;
}
}
/*
* 初始化TCP服务器,返回服务器的socket描述符
* */
int init_tcp_server(unsigned short port) {
int ret;
int opt;
int listen_fd;
struct sockaddr_in self; // 监听描述符
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?