我们通过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. java线程锁之synchronized

    声明:该博客参考https://www.cnblogs.com/kaituorensheng/p/10079916.html,感谢哥们. 1.Sync.java package com.cn.comm ...

  2. 【汇总】数据库提权(mysql、mssql)

    日期:2018-04-03 11:46:45 作者:Bay0net 介绍:利用 mssql 的 sa 账号提权.利用 MySQL 的 UDF 提权 0x01.mssql 提权 恢复 xp_cmdshe ...

  3. 1-2、kubernetes架构概述和kubernetes基础概念

    kubernetes https://draveness.me/understanding-kubernetes http://kubernetes.kansea.com/docs/ master/n ...

  4. Spring Data JPA 查询

    1 按照关键字方法名查询 自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findBy,readABy,queryBy,getBy,countBy后面跟属性名称 findBy,readAB ...

  5. Xcode真机报错clang: error: linker command failed with exit code 1 (use -v to see invocation)

    出现这种错误,如下图所示,搜索bitcode,置为NO即可.

  6. Web测试方法_02

    1.页面链接检查 检查每一个链接是否都有对应的页面,页面与页面之间的来回切换是否正常响应,包括一些返回页面的链接是否正常,还要检查点击图片所链接的页面是否准确展示. 2.相关性检查 功能相关性检查:例 ...

  7. 如何在WIN7下安装虚拟机linux系统

    需要支持多个平台的IT管理员经常会遇到如何在Windows 7计算机上安装Linux的问题.幸运的是有多种方法可供选择:双系统.Linux虚拟机和U盘引导. 当需要用到Windows 7和Linux时 ...

  8. 5-1 标准I/O和管道

    标准I/O和管道 程序:指令+数据 读入数据:Input 输出数据:Output 打开的文件都有一个fd: file descriptor (文件描述符) Linux给程序提供三种 I/O 设备 标准 ...

  9. 【Python开发】python读写文件,和设置文件的字符编码比如utf-8

    一. python打开文件代码如下: f = open("d:\test.txt", "w") 说明: 第一个参数是文件名称,包括路径: 第二个参数是打开的模式 ...

  10. 前端 api 请求缓存方案

    参考链接:https://blog.csdn.net/zhuoganliwanjin/article/details/89598753#commentBox