C++ socket网络编程——即时通信系统

转载:https://blog.csdn.net/lewis1993_cpapa/article/details/80589717

一:项目内容

本项目使用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模型

TCP服务端通信常规步骤:                                                                                                    

1.socket()创建TCP套接字                                                                              

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阻塞与非阻塞

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


   
   
  1. //将文件描述符设置为非阻塞方式(利用fcntl函数)
  2. 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的增强版本,其实现和使用方式与select/poll大有不同,epoll通过一组函数来完成有关任务,而不是一个函数。

2)epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核里的一个事件列表中,而不是像select/poll每次调用都需要重复传入文件描述符集或事件集(大量拷贝开销),比如一个事件发生,epoll无需遍历整个被监听的描述符集,而只需要遍历哪些被内核IO事件异步唤醒而加入就绪队列的描述符集合即可。

3)epoll有两种工作方式,LT(Level triggered) 水平触发 、ET(Edge triggered)边沿触发。LT是select/poll的工作方式,比较低效,而ET是epoll具有的高速工作方式。更多epoll之ET LT

Epoll 用法(三步曲):

第一步:int epoll_create(int size)系统调用,创建一个epoll句柄,参数size用来告诉内核监听的数目,size为epoll支持的最大句柄数。

第二步:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)  事件注册函数

参数 epfd为epoll的句柄。参数op 表示动作 三个宏来表示:EPOLL_CTL_ADD注册新fd到epfd 、EPOLL_CTL_MOD 修改已经注册的fd的监听事件、EPOLL_CTL_DEL从epfd句柄中删除fd。参数fd为需要监听的标识符。参数结构体epoll_event告诉内核需要监听的事件。

第三步:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 等待事件的产生,通过调用收集在epoll监控中已经发生的事件。参数struct epoll_event 是事件队列 把就绪的事件放进去。

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

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

2.然后将文件描述符(监听套接字listener)添加到事件表中

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

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

六:代码结构

每个文件的作用:

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

2.Client.h Client.cpp :客户端类的实现

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

4.ClientMain.cpp ServerMain.cpp 客户端及服务端的主函数。

七:代码实现

Common.h

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

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

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

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


   
   
  1. #ifndef CHATROOM_COMMON_H
  2. #define CHATROOM_COMMON_H
  3. #include <iostream>
  4. #include <list>
  5. #include <sys/types.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <sys/epoll.h>
  10. #include <fcntl.h>
  11. #include <errno.h>
  12. #include <unistd.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <string.h>
  16. // 默认服务器端IP地址
  17. #define SERVER_IP "127.0.0.1"
  18. // 服务器端口号
  19. #define SERVER_PORT 8888
  20. // int epoll_create(int size)中的size
  21. // 为epoll支持的最大句柄数
  22. #define EPOLL_SIZE 5000
  23. // 缓冲区大小65535
  24. #define BUF_SIZE 0xFFFF
  25. // 新用户登录后的欢迎信息
  26. #define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"
  27. // 其他用户收到消息的前缀
  28. #define SERVER_MESSAGE "ClientID %d say >> %s"
  29. #define SERVER_PRIVATE_MESSAGE "Client %d say to you privately >> %s"
  30. #define SERVER_PRIVATE_ERROR_MESSAGE "Client %d is not in the chat room yet~"
  31. // 退出系统
  32. #define EXIT "EXIT"
  33. // 提醒你是聊天室中唯一的客户
  34. #define CAUTION "There is only one int the char room!"
  35. // 注册新的fd到epollfd中
  36. // 参数enable_et表示是否启用ET模式,如果为True则启用,否则使用LT模式
  37. static void addfd( int epollfd, int fd, bool enable_et )
  38. {
  39. struct epoll_event ev;
  40. ev.data.fd = fd;
  41. ev.events = EPOLLIN;
  42. if( enable_et )
  43. ev.events = EPOLLIN | EPOLLET;
  44. epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
  45. // 设置socket为非阻塞模式
  46. // 套接字立刻返回,不管I/O是否完成,该函数所在的线程会继续运行
  47. //eg. 在recv(fd...)时,该函数立刻返回,在返回时,内核数据还没准备好会返回WSAEWOULDBLOCK错误代码
  48. fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0)| O_NONBLOCK);
  49. printf( "fd added to epoll!\n\n");
  50. }
  51. //定义信息结构,在服务端和客户端之间传送
  52. struct Msg
  53. {
  54. int type;
  55. int fromID;
  56. int toID;
  57. char content[BUF_SIZE];
  58. };
  59. #endif // CHATROOM_COMMON_H

服务端类 Server.h Server.cpp

服务端需要的接口:

1)init()初始化

2)Start()启动服务

3)Close()关闭服务

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

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

Server.h


   
   
  1. #ifndef CHATROOM_SERVER_H
  2. #define CHATROOM_SERVER_H
  3. #include <string>
  4. #include "Common.h"
  5. using namespace std;
  6. // 服务端类,用来处理客户端请求
  7. class Server {
  8. public:
  9. // 无参数构造函数
  10. Server();
  11. // 初始化服务器端设置
  12. void Init();
  13. // 关闭服务
  14. void Close();
  15. // 启动服务端
  16. void Start();
  17. private:
  18. // 广播消息给所有客户端
  19. int SendBroadcastMessage(int clientfd);
  20. // 服务器端serverAddr信息
  21. struct sockaddr_in serverAddr;
  22. //创建监听的socket
  23. int listener;
  24. // epoll_create创建后的返回值
  25. int epfd;
  26. // 客户端列表
  27. list< int> clients_list;
  28. };

   
   
  1. //Server.cpp
  2. #include <iostream>
  3. #include "Server.h"
  4. using namespace std;
  5. // 服务端类成员函数
  6. // 服务端类构造函数
  7. Server::Server(){
  8. // 初始化服务器地址和端口
  9. serverAddr.sin_family = PF_INET;
  10. serverAddr.sin_port = htons(SERVER_PORT);
  11. serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
  12. // 初始化socket
  13. listener = 0;
  14. // epool fd
  15. epfd = 0;
  16. }
  17. // 初始化服务端并启动监听
  18. void Server::Init() {
  19. cout << "Init Server..." << endl;
  20. //创建监听socket
  21. listener = socket(PF_INET, SOCK_STREAM, 0);
  22. if(listener < 0) { perror( "listener"); exit( -1);}
  23. //绑定地址
  24. if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
  25. perror( "bind error");
  26. exit( -1);
  27. }
  28. //监听
  29. int ret = listen(listener, 5);
  30. if(ret < 0) {
  31. perror( "listen error");
  32. exit( -1);
  33. }
  34. cout << "Start to listen: " << SERVER_IP << endl;
  35. //在内核中创建事件表 epfd是一个句柄
  36. epfd = epoll_create (EPOLL_SIZE);
  37. if(epfd < 0) {
  38. perror( "epfd error");
  39. exit( -1);
  40. }
  41. //往事件表里添加监听事件
  42. addfd(epfd, listener, true);
  43. }
  44. // 关闭服务,清理并关闭文件描述符
  45. void Server::Close() {
  46. //关闭socket
  47. close(listener);
  48. //关闭epoll监听
  49. close(epfd);
  50. }
  51. // 发送广播消息给所有客户端
  52. int Server::SendBroadcastMessage(int clientfd)
  53. {
  54. // buf[BUF_SIZE] 接收新消息
  55. // message[BUF_SIZE] 保存格式化的消息
  56. char recv_buf[BUF_SIZE];
  57. char send_buf[BUF_SIZE];
  58. Msg msg;
  59. bzero(recv_buf, BUF_SIZE);
  60. // 接收新消息
  61. cout << "read from client(clientID = " << clientfd << ")" << endl;
  62. int len = recv(clientfd, recv_buf, BUF_SIZE, 0);
  63. //清空结构体,把接受到的字符串转换为结构体
  64. memset(&msg, 0, sizeof(msg));
  65. memcpy(&msg,recv_buf, sizeof(msg));
  66. //判断接受到的信息是私聊还是群聊
  67. msg.fromID=clientfd;
  68. if(msg.content[ 0]== '\\'&& isdigit(msg.content[ 1])){
  69. msg.type= 1;
  70. msg.toID=msg.content[ 1]- '0';
  71. memcpy(msg.content,msg.content+ 2, sizeof(msg.content));
  72. }
  73. else
  74. msg.type= 0;
  75. // 如果客户端关闭了连接
  76. if(len == 0)
  77. {
  78. close(clientfd);
  79. // 在客户端列表中删除该客户端
  80. clients_list.remove(clientfd);
  81. cout << "ClientID = " << clientfd
  82. << " closed.\n now there are "
  83. << clients_list.size()
  84. << " client in the char room"
  85. << endl;
  86. }
  87. // 发送广播消息给所有客户端
  88. else
  89. {
  90. // 判断是否聊天室还有其他客户端
  91. if(clients_list.size() == 1){
  92. // 发送提示消息
  93. memcpy(&msg.content,CAUTION, sizeof(msg.content));
  94. bzero(send_buf, BUF_SIZE);
  95. memcpy(send_buf,&msg, sizeof(msg));
  96. send(clientfd, send_buf, sizeof(send_buf), 0);
  97. return len;
  98. }
  99. //存放格式化后的信息
  100. char format_message[BUF_SIZE];
  101. //群聊
  102. if(msg.type== 0){
  103. // 格式化发送的消息内容 #define SERVER_MESSAGE "ClientID %d say >> %s"
  104. sprintf(format_message, SERVER_MESSAGE, clientfd, msg.content);
  105. memcpy(msg.content,format_message,BUF_SIZE);
  106. // 遍历客户端列表依次发送消息,需要判断不要给来源客户端发
  107. list< int>::iterator it;
  108. for(it = clients_list.begin(); it != clients_list.end(); ++it) {
  109. if(*it != clientfd){
  110. //把发送的结构体转换为字符串
  111. bzero(send_buf, BUF_SIZE);
  112. memcpy(send_buf,&msg, sizeof(msg));
  113. if( send(*it,send_buf, sizeof(send_buf), 0) < 0 ) {
  114. return -1;
  115. }
  116. }
  117. }
  118. }
  119. //私聊
  120. if(msg.type== 1){
  121. bool private_offline= true;
  122. sprintf(format_message, SERVER_PRIVATE_MESSAGE, clientfd, msg.content);
  123. memcpy(msg.content,format_message,BUF_SIZE);
  124. // 遍历客户端列表依次发送消息,需要判断不要给来源客户端发
  125. list< int>::iterator it;
  126. for(it = clients_list.begin(); it != clients_list.end(); ++it) {
  127. if(*it == msg.toID){
  128. private_offline= false;
  129. //把发送的结构体转换为字符串
  130. bzero(send_buf, BUF_SIZE);
  131. memcpy(send_buf,&msg, sizeof(msg));
  132. if( send(*it,send_buf, sizeof(send_buf), 0) < 0 ) {
  133. return -1;
  134. }
  135. }
  136. }
  137. //如果私聊对象不在线
  138. if(private_offline){
  139. sprintf(format_message,SERVER_PRIVATE_ERROR_MESSAGE,msg.toID);
  140. memcpy(msg.content,format_message,BUF_SIZE);
  141. bzero(send_buf,BUF_SIZE);
  142. memcpy(send_buf,&msg, sizeof(msg));
  143. if(send(msg.fromID,send_buf, sizeof(send_buf), 0)< 0)
  144. return -1;
  145. }
  146. }
  147. }
  148. return len;
  149. }
  150. // 启动服务端
  151. void Server::Start() {
  152. // epoll 事件队列
  153. static struct epoll_event events[EPOLL_SIZE];
  154. // 初始化服务端
  155. Init();
  156. //主循环
  157. while( 1)
  158. {
  159. //epoll_events_count表示就绪事件的数目
  160. int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
  161. if(epoll_events_count < 0) {
  162. perror( "epoll failure");
  163. break;
  164. }
  165. cout << "epoll_events_count =\n" << epoll_events_count << endl;
  166. //处理这epoll_events_count个就绪事件
  167. for( int i = 0; i < epoll_events_count; ++i)
  168. {
  169. int sockfd = events[i].data.fd;
  170. //新用户连接
  171. if(sockfd == listener)
  172. {
  173. struct sockaddr_in client_address;
  174. socklen_t client_addrLength = sizeof(struct sockaddr_in);
  175. int clientfd = accept( listener, ( struct sockaddr* )&client_address, &client_addrLength );
  176. cout << "client connection from: "
  177. << inet_ntoa(client_address.sin_addr) << ":"
  178. << ntohs(client_address.sin_port) << ", clientfd = "
  179. << clientfd << endl;
  180. addfd(epfd, clientfd, true);
  181. // 服务端用list保存用户连接
  182. clients_list.push_back(clientfd);
  183. cout << "Add new clientfd = " << clientfd << " to epoll" << endl;
  184. cout << "Now there are " << clients_list.size() << " clients int the chat room" << endl;
  185. // 服务端发送欢迎信息
  186. cout << "welcome message" << endl;
  187. char message[BUF_SIZE];
  188. bzero(message, BUF_SIZE);
  189. sprintf(message, SERVER_WELCOME, clientfd);
  190. int ret = send(clientfd, message, BUF_SIZE, 0);
  191. if(ret < 0) {
  192. perror( "send error");
  193. Close();
  194. exit( -1);
  195. }
  196. }
  197. //处理用户发来的消息,并广播,使其他用户收到信息
  198. else {
  199. int ret = SendBroadcastMessage(sockfd);
  200. if(ret < 0) {
  201. perror( "error");
  202. Close();
  203. exit( -1);
  204. }
  205. }
  206. }
  207. }
  208. // 关闭服务
  209. Close();
  210. }

客户端类实现

需要的接口:

1)连接服务端connect()

2)退出连接close()

3)启动客户端Start()

Client.h


   
   
  1. #ifndef CHATROOM_CLIENT_H
  2. #define CHATROOM_CLIENT_H
  3. #include <string>
  4. #include "Common.h"
  5. using namespace std;
  6. // 客户端类,用来连接服务器发送和接收消息
  7. class Client {
  8. public:
  9. // 无参数构造函数
  10. Client();
  11. // 连接服务器
  12. void Connect();
  13. // 断开连接
  14. void Close();
  15. // 启动客户端
  16. void Start();
  17. private:
  18. // 当前连接服务器端创建的socket
  19. int sock;
  20. // 当前进程ID
  21. int pid;
  22. // epoll_create创建后的返回值
  23. int epfd;
  24. // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
  25. int pipe_fd[ 2];
  26. // 表示客户端是否正常工作
  27. bool isClientwork;
  28. // 聊天信息
  29. Msg msg;
  30. //结构体要转换为字符串
  31. char send_buf[BUF_SIZE];
  32. char recv_buf[BUF_SIZE];
  33. //用户连接的服务器 IP + port
  34. struct sockaddr_in serverAddr;
  35. };

Client.cpp


   
   
  1. #include <iostream>
  2. #include "Client.h"
  3. using namespace std;
  4. // 客户端类成员函数
  5. // 客户端类构造函数
  6. Client::Client(){
  7. // 初始化要连接的服务器地址和端口
  8. serverAddr.sin_family = PF_INET;
  9. serverAddr.sin_port = htons(SERVER_PORT);
  10. serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
  11. // 初始化socket
  12. sock = 0;
  13. // 初始化进程号
  14. pid = 0;
  15. // 客户端状态
  16. isClientwork = true;
  17. // epool fd
  18. epfd = 0;
  19. }
  20. // 连接服务器
  21. void Client::Connect() {
  22. cout << "Connect Server: " << SERVER_IP << " : " << SERVER_PORT << endl;
  23. // 创建socket
  24. sock = socket(PF_INET, SOCK_STREAM, 0);
  25. if(sock < 0) {
  26. perror( "sock error");
  27. exit( -1);
  28. }
  29. // 连接服务端
  30. if(connect(sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
  31. perror( "connect error");
  32. exit( -1);
  33. }
  34. // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
  35. if(pipe(pipe_fd) < 0) {
  36. perror( "pipe error");
  37. exit( -1);
  38. }
  39. // 创建epoll
  40. epfd = epoll_create(EPOLL_SIZE);
  41. if(epfd < 0) {
  42. perror( "epfd error");
  43. exit( -1);
  44. }
  45. //将sock和管道读端描述符都添加到内核事件表中
  46. addfd(epfd, sock, true);
  47. addfd(epfd, pipe_fd[ 0], true);
  48. }
  49. // 断开连接,清理并关闭文件描述符
  50. void Client::Close() {
  51. if(pid){
  52. //关闭父进程的管道和sock
  53. close(pipe_fd[ 0]);
  54. close(sock);
  55. } else{
  56. //关闭子进程的管道
  57. close(pipe_fd[ 1]);
  58. }
  59. }
  60. // 启动客户端
  61. void Client::Start() {
  62. // epoll 事件队列
  63. static struct epoll_event events[2];
  64. // 连接服务器
  65. Connect();
  66. // 创建子进程
  67. pid = fork();
  68. // 如果创建子进程失败则退出
  69. if(pid < 0) {
  70. perror( "fork error");
  71. close(sock);
  72. exit( -1);
  73. } else if(pid == 0) {
  74. // 进入子进程执行流程
  75. //子进程负责写入管道,因此先关闭读端
  76. close(pipe_fd[ 0]);
  77. // 输入exit可以退出聊天室
  78. cout << "Please input 'exit' to exit the chat room" << endl;
  79. cout<< "\\ + ClientID to private chat "<< endl;
  80. // 如果客户端运行正常则不断读取输入发送给服务端
  81. while(isClientwork){
  82. //清空结构体
  83. memset(msg.content, 0, sizeof(msg.content));
  84. fgets(msg.content, BUF_SIZE, stdin);
  85. // 客户输出exit,退出
  86. if(strncasecmp(msg.content, EXIT, strlen(EXIT)) == 0){
  87. isClientwork = 0;
  88. }
  89. // 子进程将信息写入管道
  90. else {
  91. //清空发送缓存
  92. memset(send_buf, 0,BUF_SIZE);
  93. //结构体转换为字符串
  94. memcpy(send_buf,&msg, sizeof(msg));
  95. if( write(pipe_fd[ 1], send_buf, sizeof(send_buf)) < 0 ) {
  96. perror( "fork error");
  97. exit( -1);
  98. }
  99. }
  100. }
  101. } else {
  102. //pid > 0 父进程
  103. //父进程负责读管道数据,因此先关闭写端
  104. close(pipe_fd[ 1]);
  105. // 主循环(epoll_wait)
  106. while(isClientwork) {
  107. int epoll_events_count = epoll_wait( epfd, events, 2, -1 );
  108. //处理就绪事件
  109. for( int i = 0; i < epoll_events_count ; ++i)
  110. {
  111. memset(recv_buf, 0, sizeof(recv_buf));
  112. //服务端发来消息
  113. if(events[i].data.fd == sock)
  114. {
  115. //接受服务端广播消息
  116. int ret = recv(sock, recv_buf, BUF_SIZE, 0);
  117. //清空结构体
  118. memset(&msg, 0, sizeof(msg));
  119. //将发来的消息转换为结构体
  120. memcpy(&msg,recv_buf, sizeof(msg));
  121. // ret= 0 服务端关闭
  122. if(ret == 0) {
  123. cout << "Server closed connection: " << sock << endl;
  124. close(sock);
  125. isClientwork = 0;
  126. } else {
  127. cout << msg.content << endl;
  128. }
  129. }
  130. //子进程写入事件发生,父进程处理并发送服务端
  131. else {
  132. //父进程从管道中读取数据
  133. int ret = read(events[i].data.fd, recv_buf, BUF_SIZE);
  134. // ret = 0
  135. if(ret == 0)
  136. isClientwork = 0;
  137. else {
  138. // 将从管道中读取的字符串信息发送给服务端
  139. send(sock, recv_buf, sizeof(recv_buf), 0);
  140. }
  141. }
  142. } //for
  143. } //while
  144. }
  145. // 退出进程
  146. Close();
  147. }

ClientMain.cpp


   
   
  1. #include "Client.h"
  2. // 客户端主函数
  3. // 创建客户端对象后启动客户端
  4. int main(int argc, char *argv[]) {
  5. Client client;
  6. client.Start();
  7. return 0;
  8. }

ServerMain.cpp


   
   
  1. #include "Server.h"
  2. // 服务端主函数
  3. // 创建服务端对象后启动服务端
  4. int main(int argc, char *argv[]) {
  5. Server server;
  6. server.Start();
  7. return 0;
  8. }

最后是Makefile 文件 对上面的文件进行编译


   
   
  1. CC = g++
  2. CFLAGS = - std=c++ 11
  3. all: ClientMain.cpp ServerMain.cpp Server.o Client.o
  4. $(CC) $(CFLAGS) ServerMain.cpp Server.o -o chatroom_server
  5. $(CC) $(CFLAGS) ClientMain.cpp Client.o -o chatroom_client
  6. Server.o: Server.cpp Server.h Common.h
  7. $(CC) $(CFLAGS) -c Server.cpp
  8. Client.o: Client.cpp Client.h Common.h
  9. $(CC) $(CFLAGS) -c Client.cpp
  10. clean:
  11. rm -f *.o chatroom_server chatroom_client

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴能够回答你的问题。以下是一个简单的即时通信系统C++网络编程示例,基于Linux平台使用socket库进行开发。 客户端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 int connect_result = connect(client_socket, (sockaddr *) &server_address, sizeof(server_address)); if (connect_result == -1) { cout << "连接服务器失败!" << endl; return -1; } // 发送消息 char message[1024] = {0}; while (true) { cin.getline(message, sizeof(message)); if (strlen(message) == 0) { continue; } send(client_socket, message, strlen(message), 0); memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); return 0; } ``` 服务器端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定socket和地址 bind(server_socket, (sockaddr *) &server_address, sizeof(server_address)); // 监听客户端连接 listen(server_socket, 10); // 接受客户端连接 sockaddr_in client_address{}; socklen_t client_address_len = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr *) &client_address, &client_address_len); // 接收消息 char message[1024] = {0}; while (true) { int recv_result = recv(client_socket, message, sizeof(message), 0); if (recv_result <= 0) { break; } cout << "收到消息:" << message << endl; memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); close(server_socket); return 0; } ``` 在上面的代码中,客户端和服务器端都使用了socket库中的函数来完成网络通信。客户端使用`socket`函数创建socket,使用`connect`函数连接服务器,并使用`send`函数发送消息。服务器端使用`socket`函数创建socket,使用`bind`函数将socket与服务器地址绑定,使用`listen`函数监听客户端连接,并使用`accept`函数接受客户端连接,最后使用`recv`函数接收客户端发送的消息。 当然,这只是一个简单的示例。在实际开发中,你需要考虑更多的细节,例如如何处理多个客户端连接、如何处理异常情况等等。 希望这个示例能够帮助你了解Linux下C++ socket网络编程的基本流程和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值