c++实现服务器和多个客户端的实时群聊通信
我们通过TCP/IP来实现多人聊天室,如果租一个服务器我们就可以实现全网的多人聊天室(不懂tcp/ip的点进来https://www.cnblogs.com/yskn/p/9335608.html)!首先我们要了解一下一些知识:
1.socket的IO操作:https://www.cnblogs.com/yskn/p/9355375.html
socket在连接后会有很多的发送和接收的消息如果我们采用阻塞的方式是很不方便的,我们肯本不知道用户会在什么时候进行这些操作,而socket的IO操作就相当于快递站一样加入有这样的消息,就会通知我们来取,在这里我们采用的select方式这种IO方式是通过不断的查询,下面是对select函数的参数解释,我们们可以吧我们关注的对象加入一个数组select会将有消息的自动筛选出来。
我们最主要采用的IO方式以下是对select函数的解释:
int select (
int nfds,
fd_set FAR * readfds,
fd_set FAR * writefds,
fd_set FAR * exceptfds,
const struct timeval FAR * timeout
);
第一个参数nfds沒有用,仅仅为与伯克利Socket兼容而提供。
readfds指定一個Socket数组(应该是一个,但这里主要是表现为一个Socket数组),select检查该数组中的所有Socket。如果成功返回,则readfds中存放的是符合‘可读性’条件的数组成员(如缓冲区中有可读的数据)。
writefds指定一个Socket数组,select检查该数组中的所有Socket。如果成功返回,则writefds中存放的是符合‘可写性’条件的数组成员(如连接成功)。
exceptfds指定一个Socket数组,select检查该数组中的所有Socket。如果成功返回,则cxceptfds中存放的是符合‘有异常’条件的数组成员(如连接接失败)。
timeout指定select执行的最长时间,如果在timeout限定的时间内,readfds、writefds、exceptfds中指定的Socket沒有一个符合要求,就返回0。
2.多线程操作:https://www.cnblogs.com/yskn/p/9355556.html
多线程的方式在网络通信中也是十分重要的这样我们才可以同时实现收发,或者是其他的一些功能。
下面就是代码部分绝对好用!
这里我们要注意的是如果我们采用的是阻塞的方式recv,那么一定同步的,假如我们再recv之前就已经send了,那么我们将永远不会受到之前的信息,将会永远的阻塞在这里,所以我建议当我们recv之前发送一个同步信号(自己设置)给服务器,服务器在发送就可以啦
客户端:
.cpp文件
- // win_clint.cpp: 定义控制台应用程序的入口点。
- //
- #include "public.h"
- int main()
- {
- client user;
- user.process();
- return 0;
- }
- client::client()
- {
- user = 0;
- writing = 0;
- serverAddr.sin_family = PF_INET;
- serverAddr.sin_port = SERVER_PORT;
- serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);//将字符串类型转换uint32_t
- }
- void client::init()
- {
- int Ret;
- WSADATA wsaData; // 用于初始化套接字环境
- // 初始化WinSock环境
- if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
- {
- printf("WSAStartup() failed with error %d\n", Ret);
- WSACleanup();
- }
- user= socket(AF_INET, SOCK_STREAM, 0);//采用ipv4,TCP传输
- if (user <= 0)
- {
- perror("establish client faild");
- printf("Error at socket(): %ld\n", WSAGetLastError());
- exit(1);
- };
- printf("establish succesfully\n");//创建成功
- //阻塞式的等待服务器连接
- if (connect(user, (const sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
- {
- perror("connect to server faild");
- printf("Error at socket(): %ld\n", WSAGetLastError());
- exit(1);
- }
- printf("connect IP:%s Port:%d succesfully\n", SERVER_IP, SERVER_PORT);//创建成功
- }
- void client::process()
- {
- char recvbuf[1024];
- fd_set fdread,fedwrite;
- FD_ZERO(&fdread);//将fds清零
- FD_ZERO(&fedwrite);//将fds清零
- init();
- while (1)
- {
- FD_SET(user, &fdread);
- if(writing==0) FD_SET(user, &fedwrite);
- struct timeval timeout = { 1,0 };//每个Select等待三秒
- switch (select(0, &fdread, &fedwrite, NULL, &timeout))
- {
- case -1:
- {
- //perror("select");
- printf("Error at socket(): %ld\n", WSAGetLastError());
- /*exit(1);*/
- break;
- }
- case 0:
- {
- //printf("select timeout......");
- break;
- }
- default:
- {
- if (FD_ISSET(user, &fdread))//则有读事件
- {
- int size = recv(user, recvbuf, sizeof(recvbuf) - 1, 0);
- if (size > 0)
- {
- printf("server:%s\n", recvbuf);
- memset(recvbuf, '\0', sizeof(recvbuf));
- }
- else if (size == 0)
- {
- printf("server is closed\n");
- exit(1);
- }
- }
- if (FD_ISSET(user, &fedwrite))
- {
- FD_ZERO(&fedwrite);//将fedwrite清零
- writing = 1;//表示正在写作
- thread sendtask(bind(&client::sendata, this));
- sendtask.detach();//将子线程和主进程分离且互相不影响
- }
- break;
- }
- }
- }
- }
- void client::sendata()
- {
- char sendbuf[1024];
- char middle[1024];
- cin.getline(sendbuf, 1024);//读取一行
- send(user, sendbuf, sizeof(sendbuf) - 1, 0);
- writing = 0;
- }
.h文件
- #ifndef PUBLIC_H
- #define PUBLIC_H
- //头文件引用
- #include<conio.h>
- #include <iostream>
- #include <thread>
- #include <winsock2.h>
- #include <stdio.h>
- #include<ws2tcpip.h>//定义socklen_t
- #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
- using namespace std;
- #define SERVER_IP "127.0.0.1"// 默认服务器端IP地址
- #define SERVER_PORT 8888// 服务器端口号
- class client
- {
- public:
- client();
- void init();
- void process();
- private:
- int user;
- int writing;
- sockaddr_in serverAddr;//IPV4的地址方式包括服务端地址族、服务端IP地址、服务端端口号
- void sendata();
- };
- #endif // !PUBLIC_H
服务端:
.cpp
- #include "public.h"
- int main()
- {
- server ser;
- ser.process();
- return 0;
- }
- server::server()
- {
- listener = 0;
- serverAddr.sin_family = PF_INET;
- serverAddr.sin_port = SERVER_PORT;
- serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);//将字符串类型转换uint32_t
- }
- //初始化函数,功能创建监听套接字,绑定端口,并进行监听
- void server::init()
- {
- int Ret;
- WSADATA wsaData; // 用于初始化套接字环境
- // 初始化WinSock环境
- if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
- {
- printf("WSAStartup() failed with error %d\n", Ret);
- WSACleanup();
- }
- listener = socket(AF_INET, SOCK_STREAM, 0);//采用ipv4,TCP传输
- if (listener == -1) { printf("Error at socket(): %ld\n", WSAGetLastError()); perror("listener failed"); exit(1); }
- printf("创建成功\n");
- unsigned long ul = 1;
- if (ioctlsocket(listener, FIONBIO, (unsigned long*)&ul) == -1) { perror("ioctl failed"); exit(1); };
- ///////////////////////////////////////////////////////////////////
- //中间的参数绑定的地址如果是IPV4则是///////////////////
- //struct sockaddr_in {
- // sa_family_t sin_family; /* address family: AF_INET */
- // in_port_t sin_port; /* port in network byte order */
- // struct in_addr sin_addr; /* internet address */
- //};
- //Internet address.
- //struct in_addr {
- // uint32_t s_addr; /* address in network byte order */
- //}
- /////////////////////////////////////////////////////////////////
- if (bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
- {
- perror("bind error");
- exit(1);
- }
- if (listen(listener, 6) < 0) { perror("listen failed"); exit(1); };
- socnum.push_back(listener);//将监听套接字加入
- }
- void server::process()
- {
- int mount = 0;
- fd_set fds;
- FD_ZERO(&fds);//将fds清零
- init();
- //下面就是不断的检查
- printf("正在等待中\n");
- while (1)
- {
- mount= socnum.size();
- //将fds每次都重新赋值
- for (int i = 0; i<mount; ++i)
- {
- FD_SET(socnum[i], &fds);
- }
- struct timeval timeout = { 1,0 };//每个Select等待三秒
- switch (select(0, &fds, NULL, NULL, &timeout))
- {
- case -1:
- {
- perror("select\n");
- printf("Error at socket(): %ld\n", WSAGetLastError());
- printf("%d\n",mount);
- /* for (int i = 0; i < mount; ++i)
- {
- printf("%d\n", socnum[i]);
- }*/
- Sleep(1000);
- break;
- }
- case 0:
- {
- //printf("select timeout......");
- break;
- }
- default:
- {
- //将数组中的每一个套接字都和剩余的额套接字进行比较得到当前的任务
- for (int i = 0; i < mount; ++i)
- {
- //如果第一个套接字可读的消息。就要建立连接
- if (i == 0 && FD_ISSET(socnum[i], &fds))
- {
- struct sockaddr_in client_address;
- socklen_t client_addrLength = sizeof(struct sockaddr_in);
- //返回一个用户的套接字
- int clientfd = accept(listener, (struct sockaddr*)&client_address, &client_addrLength);
- //添加用户,服务器上显示消息,并通知用户连接成功
- socnum.push_back(clientfd);
- printf("connect sucessfully\n");
- char ID[1024];
- sprintf(ID,"You ID is:%d", clientfd);
- char buf[30]="welcome to yskn's chatroom\n";
- strcat(ID,buf);
- send(clientfd, ID, sizeof(ID) - 1, 0);//减去最后一个'/0'
- }
- if (i != 0 && FD_ISSET(socnum[i], &fds))
- {
- char buf[1024];
- memset(buf, '\0', sizeof(buf));
- int size = recv(socnum[i], buf, sizeof(buf) - 1, 0);
- //检测是否断线
- if (size == 0 || size == -1)
- {
- printf("remote client close,size is%d\n", size);
- //closesocket(socnum[i]);//先关闭这个套接字
- FD_CLR(socnum[i], &fds);//在列表列表中删除
- socnum.erase(socnum.begin()+i);//在vector数组中删除
- }
- //若是没有掉线
- else
- {
- printf("clint %d says: %s \n", socnum[i], buf);
- //发送给每个用户
- for (int j = 1; j < mount; j++)
- {
- char client[1024];
- sprintf(client,"client %d:", socnum[i]);
- strcat(client, buf);
- send(socnum[j], client, sizeof(client) - 1, 0);//如果
- }
- }
- }
- }
- break;
- }
- }
- }
- }
.h文件
- #ifndef PUBLIC_H
- #define PUBLIC_H
- //头文件引用
- #include <winsock2.h>
- #include <stdio.h>
- #include <vector>
- #include<ws2tcpip.h>//定义socklen_t
- using namespace std;
- #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
- #define SERVER_IP "127.0.0.1"// 默认服务器端IP地址
- #define SERVER_PORT 8888// 服务器端口号
- class server
- {
- public:
- server();
- void init();
- void process();
- private:
- int listener;//监听套接字
- sockaddr_in serverAddr;//IPV4的地址方式
- vector <int> socnum;//存放创建的套接字
- };
- #endif // !PUBLIC_H
c++实现服务器和多个客户端的实时群聊通信的更多相关文章
- Dynamics AX 2012 R3 Demo 安装与配置 - 安装数据服务器、AOS和客户端 (Step 2)
上一节中,Reinhard主要讲解了怎么配置安装环境,尤其是域控制器,并在域中添加了一个管理员账户 MSDynAX.NET\Reinhard ,以后的安装配置,均在该账户下进行. 现在运行 AX 20 ...
- java19 先开服务器,再开客户端
先开服务器,再开客户端. import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOExcep ...
- Windows Socket 编程_单个服务器对多个客户端简单通讯
单个服务器对多个客户端程序: 一.简要说明 二.查看效果 三.编写思路 四.程序源代码 五.存在问题 一.简要说明: 程序名为:TcpSocketOneServerToMulClient 程序功能:实 ...
- Cas 服务器 Service(Cas客户端)注册信息维护
作为Cas服务器,允许哪些客户端接入与否是通过配置来定义的.对Cas服务器来说,每一个接入的客户端与一个Service配置对应:在Cas服务器启动时加载并注册上这些Service,与之对应的客户端才能 ...
- 伯克利SocketAPI(一) socket的C语言接口/最简单的服务器和对应的客户端C语言实现
1. 头文件 2. API函数 3. 最简单的服务器和对应的客户端C语言实现 3.1 server #include <sys/types.h> #include <sys/sock ...
- node.js中express模块创建服务器和http模块客户端发请求
首先下载express模块,命令行输入 npm install express 1.node.js中express模块创建服务端 在js代码同文件位置新建一个文件夹(www_root),里面存放网页文 ...
- nodejs 服务器实现区分多客户端请求服务
初始实现 var net = require('net');//1 引入net模块 var chatServer = net.createServer();//创建net服务器 var clientL ...
- Unix lrzsz命令 上传本地文件到服务器 / 发送文件到客户端
第三方教程:https://www.jb51.net/article/73690.htm 安装命令: $ yum install lrzsz 本地上传文件到服务器,如果是xshell,直接拖拽文件进入 ...
- SVN入门 服务器VisualSVN Server和客户端TortoiseSVN安装
Subversion是一个版本控制系统,相对于的RCS.CVS,采用了分支管理系统,它的设计目标就是取代CVS.互联网上免费的版本控制服务多基于Subversion. 一.SVN工作原理 SVN(Su ...
随机推荐
- [idea]添加jar包的方法
一:在项目的根目录下建立lib文件夹,然后将对应的jar包文件拷贝进去. 二:点击项目右键,选择Open Module Settings 三.选择Project Settings->Librar ...
- linux常用命令(8)cat命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 1 命令格式:cat [选项] [文件]. ...
- 关于Yii的ocracle链接问题
1. http://www.yiiframework.com/extension/oci8pdo/ 2.下载extension包,根据配置可解决.
- 如何为根分区扩容(centos7为例)
linux系统所有的文件都是存放在根分区中的,如果根分区容量即将耗尽,我们就需要给根分区扩容,我们可以使用lsblk命令来查看,系统的根分区实际是逻辑卷,所以想要扩展根分区只要将逻辑卷扩容就可以了.此 ...
- 关于Pytorch中accuracy和loss的计算
这几天关于accuracy和loss的计算有一些疑惑,原来是自己还没有弄清楚. 给出实例 def train(train_loader, model, criteon, optimizer, epoc ...
- elasticsearch的cross_fields查询
1.most_fields 这种方式搜索也存在某些问题 它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应. 2.词 peter 和 smi ...
- 2 基于梯度的攻击——PGD
PGD攻击原论文地址——https://arxiv.org/pdf/1706.06083.pdf 1.PGD攻击的原理 PGD(Project Gradient Descent)攻击是一种迭代攻击,可 ...
- subquery 子查询
#encoding: utf-8 from sqlalchemy import create_engine,Column,Integer,String,Float,func,and_,or_,Enum ...
- Laravel模板事项
1.模板中己显示的时间,可以在此基础上增加时间 请于{{ $order->created_at->addSeconds(config('app.order_ttl'))->forma ...
- Luogu P2569 [SCOI2010] 股票交易
此题链接到dp常见优化方法 开始的时候被纪念品误导,以为是多支股票,后来发现事情不妙: 这道题知道的是某一只股票的走势: \(Solution\): \(70pts\): 设\(f[i][j]\)表示 ...