上面是三人聊天室的终端实现,也可以加界面做成客户端形式,用云服务器可以实现qq的随发随用功能,采用的是C/S架构。目前BUG:超过两人必须同时开启,不然后面的人终端显示不停输出换行;关闭一人后其他人出现终端显示不停输出换行。代码展示:
客户端:
/*要在win上使用socket需要进行相应的配置*/
#include<bits/stdc++.h>
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<Windows.h>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
#define BUF_SIZE 1024
char buf[BUF_SIZE];
unsigned sendmsg(void* arg) {
SOCKET sock = *((SOCKET*)arg);
while (1) {
scanf("%s", buf);
if (!strcmp(buf, "Q\n") || !strcmp(buf, "q\n"))
{
closesocket(sock);
exit(1);
}
send(sock, buf, strlen(buf), 0);
}
return 0;
}
unsigned recvmsg(void* arg) {
SOCKET sock = *((SOCKET*)arg);
char msg[BUF_SIZE];
while (1) {
int len = recv(sock, msg, sizeof msg - 1, 0);
if (len == -1) //服务器挂了
return -1;
msg[len] = '\0';
printf("%s\n", msg);
}
return 0;
}
int main() {
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return -1;
}
//创建socket
SOCKET hsock;
hsock = socket(AF_INET, SOCK_STREAM, 0);
//bind
SOCKADDR_IN s_r;
//memset(&s_r, 0, sizeof s_r);
s_r.sin_family = AF_INET;
s_r.sin_port =htons(6665);
inet_pton(AF_INET, "192.168.17.132", &s_r.sin_addr);
//connect
connect(hsock, (sockaddr*)&s_r, sizeof s_r);
cout << "多人聊天室 启动!! --注:服务器部署在linux上 请输入你的名字"<<endl;
//开两个线程进行发消息和收消息
//循环发消息
HANDLE s_hand=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendmsg, (void*)&hsock,0,NULL);
//循环收消息
HANDLE r_hand=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvmsg, (void*)&hsock, 0, NULL);
//等待两个线程结束
WaitForSingleObject(s_hand, INFINITE); //无限进行检测
WaitForSingleObject(r_hand, INFINITE); //当前线程将等待 s_hand 对象变为可用,而且这个等待时间是无限长的,直到对象可用为止。
closesocket(hsock);
WSACleanup(); //关掉环境
}
服务器端:
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<map>
#include<arpa/inet.h>
#include"./ser/s.h"
#define PORT 1956
#define MAX_CLIENTS 1024
using namespace std;
typedef struct Client {
int sd;
std::string name;
}c;
std::map<int, c> cs;
int main(int argc,char ** argv) {
int l_sd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in laddr;
laddr.sin_family=AF_INET;
laddr.sin_port=htons(atoi(argv[1]));
laddr.sin_addr.s_addr=INADDR_ANY;
bind(l_sd,(struct sockaddr*)&laddr,(socklen_t)sizeof laddr);
listen(l_sd,5);
cout<<"Runing..."<<endl;
struct epoll_event ev,evs[MAX_CLIENTS];//进行epoll_event的变量的初始化和其数组的初始化,数组只是用来隐含地返回发生的时间的描述符 只有在epoll_wait时才会用到数组 epoll_wait(int epfd,struct epoll_event* evs,int max_evs,int time_out)
ev.data.fd=l_sd;//进行epoll_event变量的赋值 监听的对象,监听对象的监听的事件 l_sd EPOLLIN 可读事件
ev.events=EPOLLIN;
struct epoll_event mid_ev;//临时变量epoll_evennt其实也可以不需要 因为用ev就行了 目的就是添加到epoll描述符(epoll_create 返回值)中
int e_fd=epoll_create(2);//epoll描述符
epoll_ctl(e_fd,EPOLL_CTL_ADD,l_sd,&ev); //增加监听的对象
while(true){
cout<<"Ready for waiting!"<<endl;
int num=epoll_wait(e_fd,evs,MAX_CLIENTS,-1);//阻塞等待
cout<<"wait is over."<<endl;
for(int i=0;i<num;++i){
int mid_fd=evs[i].data.fd;
if(evs[i].data.fd==l_sd){//是监听的对象改变了 说明有新的连接
struct sockaddr_in raddr;
socklen_t len;
int new_sd=accept(l_sd,(struct sockaddr*)&raddr,&len);
char str[1024];
inet_ntop(AF_INET,&raddr.sin_addr,str,sizeof str);
cout<<"connected by"<<str<<" port:"<<ntohs(raddr.sin_port)<<endl;
mid_ev.data.fd=new_sd;//将连接的描述符进行监视对象的初始化
mid_ev.events=EPOLLIN;
epoll_ctl(e_fd,EPOLL_CTL_ADD,new_sd,&mid_ev);//将连接的描述符进行添加到监视队列中
c c_d;
c_d.sd = new_sd;
c_d.name = " ";
cs[c_d.sd] =c_d;
}
else{
cout << "Having msg..." << endl;
char buf[1024];
int n = read(mid_fd, buf, 1024);
if (n == 0) {
close(mid_fd);
epoll_ctl(e_fd, EPOLL_CTL_DEL, mid_fd,0);
cs.erase(mid_fd);
}
else
{
std::string msg(buf, n);
if (cs[mid_fd].name == " ") {
cs[mid_fd].name = msg;
}
else { //speak message
std::string name = cs[mid_fd].name;
//send the msg to others all
for (auto& x : cs) {
if (x.first != mid_fd) {
write(x.first, ('[' + name + ']' + ": " + msg).c_str(),msg.size() + sizeof name + 4);
}
}
}
}
}
}
}
}