亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

一:項(xiàng)目?jī)?nèi)容

本項(xiàng)目使用C++實(shí)現(xiàn)一個(gè)具備服務(wù)器端和客戶端即時(shí)通信且具有私聊功能的聊天室。

目的是學(xué)習(xí)C++網(wǎng)絡(luò)開(kāi)發(fā)的基本概念,同時(shí)也可以熟悉下linux下的C++程序編譯和簡(jiǎn)單MakeFile編寫(xiě)

分享一個(gè)即時(shí)通訊的實(shí)戰(zhàn)項(xiàng)目給大家:即時(shí)通訊實(shí)戰(zhàn)項(xiàng)目

需要Linux服務(wù)器開(kāi)發(fā)高階學(xué)習(xí)資料的朋友可以后臺(tái)私信【架構(gòu)】獲取

包括Linux,Nginx,ZeroMQ,MySQL,redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK等等視頻學(xué)習(xí)資料。

二:需求分析

這個(gè)聊天室主要有兩個(gè)程序:

1.服務(wù)端:能夠接受新的客戶連接,并將每個(gè)客戶端發(fā)來(lái)的信息,廣播給對(duì)應(yīng)的目標(biāo)客戶端。

2.客戶端:能夠連接服務(wù)器,并向服務(wù)器發(fā)送消息,同時(shí)可以接收服務(wù)器發(fā)來(lái)的消息。

即最簡(jiǎn)單的C/S模型。

三:抽象與細(xì)化

服務(wù)端類(lèi)需要支持:

1.支持多個(gè)客戶端接入,實(shí)現(xiàn)聊天室基本功能。

2.啟動(dòng)服務(wù),建立監(jiān)聽(tīng)端口等待客戶端連接。

3.使用epoll機(jī)制實(shí)現(xiàn)并發(fā),增加效率。

4.客戶端連接時(shí),發(fā)送歡迎消息,并存儲(chǔ)連接記錄。

5.客戶端發(fā)送消息時(shí),根據(jù)消息類(lèi)型,廣播給所有用戶(群聊)或者指定用戶(私聊)。

6.客戶端請(qǐng)求退出時(shí),對(duì)相應(yīng)連接信息進(jìn)行清理。

客戶端類(lèi)需要支持:

1.連接服務(wù)器。

2.支持用戶輸入消息,發(fā)送給服務(wù)端。

3.接受并顯示服務(wù)端發(fā)來(lái)的消息。

4.退出連接。

涉及兩個(gè)事情,一個(gè)寫(xiě),一個(gè)讀。所以客戶端需要兩個(gè)進(jìn)程分別支持以下功能。

子進(jìn)程:

1.等待用戶輸入信息。

2.將聊天信息寫(xiě)入管道(pipe),并發(fā)送給父進(jìn)程。

父進(jìn)程:

1.使用epoll機(jī)制接受服務(wù)端發(fā)來(lái)的消息,并顯示給用戶,使用戶看到其他用戶的信息。

2.將子進(jìn)程發(fā)送的聊天信息從管道(pipe)中讀取出來(lái),并發(fā)送給客戶端。

四:C/S模型

C++ socket網(wǎng)絡(luò)編程——即時(shí)通信系統(tǒng)

 

TCP服務(wù)端通信常規(guī)步驟:

1.socket()創(chuàng)建TCP套接字

2.bind()將創(chuàng)建的套接字綁定到一個(gè)本地地址和端口上

3.listen(),將套接字設(shè)為監(jiān)聽(tīng)模式,準(zhǔn)備接受客戶請(qǐng)求

4.accept()等用戶請(qǐng)求到來(lái)時(shí)接受,返回一個(gè)對(duì)應(yīng)此連接新套接字

5.用accept()返回的套接字和客戶端進(jìn)行通信,recv()/send() 接受/發(fā)送信息。

6.返回,等待另一個(gè)客戶請(qǐng)求。

7.關(guān)閉套接字

TCP客戶端通信常規(guī)步驟:

1.socket()創(chuàng)建TCP套接字。

2.connect()建立到達(dá)服務(wù)器的連接。

3.與客戶端進(jìn)行通信,recv()/send()接受/發(fā)送信息,write()/read() 子進(jìn)程寫(xiě)入管道,父進(jìn)程從管道中讀取信息然后send給客戶端

5. close() 關(guān)閉客戶連接。

五:相關(guān)技術(shù)介紹

1.socket 阻塞與非阻塞。

阻塞與非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果時(shí)(消息,返回值)的狀態(tài)。

阻塞調(diào)用是指在調(diào)用結(jié)果返回前,當(dāng)前線程會(huì)被掛起,調(diào)用線程只有在得到調(diào)用結(jié)果之后才會(huì)返回。

非阻塞調(diào)用是指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程。

eg. 你打電話問(wèn)書(shū)店老板有沒(méi)有《網(wǎng)絡(luò)編程》這本書(shū),老板去書(shū)架上找,如果是阻塞式調(diào)用,你就會(huì)把自己一直掛起,守在電話邊上,直到得到這本書(shū)有或者沒(méi)有的答案。如果是非阻塞式調(diào)用,你可以干別的事情去,隔一段時(shí)間來(lái)看一下老板有沒(méi)有告訴你結(jié)果。

同步異步是對(duì)書(shū)店老板而言(同步老板不會(huì)提醒你找到結(jié)果了,異步老板會(huì)打電話告訴你),阻塞和非阻塞是對(duì)你而言。

socket()函數(shù)創(chuàng)建套接字時(shí),默認(rèn)的套接字都是阻塞的,非阻塞設(shè)置方式代碼:

//將文件描述符設(shè)置為非阻塞方式(利用fcntl函數(shù))

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

2. epoll

當(dāng)服務(wù)端的人數(shù)越來(lái)越多,會(huì)導(dǎo)致資源吃緊,I/O效率越來(lái)越低,這時(shí)就應(yīng)該考慮epoll,epoll是Linux內(nèi)核為處理大量句柄而改進(jìn)的poll,是linux特有的I/O函數(shù)。其特點(diǎn)如下:

1)epoll是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,其實(shí)現(xiàn)和使用方式與select/poll大有不同,epoll通過(guò)一組函數(shù)來(lái)完成有關(guān)任務(wù),而不是一個(gè)函數(shù)。

2)epoll之所以高效,是因?yàn)閑poll將用戶關(guān)心的文件描述符放到內(nèi)核里的一個(gè)事件列表中,而不是像select/poll每次調(diào)用都需要重復(fù)傳入文件描述符集或事件集(大量拷貝開(kāi)銷(xiāo)),比如一個(gè)事件發(fā)生,epoll無(wú)需遍歷整個(gè)被監(jiān)聽(tīng)的描述符集,而只需要遍歷那些被內(nèi)核IO事件異步喚醒而加入就緒隊(duì)列的描述符集合即可。

3)epoll有兩種工作方式,LT(Level triggered) 水平觸發(fā) 、ET(Edge triggered)邊沿觸發(fā)。LT是select/poll的工作方式,比較低效,而ET是epoll具有的高速工作方式。更多epoll之ET LT

Epoll 用法(三步曲):

第一步:int epoll_create(int size)系統(tǒng)調(diào)用,創(chuàng)建一個(gè)epoll句柄,參數(shù)size用來(lái)告訴內(nèi)核監(jiān)聽(tīng)的數(shù)目,size為epoll支持的最大句柄數(shù)。

第二步:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 事件注冊(cè)函數(shù)

參數(shù) epfd為epoll的句柄。參數(shù)op 表示動(dòng)作 三個(gè)宏來(lái)表示:EPOLL_CTL_ADD注冊(cè)新fd到epfd 、EPOLL_CTL_MOD 修改已經(jīng)注冊(cè)的fd的監(jiān)聽(tīng)事件、EPOLL_CTL_DEL從epfd句柄中刪除fd。參數(shù)fd為需要監(jiān)聽(tīng)的標(biāo)識(shí)符。參數(shù)結(jié)構(gòu)體epoll_event告訴內(nèi)核需要監(jiān)聽(tīng)的事件。

第三步:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 等待事件的產(chǎn)生,通過(guò)調(diào)用收集在epoll監(jiān)控中已經(jīng)發(fā)生的事件。參數(shù)struct epoll_event 是事件隊(duì)列 把就緒的事件放進(jìn)去。

eg. 服務(wù)端使用epoll的時(shí)候步驟如下:

1.調(diào)用epoll_create()在linux內(nèi)核中創(chuàng)建一個(gè)事件表。

2.然后將文件描述符(監(jiān)聽(tīng)套接字listener)添加到事件表中

3.在主循環(huán)中,調(diào)用epoll_wait()等待返回就緒的文件描述符集合。

4.分別處理就緒的事件集合,本項(xiàng)目中一共有兩類(lèi)事件:新用戶連接事件和用戶發(fā)來(lái)消息事件。

六:代碼結(jié)構(gòu)

C++ socket網(wǎng)絡(luò)編程——即時(shí)通信系統(tǒng)

 

每個(gè)文件的作用:

1.Common.h:公共頭文件,包括所有需要的宏以及socket網(wǎng)絡(luò)編程頭文件,以及消息結(jié)構(gòu)體(用來(lái)表示消息類(lèi)別等)

2.Client.h Client.cpp :客戶端類(lèi)的實(shí)現(xiàn)

3.Server.h Server.cpp : 服務(wù)端類(lèi)的實(shí)現(xiàn)

4.ClientMain.cpp ServerMain.cpp 客戶端及服務(wù)端的主函數(shù)。

七:代碼實(shí)現(xiàn)

Common.h

定義一些共用的宏定義,包括一些共用的網(wǎng)絡(luò)編程相關(guān)頭文件。

1)定義一個(gè)函數(shù)將文件描述符fd添加到epfd表示的內(nèi)核事件表中供客戶端和服務(wù)端兩個(gè)類(lèi)使用。

2)定義一個(gè)信息數(shù)據(jù)結(jié)構(gòu),用來(lái)表示傳送的信息,結(jié)構(gòu)體包括發(fā)送方fd, 接收方fd,用來(lái)表示消息類(lèi)別的type,還有文字信息。

函數(shù)recv() send() write() read() 參數(shù)傳遞是字符串,所以在傳送前/接受后要把結(jié)構(gòu)體轉(zhuǎn)換為字符串/字符串轉(zhuǎn)換為結(jié)構(gòu)體。

#ifndef CHATROOM_COMMON_H

#define CHATROOM_COMMON_H

 

#include <IOStream>

#include <list>

#include <sys/types.h>

#include <sys/socket.h>

#include <.NETinet/in.h>

#include <arpa/inet.h>

#include <sys/epoll.h>

#include <fcntl.h>

#include <errno.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

// 默認(rèn)服務(wù)器端IP地址

#define SERVER_IP "127.0.0.1"

 

// 服務(wù)器端口號(hào)

#define SERVER_PORT 8888

 

// int epoll_create(int size)中的size

// 為epoll支持的最大句柄數(shù)

#define EPOLL_SIZE 5000

 

// 緩沖區(qū)大小65535

#define BUF_SIZE 0xFFFF

 

// 新用戶登錄后的歡迎信息

#define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"

 

// 其他用戶收到消息的前綴

#define SERVER_MESSAGE "ClientID %d say >> %s"

#define SERVER_PRIVATE_MESSAGE "Client %d say to you privately >> %s"

#define SERVER_PRIVATE_ERROR_MESSAGE "Client %d is not in the chat room yet~"

// 退出系統(tǒng)

#define EXIT "EXIT"

 

// 提醒你是聊天室中唯一的客戶

#define CAUTION "There is only one int the char room!"

 

// 注冊(cè)新的fd到epollfd中

// 參數(shù)enable_et表示是否啟用ET模式,如果為T(mén)rue則啟用,否則使用LT模式

static void addfd( int epollfd, int fd, bool enable_et )

{

struct epoll_event ev;

ev.data.fd = fd;

ev.events = EPOLLIN;

if( enable_et )

ev.events = EPOLLIN | EPOLLET;

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

// 設(shè)置socket為非阻塞模式

// 套接字立刻返回,不管I/O是否完成,該函數(shù)所在的線程會(huì)繼續(xù)運(yùn)行

//eg. 在recv(fd...)時(shí),該函數(shù)立刻返回,在返回時(shí),內(nèi)核數(shù)據(jù)還沒(méi)準(zhǔn)備好會(huì)返回WSAEWOULDBLOCK錯(cuò)誤代碼

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

printf("fd added to epoll!nn");

}

 

//定義信息結(jié)構(gòu),在服務(wù)端和客戶端之間傳送

struct Msg

{

int type;

int fromID;

int toID;

char content[BUF_SIZE];

 

};

#endif // CHATROOM_COMMON_H

服務(wù)端類(lèi) Server.h Server.cpp

服務(wù)端需要的接口:

1)init()初始化

2)Start()啟動(dòng)服務(wù)

3)Close()關(guān)閉服務(wù)

4)廣播消息給所有客戶端函數(shù) SendBroadcastMessage()

服務(wù)端的主循環(huán)中每次都會(huì)檢查并處理EPOLL中的就緒事件,而就緒事件列表主要是兩種類(lèi)型:新連接或新消息。服務(wù)器會(huì)依次從就緒事件列表里提取事件進(jìn)行處理,如果是新連接則accept()然后addfd(),如果是新消息則SendBroadcastMessage()實(shí)現(xiàn)聊天功能。

Server.h

#ifndef CHATROOM_SERVER_H

#define CHATROOM_SERVER_H

 

#include <string>

 

#include "Common.h"

 

using namespace std;

 

// 服務(wù)端類(lèi),用來(lái)處理客戶端請(qǐng)求

class Server {

 

public:

// 無(wú)參數(shù)構(gòu)造函數(shù)

Server();

 

// 初始化服務(wù)器端設(shè)置

void Init();

 

// 關(guān)閉服務(wù)

void Close();

 

// 啟動(dòng)服務(wù)端

void Start();

 

private:

// 廣播消息給所有客戶端

int SendBroadcastMessage(int clientfd);

 

// 服務(wù)器端serverAddr信息

struct sockaddr_in serverAddr;

 

//創(chuàng)建監(jiān)聽(tīng)的socket

int listener;

 

// epoll_create創(chuàng)建后的返回值

int epfd;

 

// 客戶端列表

list<int> clients_list;

};

//Server.cpp

#include <iostream>

 

#include "Server.h"

 

using namespace std;

 

// 服務(wù)端類(lèi)成員函數(shù)

 

// 服務(wù)端類(lèi)構(gòu)造函數(shù)

Server::Server(){

 

// 初始化服務(wù)器地址和端口

serverAddr.sin_family = PF_INET;

serverAddr.sin_port = htons(SERVER_PORT);

serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

 

// 初始化socket

listener = 0;

 

// epool fd

epfd = 0;

}

// 初始化服務(wù)端并啟動(dòng)監(jiān)聽(tīng)

void Server::Init() {

cout << "Init Server..." << endl;

 

//創(chuàng)建監(jiān)聽(tīng)socket

listener = socket(PF_INET, SOCK_STREAM, 0);

if(listener < 0) { perror("listener"); exit(-1);}

 

//綁定地址

if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {

perror("bind error");

exit(-1);

}

 

//監(jiān)聽(tīng)

int ret = listen(listener, 5);

if(ret < 0) {

perror("listen error");

exit(-1);

}

 

cout << "Start to listen: " << SERVER_IP << endl;

 

//在內(nèi)核中創(chuàng)建事件表 epfd是一個(gè)句柄

epfd = epoll_create (EPOLL_SIZE);

 

if(epfd < 0) {

perror("epfd error");

exit(-1);

}

 

//往事件表里添加監(jiān)聽(tīng)事件

addfd(epfd, listener, true);

 

}

 

// 關(guān)閉服務(wù),清理并關(guān)閉文件描述符

void Server::Close() {

 

//關(guān)閉socket

close(listener);

 

//關(guān)閉epoll監(jiān)聽(tīng)

close(epfd);

}

 

// 發(fā)送廣播消息給所有客戶端

int Server::SendBroadcastMessage(int clientfd)

{

// buf[BUF_SIZE] 接收新消息

// message[BUF_SIZE] 保存格式化的消息

char recv_buf[BUF_SIZE];

char send_buf[BUF_SIZE];

Msg msg;

bzero(recv_buf, BUF_SIZE);

// 接收新消息

cout << "read from client(clientID = " << clientfd << ")" << endl;

int len = recv(clientfd, recv_buf, BUF_SIZE, 0);

//清空結(jié)構(gòu)體,把接受到的字符串轉(zhuǎn)換為結(jié)構(gòu)體

memset(&msg,0,sizeof(msg));

memcpy(&msg,recv_buf,sizeof(msg));

//判斷接收到的信息是私聊還是群聊

msg.fromID=clientfd;

if(msg.content[0]=='\'&&isdigit(msg.content[1])){

msg.type=1;

msg.toID=msg.content[1]-'0';

memcpy(msg.content,msg.content+2,sizeof(msg.content));

}

else

msg.type=0;

// 如果客戶端關(guān)閉了連接

if(len == 0)

{

close(clientfd);

 

// 在客戶端列表中刪除該客戶端

clients_list.remove(clientfd);

cout << "ClientID = " << clientfd

<< " closed.n now there are "

<< clients_list.size()

<< " client in the char room"

<< endl;

 

}

// 發(fā)送廣播消息給所有客戶端

else

{

// 判斷是否聊天室還有其他客戶端

if(clients_list.size() == 1){

// 發(fā)送提示消息

memcpy(&msg.content,CAUTION,sizeof(msg.content));

bzero(send_buf, BUF_SIZE);

memcpy(send_buf,&msg,sizeof(msg));

send(clientfd, send_buf, sizeof(send_buf), 0);

return len;

}

//存放格式化后的信息

char format_message[BUF_SIZE];

//群聊

if(msg.type==0){

// 格式化發(fā)送的消息內(nèi)容 #define SERVER_MESSAGE "ClientID %d say >> %s"

sprintf(format_message, SERVER_MESSAGE, clientfd, msg.content);

memcpy(msg.content,format_message,BUF_SIZE);

// 遍歷客戶端列表依次發(fā)送消息,需要判斷不要給來(lái)源客戶端發(fā)

list<int>::iterator it;

for(it = clients_list.begin(); it != clients_list.end(); ++it) {

if(*it != clientfd){

//把發(fā)送的結(jié)構(gòu)體轉(zhuǎn)換為字符串

bzero(send_buf, BUF_SIZE);

memcpy(send_buf,&msg,sizeof(msg));

if( send(*it,send_buf, sizeof(send_buf), 0) < 0 ) {

return -1;

}

}

}

}

//私聊

if(msg.type==1){

bool private_offline=true;

sprintf(format_message, SERVER_PRIVATE_MESSAGE, clientfd, msg.content);

memcpy(msg.content,format_message,BUF_SIZE);

// 遍歷客戶端列表依次發(fā)送消息,需要判斷不要給來(lái)源客戶端發(fā)

list<int>::iterator it;

for(it = clients_list.begin(); it != clients_list.end(); ++it) {

if(*it == msg.toID){

private_offline=false;

//把發(fā)送的結(jié)構(gòu)體轉(zhuǎn)換為字符串

bzero(send_buf, BUF_SIZE);

memcpy(send_buf,&msg,sizeof(msg));

if( send(*it,send_buf, sizeof(send_buf), 0) < 0 ) {

return -1;

}

}

}

//如果私聊對(duì)象不在線

if(private_offline){

sprintf(format_message,SERVER_PRIVATE_ERROR_MESSAGE,msg.toID);

memcpy(msg.content,format_message,BUF_SIZE);

bzero(send_buf,BUF_SIZE);

memcpy(send_buf,&msg,sizeof(msg));

if(send(msg.fromID,send_buf,sizeof(send_buf),0)<0)

return -1;

}

}

}

return len;

}

 

// 啟動(dòng)服務(wù)端

void Server::Start() {

 

// epoll 事件隊(duì)列

static struct epoll_event events[EPOLL_SIZE];

 

// 初始化服務(wù)端

Init();

 

//主循環(huán)

while(1)

{

//epoll_events_count表示就緒事件的數(shù)目

int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);

 

if(epoll_events_count < 0) {

perror("epoll failure");

break;

}

 

cout << "epoll_events_count =n" << epoll_events_count << endl;

 

//處理這epoll_events_count個(gè)就緒事件

for(int i = 0; i < epoll_events_count; ++i)

{

int sockfd = events[i].data.fd;

//新用戶連接

if(sockfd == listener)

{

struct sockaddr_in client_address;

socklen_t client_addrLength = sizeof(struct sockaddr_in);

int clientfd = accept( listener, ( struct sockaddr* )&client_address, &client_addrLength );

 

cout << "client connection from: "

<< inet_ntoa(client_address.sin_addr) << ":"

<< ntohs(client_address.sin_port) << ", clientfd = "

<< clientfd << endl;

 

addfd(epfd, clientfd, true);

 

// 服務(wù)端用list保存用戶連接

clients_list.push_back(clientfd);

cout << "Add new clientfd = " << clientfd << " to epoll" << endl;

cout << "Now there are " << clients_list.size() << " clients int the chat room" << endl;

 

// 服務(wù)端發(fā)送歡迎信息

cout << "welcome message" << endl;

char message[BUF_SIZE];

bzero(message, BUF_SIZE);

sprintf(message, SERVER_WELCOME, clientfd);

int ret = send(clientfd, message, BUF_SIZE, 0);

if(ret < 0) {

perror("send error");

Close();

exit(-1);

}

}

//處理用戶發(fā)來(lái)的消息,并廣播,使其他用戶收到信息

else {

int ret = SendBroadcastMessage(sockfd);

if(ret < 0) {

perror("error");

Close();

exit(-1);

}

}

}

}

 

// 關(guān)閉服務(wù)

Close();

}

客戶端類(lèi)實(shí)現(xiàn)

需要的接口:

1)連接服務(wù)端connect()

2)退出連接close()

3)啟動(dòng)客戶端Start()

Client.h

#ifndef CHATROOM_CLIENT_H

#define CHATROOM_CLIENT_H

 

#include <string>

#include "Common.h"

 

using namespace std;

 

// 客戶端類(lèi),用來(lái)連接服務(wù)器發(fā)送和接收消息

class Client {

 

public:

// 無(wú)參數(shù)構(gòu)造函數(shù)

Client();

 

// 連接服務(wù)器

void Connect();

 

// 斷開(kāi)連接

void Close();

 

// 啟動(dòng)客戶端

void Start();

 

private:

 

// 當(dāng)前連接服務(wù)器端創(chuàng)建的socket

int sock;

 

// 當(dāng)前進(jìn)程ID

int pid;

 

// epoll_create創(chuàng)建后的返回值

int epfd;

 

// 創(chuàng)建管道,其中fd[0]用于父進(jìn)程讀,fd[1]用于子進(jìn)程寫(xiě)

int pipe_fd[2];

 

// 表示客戶端是否正常工作

bool isClientwork;

 

// 聊天信息

Msg msg;

//結(jié)構(gòu)體要轉(zhuǎn)換為字符串

char send_buf[BUF_SIZE];

char recv_buf[BUF_SIZE];

//用戶連接的服務(wù)器 IP + port

struct sockaddr_in serverAddr;

};

Client.cpp

#include <iostream>

 

#include "Client.h"

 

using namespace std;

 

// 客戶端類(lèi)成員函數(shù)

 

// 客戶端類(lèi)構(gòu)造函數(shù)

Client::Client(){

 

// 初始化要連接的服務(wù)器地址和端口

serverAddr.sin_family = PF_INET;

serverAddr.sin_port = htons(SERVER_PORT);

serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

 

// 初始化socket

sock = 0;

 

// 初始化進(jìn)程號(hào)

pid = 0;

 

// 客戶端狀態(tài)

isClientwork = true;

 

// epool fd

epfd = 0;

}

 

// 連接服務(wù)器

void Client::Connect() {

cout << "Connect Server: " << SERVER_IP << " : " << SERVER_PORT << endl;

 

// 創(chuàng)建socket

sock = socket(PF_INET, SOCK_STREAM, 0);

if(sock < 0) {

perror("sock error");

exit(-1);

}

 

// 連接服務(wù)端

if(connect(sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {

perror("connect error");

exit(-1);

}

 

// 創(chuàng)建管道,其中fd[0]用于父進(jìn)程讀,fd[1]用于子進(jìn)程寫(xiě)

if(pipe(pipe_fd) < 0) {

perror("pipe error");

exit(-1);

}

 

// 創(chuàng)建epoll

epfd = epoll_create(EPOLL_SIZE);

 

if(epfd < 0) {

perror("epfd error");

exit(-1);

}

 

//將sock和管道讀端描述符都添加到內(nèi)核事件表中

addfd(epfd, sock, true);

addfd(epfd, pipe_fd[0], true);

 

}

 

// 斷開(kāi)連接,清理并關(guān)閉文件描述符

void Client::Close() {

 

if(pid){

//關(guān)閉父進(jìn)程的管道和sock

close(pipe_fd[0]);

close(sock);

}else{

//關(guān)閉子進(jìn)程的管道

close(pipe_fd[1]);

}

}

 

// 啟動(dòng)客戶端

void Client::Start() {

 

// epoll 事件隊(duì)列

static struct epoll_event events[2];

 

// 連接服務(wù)器

Connect();

 

// 創(chuàng)建的進(jìn)程

pid = fork();

 

// 如果創(chuàng)建的進(jìn)程失敗則退出

if(pid < 0) {

perror("fork error");

close(sock);

exit(-1);

} else if(pid == 0) {

// 進(jìn)入子進(jìn)程執(zhí)行流程

//子進(jìn)程負(fù)責(zé)寫(xiě)入管道,因此先關(guān)閉讀端

close(pipe_fd[0]);

 

// 輸入exit可以退出聊天室

cout << "Please input 'exit' to exit the chat room" << endl;

cout<<"\ + ClientID to private chat "<<endl;

// 如果客戶端運(yùn)行正常則不斷讀取輸入發(fā)送給服務(wù)端

while(isClientwork){

//清空結(jié)構(gòu)體

memset(msg.content,0,sizeof(msg.content));

fgets(msg.content, BUF_SIZE, stdin);

// 客戶輸出exit,退出

if(strncasecmp(msg.content, EXIT, strlen(EXIT)) == 0){

isClientwork = 0;

}

// 的進(jìn)程將信息寫(xiě)入管道

else {

//清空發(fā)送緩存

memset(send_buf,0,BUF_SIZE);

//結(jié)構(gòu)體轉(zhuǎn)換為字符串

memcpy(send_buf,&msg,sizeof(msg));

if( write(pipe_fd[1], send_buf, sizeof(send_buf)) < 0 ) {

perror("fork error");

exit(-1);

}

}

}

} else {

//pid > 0 父進(jìn)程

//父進(jìn)程負(fù)責(zé)讀管道數(shù)據(jù),因此先關(guān)閉寫(xiě)端

close(pipe_fd[1]);

 

// 主循環(huán)(epoll_wait)

while(isClientwork) {

int epoll_events_count = epoll_wait( epfd, events, 2, -1 );

 

//處理就緒事件

for(int i = 0; i < epoll_events_count ; ++i)

{

memset(recv_buf,0,sizeof(recv_buf));

//服務(wù)端發(fā)來(lái)消息

if(events[i].data.fd == sock)

{

//接受服務(wù)端廣播消息

int ret = recv(sock, recv_buf, BUF_SIZE, 0);

//清空結(jié)構(gòu)體

memset(&msg,0,sizeof(msg));

//將發(fā)來(lái)的消息轉(zhuǎn)換為結(jié)構(gòu)體

memcpy(&msg,recv_buf,sizeof(msg));

 

// ret= 0 服務(wù)端關(guān)閉

if(ret == 0) {

cout << "Server closed connection: " << sock << endl;

close(sock);

isClientwork = 0;

} else {

cout << msg.content << endl;

}

}

//子進(jìn)程寫(xiě)入事件發(fā)生,父進(jìn)程處理并發(fā)送服務(wù)端

else {

//父進(jìn)程從管道中讀取數(shù)據(jù)

int ret = read(events[i].data.fd, recv_buf, BUF_SIZE);

// ret = 0

if(ret == 0)

isClientwork = 0;

else {

// 將從管道中讀取的字符串信息發(fā)送給服務(wù)端

send(sock, recv_buf, sizeof(recv_buf), 0);

}

}

}//for

}//while

}

 

// 退出進(jìn)程

Close();

}

ClientMain.cpp

#include "Client.h"

 

// 客戶端主函數(shù)

// 創(chuàng)建客戶端對(duì)象后啟動(dòng)客戶端

int main(int argc, char *argv[]) {

Client client;

client.Start();

return 0;

}

ServerMain.cpp

#include "Server.h"

 

// 服務(wù)端主函數(shù)

// 創(chuàng)建服務(wù)端對(duì)象后啟動(dòng)服務(wù)端

int main(int argc, char *argv[]) {

Server server;

server.Start();

return 0;

}

最后是Makefile 文件 對(duì)上面的文件進(jìn)行編譯

CC = g++

CFLAGS = -std=c++11

 

all: ClientMain.cpp ServerMain.cpp Server.o Client.o

$(CC) $(CFLAGS) ServerMain.cpp Server.o -o chatroom_server

$(CC) $(CFLAGS) ClientMain.cpp Client.o -o chatroom_client

 

Server.o: Server.cpp Server.h Common.h

$(CC) $(CFLAGS) -c Server.cpp

 

Client.o: Client.cpp Client.h Common.h

$(CC) $(CFLAGS) -c Client.cpp

 

clean:

rm -f *.o chatroom_server chatroom_client

分享到:
標(biāo)簽:網(wǎng)絡(luò)編程 socket
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定