转自实验楼教程:C++实现即时通信软件 1 知识储备1.1 C/S模型
服务器端:
- socket()创建监听Socket
- bind()绑定服务器端口
- listen()监听客户端连接
- accept()接受连接
- recv/send接收及发送数据
- close()关闭socket
客户端: - socket()创建监听Socket
- connect()连接服务器
- recv/send接收及发送数据
- close()关闭socket
1.2 TCP服务端通信的常规步骤- 使用socket()创建TCP套接字(socket)
- 将创建的套接字绑定到一个本地地址和端口上(Bind)
- 将套接字设为监听模式,准备接收客户端请求(listen)
- 等待客户请求到来: 当请求到来后,接受连接请求,返回一个对应于此次连接的新的套接字(accept)
- 用accept返回的套接字和客户端进行通信(使用write()/send()或send()/recv() )
- 返回,等待另一个客户请求
- 关闭套接字
1.3 TCP客户端通信的常规步骤- 创建套接字(socket)
- 使用connect()建立到达服务器的连接(connect)
- 客户端进行通信(使用write()/send()或send()/recv())
- 使用close()关闭客户连接
1.4 阻塞与非阻塞socket通常的,对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞方式。 - 阻塞方式是指: 当试图对该文件描述符进行读写时,如果当时没有数据可读,或者暂时不可写,程序就进入等待状态,直到有东西可读或者可写为止。
- 非阻塞方式是指: 如果没有数据可读,或者不可写,读写函数马上返回,而不会等待。
举个例子来说,比如说小明去找一个女神聊天,女神却不在。如果小明舍不得走,只能在女神大门口死等着,当然小明可以休息。当女神来了,她会把你唤醒(囧,因为挡着她门了),这就是阻塞方式。如果小明发现女神不在,立即离开,以后每隔十分钟回来看一下(采用轮询方式),不在的话仍然立即离开,这就是非阻塞方式,在他离开的十分钟内可以干别的事情。1.5 epoll当服务端的在线人数越来越多,会导致系统资源吃紧,I/O效率越来越慢,这时候就应该考虑epoll了。epoll是Linux内核为处理大批句柄而作改进的poll,是Linux特有的I/O函数。其特点如下:
- epoll是Linux下多路复用IO接口select/poll的增强版本。其实现和使用方式与select/poll有很多不同,epoll通过一组函数来完成有关任务,而不是一个函数。
- epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核里的一个事件表中,而不是像select/poll每次调用都需要重复传入文件描述符集或事件集。比如当一个事件发生(比如说读事件),epoll无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入就绪队列的描述符集合就行了。
- epoll有两种工作方式,LT(level triggered):水平触发和ET(edge-triggered):边沿触发。LT是select/poll使用的触发方式,比较低效;而ET是epoll的高速工作方式(本项目使用epoll的ET方式)。
通俗理解就是,比如说有一堆女孩,有的很漂亮,有的很凤姐。现在你想找漂亮的女孩聊天,LT就是你需要把这一堆女孩全都看一遍,才可以找到其中的漂亮的(就绪事件);而ET是你的小弟(内核)将N个漂亮的女孩编号告诉你,你直接去看就好,所以epoll很高效。另外,还记得小明找女神聊天的例子吗?采用非阻塞方式,小明还需要每隔十分钟回来看一下(select);如果小明有小弟(内核)帮他守在大门口,女神回来了,小弟会主动打电话,告诉小明女神回来了,快来处理吧!这就是epoll。
2 需求分析最简单聊天室的群聊,线程池、多线程编程、超时重传确认收包等都不涉及,考虑有两个以下程序 - 服务器:能接受新的客户端的连接,并将每个客户端发过来的消息发给所有其他的客户端
- 客户端:能够连接服务器,并向服务器发送消息,同时接收服务器发过来的任何消息
3 抽象与细化需求中的角色非常简单,同时功能也很简单,所以我们只需要根据功能角色设计客户端类和服务端类。 其中客户端类我们需要支持下面几个功能: - 连接服务器
- 支持用户输入聊天消息,发送消息给服务器
- 接收并显示服务器的消息
- 退出连接
针对上述需求,客户端的实现需要两个进程分别支持下面的功能: 子进程的功能:
- 等待用户输入聊天信息
- 将聊天信息写到管道(pipe),并发送给父进程
父进程的功能:
- 使用epoll机制接受服务端发来的信息,并显示给用户,使用户看到其他用户的聊天信息
- 将子进程发给的聊天信息从管道(pipe)中读取, 并发送给服务端
服务端类需要支持:
- 支持多个客户端接入,实现聊天室基本功能
- 启动服务建立监听端口等待客户端连接
- 使用epoll机制实现并发,增加效率
- 客户端连接时发送欢迎消息并存储连接记录
- 客户端发送消息时广播给其他所有客户端
- 客户端请求退出时对连接信息进行清理
4 代码结构- Common.h:公共头文件,包含所需的所有宏定义及socket网络编程头文件
- Client.h, Client.cpp:客户端类实现。
- Server.h,Server.cpp:服务端类实现。
- ClientMain.cpp ServerMain.cpp:客户端及服务端的主函数。
5 实现- Common.h
- Client.h
- Client.cpp
- Server.h
- Server.cpp
- ClientMain.cpp
- ServerMain.cpp
|