我们通过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++实现服务器和多个客户端的实时群聊通信的更多相关文章

  1. Dynamics AX 2012 R3 Demo 安装与配置 - 安装数据服务器、AOS和客户端 (Step 2)

    上一节中,Reinhard主要讲解了怎么配置安装环境,尤其是域控制器,并在域中添加了一个管理员账户 MSDynAX.NET\Reinhard ,以后的安装配置,均在该账户下进行. 现在运行 AX 20 ...

  2. java19 先开服务器,再开客户端

    先开服务器,再开客户端. import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOExcep ...

  3. Windows Socket 编程_单个服务器对多个客户端简单通讯

    单个服务器对多个客户端程序: 一.简要说明 二.查看效果 三.编写思路 四.程序源代码 五.存在问题 一.简要说明: 程序名为:TcpSocketOneServerToMulClient 程序功能:实 ...

  4. Cas 服务器 Service(Cas客户端)注册信息维护

    作为Cas服务器,允许哪些客户端接入与否是通过配置来定义的.对Cas服务器来说,每一个接入的客户端与一个Service配置对应:在Cas服务器启动时加载并注册上这些Service,与之对应的客户端才能 ...

  5. 伯克利SocketAPI(一) socket的C语言接口/最简单的服务器和对应的客户端C语言实现

    1. 头文件 2. API函数 3. 最简单的服务器和对应的客户端C语言实现 3.1 server #include <sys/types.h> #include <sys/sock ...

  6. node.js中express模块创建服务器和http模块客户端发请求

    首先下载express模块,命令行输入 npm install express 1.node.js中express模块创建服务端 在js代码同文件位置新建一个文件夹(www_root),里面存放网页文 ...

  7. nodejs 服务器实现区分多客户端请求服务

    初始实现 var net = require('net');//1 引入net模块 var chatServer = net.createServer();//创建net服务器 var clientL ...

  8. Unix lrzsz命令 上传本地文件到服务器 / 发送文件到客户端

    第三方教程:https://www.jb51.net/article/73690.htm 安装命令: $ yum install lrzsz 本地上传文件到服务器,如果是xshell,直接拖拽文件进入 ...

  9. SVN入门 服务器VisualSVN Server和客户端TortoiseSVN安装

    Subversion是一个版本控制系统,相对于的RCS.CVS,采用了分支管理系统,它的设计目标就是取代CVS.互联网上免费的版本控制服务多基于Subversion. 一.SVN工作原理 SVN(Su ...

随机推荐

  1. WPF 键盘全局接收消息

    1.========================================================================== 在c#中怎样禁用鼠标左键的使用,其实我们可以通 ...

  2. C基础知识(6):指针--函数指针与回调涵数

    函数指针 函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调用函数.传递参数. #include < ...

  3. 刷新页面后,让控制台的js代码继续执行

    在各种限时,秒杀活动中,有个自动循环的点击的工具是很重要的. 为了方便起见,我们把Js代码放在浏览器的控制台执行,但是刷新页面后,js代码就清空了,也就无法执行. 可以用js代码实现一个不受页面刷新影 ...

  4. jvm学习笔记:一、类的加载、连接、初始化

    在JAVA代码中,类型的加载.连接与初始化过程都是程序运行期间完成的. 类型的加载:将已经存在的class从硬盘加载到内存. 类型的连接:将类与类之间的关系确定好. 类型的初始化:类型 静态的变量进行 ...

  5. 我和CMS的往事

    CMS(内容管理系统)放在web中理解,也就是我们常说的后台了,用于网站的日常管理.又到期末了,我们的课程——web程序设计,需要提交一份期末大作业,最近需要开发出一个基于JSP的简单web,所以呢, ...

  6. aliyun挂载oss

    配置 oss 挂载 阿里云 ecs 按照ossfs工具:yum install http://gosspublic.alicdn.com/ossfs/ossfs_1.80.5_centos6.5_x8 ...

  7. 【Python】【demo实验2】【打印乘法口诀表】

    打印乘法口诀表 源代码: # encoding=utf-8 for i in range(1,10): print("\n") for j in range(1,10): if i ...

  8. p1000 A+B问题

    题目描述 Description 输入两个整数A和B,输出他们的和 输入描述 Input Description 输入为一行,包含两个整数A,B.数据保证A与B都在2^31-1的范围内 输出描述 Ou ...

  9. 小记---------CDH版大数据组件--clouderManager UI界面

    启动 /opt/cm-5.14.0/etc/init.d/clouder-scm-server start /opt/cm-5.14.0/etc/init.d/clouder-scm-agent st ...

  10. mysql连接数据库时报2003错误怎么解决

    mysql 2003是连接错误,连不上服务器. 你目前可以如下方法:进入控制面板->服务管理(我的是管理工具),->服务,然后找到Mysql服务,右键修改属性,改为自启动,以后再重启就没有 ...