一:项目内容

本项目使用C++实现一个具备服务器端和顾客端即时通讯且具有私聊功能的聊天室。

目的是学习C++网路开发的基本概念,同时也可以熟悉下Linux下的C++程序编译和简单MakeFile编撰

二:需求剖析

这个聊天室主要有两个程序:

1.服务端:能否接受新的顾客联接,并将每位顾客端发来的信息,广播给对应的目标顾客端。

2.顾客端:能否联接服务器,并向服务器发送消息,同时可以接收服务器发来的消息。

即最简单的C/S模型。

三:具象与细化

服务端类须要支持:

1.支持多个顾客端接入,实现聊天室基本功能。

2.启动服务,构建窃听端口等待顾客端联接。

3.使用epoll机制实现并发,降低效率。

4.顾客端联接时,发送欢迎消息,并储存联接记录。

5.顾客端发送消息时,按照消息类型,广播给所有用户(群聊)或则指定用户(私聊)。

6.顾客端恳求退出时,对相应联接信息进行清除。

顾客端类须要支持:

1.联接服务器。

2.支持用户输入消息,发送给服务端。

3.接受并显示服务端发来的消息。

4.退出联接。

涉及两个事情,一个写,一个读。所以顾客端须要两个进程分别支持以下功能。

子进程:

1.等待用户输入信息。

2.将聊天信息写入管线(pipe),并发献给父进程。

父进程:

1.使用epoll机制接受服务端发来的消息,并显示给用户,使用户看见其他用户的信息。

2.将子进程发送的聊天信息从管线(pipe)中读取下来,并发献给顾客端。

四:C/S模型

c语言管道通信_linuxc管道_linux管道通信c  编程

TCP服务端通讯常规步骤:

1.socket()创建TCP套接字

c语言管道通信_linuxc管道_linux管道通信c  编程

2.bind()将创建的套接字绑定到一个本地地址和端口上

3.listen(),将套接字设为窃听模式,打算接受顾客恳求

4.accept()等用户恳求到来时接受,返回一个对应此联接新套接字

5.用accept()返回的套接字和顾客端进行通讯,recv()/send()接受/发送信息。

6.返回,等待另一个顾客恳求。

7.关掉套接字

TCP顾客端通讯常规步骤:

1.socket()创建TCP套接字。

2.connect()构建抵达服务器的联接。

3.与顾客端进行通讯,recv()/send()接受/发送信息,write()/read()子进程写入管线,父进程从管线中读取信息之后send给顾客端

5.close()关掉顾客联接。

五:相关技术介绍

1.socket阻塞与非阻塞

阻塞与非阻塞关注的是程序在等待调用结果时(消息,返回值)的状态。

阻塞调用是指在调用结果返回前,当前线程会被挂起,调用线程只有在得到调用结果以后才能返回。

非阻塞调用是指在不能立即得到结果之前,该调用不会阻塞当前线程。

eg.你打电话问书城老总有没有《网络编程》这本书,老总去书柜上找,倘若是阻塞式调用,你都会把自己仍然挂起,守在电话边上,直至得到这本书有或则没有的答案。假如是非阻塞式调用,你可以干别的事情去,隔一段时间来看一下老总有没有告诉你结果。

同步异步是对书城老总而言(同步老总不会提醒你找到结果了,异步老总会打电话告诉你),阻塞和非阻塞是对你而言。

socket()函数创建套接字时,默认的套接字都是阻塞的,非阻塞设置方法代码:

//将文件描述符设置为非阻塞形式(借助fcntl函数)

fcntl(sockfd,F_SETFL,fcntl(sockfd,F_GETFD,0)|O_NONBLOCK);

2.epoll

当服务端的人数越来越多,会造成资源吃紧,I/O效率越来越低,这时就应当考虑epoll,epoll是Linux内核为处理大量句柄而改进的poll,是linux特有的I/O函数。其特征如下:

1)epoll是Linux下多路复用IO插口select/poll的提高版本linux论坛,其实现和使用方法与select/poll大有不同linux软件下载,epoll通过一组函数来完成有关任务,而不是一个函数。

2)epoll之所以高效,是由于epoll将用户关心的文件描述符放在内核里的一个风波列表中,而不是像select/poll每次调用都须要重复传入文件描述符集或风波集(大量拷贝开支),例如一个风波发生,epoll无需遍历整个被窃听的描述符集,而只须要遍历什么被内核IO风波异步唤起而加入就绪队列的描述符集合即可。

3)epoll有两种工作方法,LT(Leveltriggered)水平触发、ET(Edgetriggered)边缘触发。LT是select/poll的工作方法,比较低效,而ET是epoll具有的高速工作方法。

Epoll用法(三步曲):

第一步:intepoll_create(intsize)系统调用linux管道通信c 编程,创建一个epoll句柄,参数size拿来告诉内核窃听的数量,size为epoll支持的最大句柄数。

第二步:intepoll_ctl(intepfd,intop,intfd,structepoll_event*event)风波注册函数

参数epfd为epoll的句柄。参数op表示动作三个宏来表示:EPOLL_CTL_ADD注册新fd到epfd、EPOLL_CTL_MOD更改早已注册的fd的窃听风波、EPOLL_CTL_DEL从epfd句柄中删掉fd。参数fd为须要窃听的标示符。参数结构体epoll_event告诉内核须要窃听的风波。

第三步:intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout)等待风波的形成,通过调用搜集在epoll监控中已然发生的风波。参数structepoll_event是风波队列把就绪的风波放进去。

eg.服务端使用epoll的时侯步骤如下:

c语言管道通信_linux管道通信c  编程_linuxc管道

1.调用epoll_create()在linux内核中创建一个风波表。

2.之后将文件描述符(窃听套接字listener)添加到风波表中

3.在主循环中,调用epoll_wait()等待返回就绪的文件描述符集合。

4.分别处理就绪的风波集合,本项目中一共有两类风波:新用户联接风波和用户发来消息风波。

六:代码结构

c语言管道通信_linux管道通信c  编程_linuxc管道

每位文件的作用:

1.Common.h:公共头文件,包括所有须要的宏以及socket网路编程头文件,以及消息结构体(拿来表示消息类别等)

2.Client.hClient.cpp:顾客端类的实现

3.Server.hServer.cpp:服务端类的实现

4.ClientMain.cppServerMain.cpp顾客端及服务端的主函数。

七:代码实现

Common.h

定义一些共用的宏定义,包括一些共用的网路编程相关头文件。

1)定义一个函数将文件描述符fd添加到epfd表示的内核风波表中供顾客端和服务端两个类使用。

2)定义一个信息数据结构,拿来表示传送的信息linux管道通信c 编程,结构体包括发送方fd,接收方fd,拿来表示消息类别的type,还有文字信息。

函数recv()send()write()read()参数传递是字符串,所以在传送前/接受后要把结构体转换为字符串/字符串转换为结构体。

#ifndefCHATROOM_COMMON_H

#defineCHATROOM_COMMON_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

//默认服务器端IP地址

linux管道通信c  编程_c语言管道通信_linuxc管道

#defineSERVER_IP”127.0.0.1″

//服务器端标语

#defineSERVER_PORT8888

//intepoll_create(intsize)中的size

//为epoll支持的最大句柄数

#defineEPOLL_SIZE5000

//缓冲区大小65535

#defineBUF_SIZE0xFFFF

//新用户登陆后的欢迎信息

#defineSERVER_WELCOME”Welcomeyoujointothechatroom!YourchatIDis:Client#%d”

//其他用户收到消息的前缀

#defineSERVER_MESSAGE”ClientID%dsay>>%s”

#defineSERVER_PRIVATE_MESSAGE”Client%dsaytoyouprivately>>%s”

#defineSERVER_PRIVATE_ERROR_MESSAGE”Client%disnotinthechatroomyet~”

//退出系统

#defineEXIT”EXIT”

//提醒你是聊天室中惟一的顾客

#defineCAUTION”Thereisonlyoneintthecharroom!”

//注册新的fd到epollfd中

//参数enable_et表示是否启用ET模式,倘若为True则启用,否则使用LT模式

staticvoidaddfd(intepollfd,intfd,boolenable_et)

structepoll_eventev;

ev.data.fd=fd;

ev.events=EPOLLIN;

if(enable_et)

ev.events=EPOLLIN|EPOLLET;

epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);

//设置socket为非阻塞模式

//套接字立即返回,不管I/O是否完成,该函数所在的线程会继续运行

//eg.在recv(fd…)时,该函数立即返回,在返回时,内核数据还没打算好会返回WSAEWOULDBLOCK错误代码

fcntl(fd,F_SETFL,fcntl(fd,F_GETFD,0)|O_NONBLOCK);

linuxc管道_linux管道通信c  编程_c语言管道通信

printf(“fdaddedtoepoll!nn”);

//定义信息结构,在服务端和顾客端之间传送

structMsg

inttype;

intfromID;

inttoID;

charcontent[BUF_SIZE];

};

#endif//CHATROOM_COMMON_H

服务端类Server.hServer.cpp

服务端须要的插口:

1)init()初始化

2)Start()启动服务

3)Close()关掉服务

4)广播消息给所有顾客端函数SendBroadcastMessage()

服务端的主循环中每次就会检测并处理EPOLL中的就绪风波,而就绪风波列表主要是两种类型:新联接或新消息。服务器会依次从就绪风波列表里提取风波进行处理,若果是新联接则accept()之后addfd(),假如是新消息则SendBroadcastMessage()实现聊天功能。

Server.h

#ifndefCHATROOM_SERVER_H

#defineCHATROOM_SERVER_H

#include

#include”Common.h”

usingnamespacestd;

//服务端类,拿来处理顾客端恳求

classServer{

public:

//无参数构造函数

Server();

//初始化服务器端设置

voidInit();

//关掉服务

voidClose();

//启动服务端

voidStart();

private:

//广播消息给所有顾客端

intSendBroadcastMessage(intclientfd);

//服务器端serverAddr信息

structsockaddr_inserverAddr;

//创建窃听的socket

intlistener;

//epoll_create创建后的返回值

intepfd;

//顾客端列表

listclients_list;

};

//Server.cpp

#include

#include”Server.h”

usingnamespacestd;

//服务端类成员函数

//服务端类构造函数

Server::Server(){

//初始化服务器地址和端口

serverAddr.sin_family=PF_INET;

serverAddr.sin_port=htons(SERVER_PORT);

serverAddr.sin_addr.s_addr=inet_addr(SERVER_IP);

//初始化socket

listener=0;

//epoolfd

epfd=0;

//初始化服务端并启动窃听

voidServer::Init(){

cout

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复