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 ...
随机推荐
- OpenStack 虚拟机启动流程 UML 分析(内含 UML 源码)
目录 文章目录 目录 前言 API 请求 Nova API 阶段 Nova Conductor 阶段 Nova Scheduler 阶段 Nova Compute 阶段(计算节点资源分配部分) Nov ...
- js中||与&&的用法
|| 或 &&与常用法 func a()和func b() a() && b() 如果a执行成功返回true则执行b返回b结果的值,如果a执行返回false则不执行b ...
- Eclipse使用git发布项目到github
因为一直都在使用svn,今天尝试了下git,记录下来既是方便自己以后查看,也是分享一些经验! 废话不多说,撸起袖子就是干!!! 1.选中要上传的项目右键 2.选中git 3.在图上打钩,点击所要上传的 ...
- Python中的变量和作用域详解
Python中的变量和作用域详解 python中的作用域分4种情况: L:local,局部作用域,即函数中定义的变量: E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部 ...
- POJO是什么,javabean是什么,以及POJO与javabean的区别
POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称.使用POJO名称是为了避免和EJB混淆起来, 而 ...
- 网络实验 04-利用三层交换机实现VLAN间路由
利用三层交换机实现VLAN间路由 一.实验目标 掌握交换机Tag VLAN 的配置 掌握三层交换机基本配置方法 掌握三层交换机VLAN路由的配置方法 通过三层交换机实现VLAN间相互通信 二.实验背景 ...
- 【学习笔记】python3中yaml文件使用
1.yaml -> 字典:用yaml.load()或yaml.safe_load(YAML字符串或文件句柄),如yaml中有中文,可以使用.encode('utf-8')或打开文件时指定enco ...
- Zookeeper入门(一)
Zookeeper是分布式服务治理中间件 一.Zookeeper的简介 官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用 ...
- 获取JSON中所有的KEY
采用递归的方式,遍历JSON中所有的KEY. JSON格式如下: {"username":"tom","age":18,"addr ...
- 菜鸟系列k8s——k8s集群部署(2)
k8s集群部署 1. 角色分配 角色 IP 安装组件 k8s-master 10.0.0.170 kube-apiserver,kube-controller-manager,kube-schedul ...