简易聊天室
使用一台服务器做服务端,支持多台服务器进行连接,并发言与接收消息
效果展示
客户端效果:
两个进程通过服务器交流
代码:
Serve.cpp
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <unistd.h>
#include <set>
#include <map>
#include <iostream>
#define PORT 12344 //端口号
#define SIP "192.168.0.231" // 替换为服务器的本地ip
int sockfd;
std::set<int> cfds;
char buf[1024];
char sbuf[2048];
std::map<int, std::string> mp;
// 将信息对除发送方外所有连接对象发送
void fa(int cf)
{
for (int t : cfds)
{
if (t != cf)
{
sprintf(sbuf, "%s:%s", mp[cf].c_str(), buf);
send(t, sbuf, strlen(sbuf), 0);
}
}
printf("发送完毕\n");
}
// 接受链接,并记录链接方名字
void acc()
{
struct sockaddr_in caddr;
int len;
int cfd = accept(sockfd, (struct sockaddr *)&caddr, (socklen_t *)&len);
printf("cfd:%d\n", cfd);
if (cfd != -1)
{
cfds.insert(cfd);
}
int r = recv(cfd, buf, 1024, 0);
buf[r] = 0;
std::string name(buf);
mp[cfd] = name;
std::cout << name << "\n";
}
int main()
{
sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sockfd:%d\n", sockfd);
int r;
struct sockaddr_in Saddr;
Saddr.sin_family = AF_INET;
Saddr.sin_port = htons(PORT);
Saddr.sin_addr.s_addr = inet_addr(SIP);
r = bind(sockfd, (struct sockaddr *)&Saddr, sizeof(Saddr));
printf("bind:%d\n", r);
if (r == -1)
{
printf("%m\n");
exit(0);
}
r = listen(sockfd, 10);
printf("listen:%d\n", r);
fd_set rst;
int mxfds;
while (1)
{
// 将所有描述符放入集合
FD_ZERO(&rst);
FD_SET(sockfd, &rst);
mxfds = sockfd;
for (int cfd : cfds)
{
printf("%d ", cfd);
FD_SET(cfd, &rst);
mxfds = mxfds > cfd ? mxfds : cfd;
}
// 当前连接的客户端数量
printf("\n==========%d\n", (int)cfds.size());
// io多路复用,等到集合中的一个活动对象
int reselect = select(mxfds + 1, &rst, NULL, NULL, NULL);
printf("select:%d\n", reselect);
// sockfd初相变动新连接
if (FD_ISSET(sockfd, &rst))
{
printf("新连接\n");
acc();
}
else
{
// 新消息
printf("新消息\n");
for (int cfdd : cfds)
{
if (FD_ISSET(cfdd, &rst))
{
r = recv(cfdd, buf, 1024, 0);
if (r <= 0)
{
printf("断开连接\n");
close(cfdd);
cfds.erase(cfdd);
FD_CLR(cfdd, &rst);
break;
}
buf[r] = 0;
printf("C:%s", buf);
// 调用函数,对除此连接外所有链接发送消息
fa(cfdd);
}
}
}
}
return 0;
}
Clint.cpp
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <unistd.h>
#include <signal.h>
int sockfd;
#define PORT 12344 //端口号,与客户端一致
#define SIP "11.11.11.11"
/*
SIP: 服务端公网ip,若在局域网内可为服务端本机IP
*/
// Ctrl+C 关闭连接,结束程序
void hand(int n)
{
printf("bye!\n");
close(sockfd);
sleep(1);
exit(0);
}
int main()
{
// 创建socket套接字,采用TCP通信
sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sockfd:%d\n", sockfd);
int r;
struct sockaddr_in Saddr;
Saddr.sin_family = AF_INET;
Saddr.sin_port = htons(PORT);
Saddr.sin_addr.s_addr = inet_addr(SIP);
char buf[1024];
printf("输入你的名字:");
scanf("%s", buf);
r = connect(sockfd, (struct sockaddr *)&Saddr, sizeof Saddr);
if (r == -1)
{
printf("error%m\n");
exit(0);
}
printf("连接成功\n");
signal(2, hand);
send(sockfd, buf, strlen(buf), 0);
fd_set rst;
while (1)
{
FD_ZERO(&rst);
// 标准输入设备
FD_SET(0, &rst);
// 网络socket输入
FD_SET(sockfd, &rst);
// io多路复用,当标准输入设备和socket有一端有数据时结束阻塞
r = select(sockfd + 1, &rst, NULL, NULL, NULL);
// 标准输入设备出现输入
if (FD_ISSET(0, &rst))
{
scanf("%s", buf);
r = send(sockfd, buf, strlen(buf), 0);
// sleep(1);
// printf("sendr:%s %d\n", buf, r);
}
// socket出现输入
else if (FD_ISSET(sockfd, &rst))
{
// 接收并输出得到的数据
r = recv(sockfd, buf, 1024, 0);
buf[r] = 0;
if (r > 0)
{
printf("%s\n", buf);
}
}
else
{
printf("====error====%m");
}
}
}
编译指令
//服务端
g++ Serve.cpp -o Serve
客户端
g++ Clint.cpp -o Clint