2024-05-27 更新
- 在这个程序中有些未定义行为,导致有的编译器运行过程中出现create后服务器没有收到消息。如评论所说。现已修复。
- 打开多个客户端可以在你创建的项目中找到 .exe可执行程序,多次运行就可以打开多个客户端。
- 极简版删除
- 若是用clion编辑器,使用cmake构建项目,在CMakeLists.txt文件中添加
link_libraries(ws2_32)
如下:
cmake_minimum_required(VERSION 3.14)
project(项目名)
link_libraries(ws2_32)
set(CMAKE_CXX_STANDARD 标准)
add_executable(项目名 main.cpp
1.cpp)
代码很乱,不建议仔细研究,需要讲解可留评论。
运行截图:
Server:
#include<iostream>
#include<winsock2.h>
#include<process.h>
#include<windows.h>
#include<vector>
#include<thread>
#include<set>
#pragma comment(lib,"ws2_32.lib")
#define PORT 65432 //定义要访问的服务器端口常量
#define MSG_SHOW 1
#define MSG_CREATE 2
#define MSG_JOIN 3
#define MSG_LEAVE 4
#define MSG_QUIT 5
#define MSG_EXIT 5
#define MSG_TALK 6
//#pragma comment(lib,"ws2_32.lib")
using namespace std;
int writefile(char* num, HANDLE fileHandle);
DWORD d1 = 0;
class MsgHead {
public:
int msgType;
int dataLength;
};
class MsgShow :public MsgHead
{
public:
MsgShow() {
msgType = MSG_SHOW;
dataLength = sizeof(MsgShow);
}
};
class MsgCreate :public MsgHead
{
public:
MsgCreate() {
msgType = MSG_CREATE;
dataLength = sizeof(MsgCreate);
}
};
class MsgJoin :public MsgHead
{
public:
int number;
MsgJoin(int number) {
msgType = MSG_JOIN;
dataLength = sizeof(MsgJoin);
this->number = number;
}
};
class MsgLeave :public MsgHead
{
public:
MsgLeave() {
msgType = MSG_LEAVE;
dataLength = sizeof(MsgLeave);
}
};
class MsgExit :public MsgHead
{
public:
MsgExit() {
msgType = MSG_QUIT;
dataLength = sizeof(MsgExit);
}
};
class MsgTalk :public MsgHead
{
char buff[1024];
public:
MsgTalk() {
memset(buff, '\0', sizeof(buff));
msgType = MSG_TALK;
dataLength = sizeof(MsgTalk);
}
char* getBuff() {
return buff;
}
};
vector<set<SOCKET>> vecMsgCreate;
void dealWithData(MsgHead* msg,SOCKET sClient) {
if (msg->msgType == MSG_SHOW) {
cout << "show" << endl;
int identifier = 1;
for (vector<set<SOCKET>>::iterator it = vecMsgCreate.begin();it != vecMsgCreate.end();it++)
{
cout << "**********chat room: " << identifier <<" ********" << endl;
cout << " users: " << endl;
for (set<SOCKET>::iterator j = it->begin();j != it->end();j++)
{
cout <<' '<< * j << endl;
}
cout << "*****************************" << endl;
identifier++;
}
}
else if (msg->msgType == MSG_CREATE){
cout << "create" << endl;
set<SOCKET> S;
S.insert(sClient);
vecMsgCreate.push_back(S);
}
else if (msg->msgType == MSG_JOIN) {
cout << "join" << endl;
int number = ((MsgJoin*)msg)->number;
vecMsgCreate[number - 1].insert(sClient);
//vector<set<SOCKET>>::iterator it = vecMsgCreate[number - 1].begin();
}
else if (msg->msgType == MSG_LEAVE) {
cout << "leave" << endl;
}
else if ((msg->msgType == MSG_QUIT ) || (msg->msgType == MSG_EXIT)) {
cout << "quit" << endl;
}
else if (msg->msgType == MSG_TALK) {
//cout << "开始聊天" << endl;
//MsgTalk* msgtalk = (MsgTalk*)buff;
char *s= ((MsgTalk*)msg)->getBuff();
cout << s << endl;
for (vector<set<SOCKET>>::iterator it = vecMsgCreate.begin();it != vecMsgCreate.end();it++)
{
if (it->find(sClient)!=it->end())
{
for (set<SOCKET>::iterator j = it->begin();j != it->end();j++)
{
if (*j != sClient)
{
send(*j, (const char*)msg, 1000, 0);
}
}
}
}
}
}
int main()
{
//MessageBox(NULL,TEXT("点击确定运行程序"), TEXT("hello world"),MB_OK);
SOCKET sock_server, newsock;
sockaddr_in addr, client_addr;
unsigned hThread;
/*初始化winsock DLL*/
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "加载winsock.dll失败!\n";
return 0;
}
//data文件句柄
HANDLE hfile = CreateFile(TEXT("..\\..\\data.txt"), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
SetFilePointer(hfile, 0, NULL, FILE_END);
/*创建套接字*/
// if ((sock_server=socket(AF_INET,SOCK_STREAM,0))==INVALID_SOCKET)
if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
cout << "创建套接字失败!" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
/*绑定IP端口*/
int addr_len = sizeof(struct sockaddr_in);
memset((void*)&addr, 0, addr_len);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*给监听套接字绑定地址*/
// if (bind(sock_server,(LPSOCKADDR)&addr,sizeof(addr))!=0)
if (bind(sock_server, (const sockaddr *)&addr, sizeof(addr)) != 0)
{
cout << "绑定地址失败!" << WSAGetLastError() << endl;
closesocket(sock_server);
WSACleanup();
return 0;
}
/*开始监听*/
if (listen(sock_server, 0) != 0)
{
cout << "listen函数调用失败!" << WSAGetLastError() << endl;
closesocket(sock_server);
WSACleanup();
return 0;
}
else
{
cout << "listenning......\n";
}
/*接收并处理客户连接*/
FD_SET fd_read; //fd_set 里面放的是socket,是一个socket集合
FD_ZERO(&fd_read);
FD_SET(sock_server, &fd_read);
FD_SET copy(fd_read);
while (true) {
const timeval tv = { 1,0 };
fd_read = copy;
//cout << fd_read.fd_count << endl;
int ret = select(NULL, &fd_read, NULL, NULL, &tv);//select会识别有没有网络事件发送,若没有就阻塞在这里。加了个timeval代表让他等一秒就下执行
int fdCount = fd_read.fd_count;
for (int i = 0;i < fdCount;i++) {
if (fd_read.fd_array[i] == sock_server) {
sockaddr_in clientAddr;
int nlen = sizeof(sockaddr_in);
SOCKET sClient = accept(sock_server, (sockaddr*)&clientAddr, &nlen);
/* 第一个参数sockfd为服务器的socket描述字,
第二个参数addr为指向struct sockaddr* 的指针,用于返回客户端的协议地址,
第三个参数addrlen为协议地址的长度。*/
if (sClient == SOCKET_ERROR) {
cout << "接收客户端失败" << endl;
closesocket(sock_server);
return -1;
}
else cout << "connect client success" << endl;
FD_SET(sClient, &fd_read);
FD_SET(sClient, ©);
}
else {
char msg[1024];
memset(msg, '\0', sizeof(msg));
int res = recv(fd_read.fd_array[i], msg, 1000, 0);
//for (auto j : copy.fd_array) { //向其他客户端发送的消息
// if (j != sock_server && j != fd_read.fd_array[i]) {
// send(j, msg, 1000, 0);
// }
//}
/***********向data.txt文件写入数据*************/
/*char* s;
s = ((MsgTalk*)msg)->getBuff();
writefile(s, hfile);*/
/***********************************************/
dealWithData((MsgHead *)msg,fd_read.fd_array[i]);
if (res <= 0) {
FD_CLR(fd_read.fd_array[i], ©);
cout << "quit" << endl;
break;
}
}
}
}
closesocket(newsock);
CloseHandle(hfile);
closesocket(sock_server);
WSACleanup();
return 0;
}
void tcpsend(void* par)
{
int size;
SOCKET sock = (SOCKET)par;
while (true)
{
char msg[1000];
memset(msg, '\0', sizeof(msg));
if ((size = recv(sock, msg,99, 0)) < 0)
{
cout << "接收信息失败,错误码:" << WSAGetLastError() << endl;
break;
}
else if (size == 0)
{
break;
}
else //输出收到的数据
{
//将数据中的整型数据字段的值由网络字节顺序转换为主机字节顺序
//stud.examination = htonl(stud.examination);
cout << "收到的消息:" << msg << endl;
}
}
}
int writefile(char* num,HANDLE fileHandle) {
BOOL bRet = WriteFile(fileHandle, num, strlen(num), &d1, NULL);
char c = '\n';
if (!bRet) {
int w = GetLastError();
MessageBox(NULL, TEXT("数据写入失败"), TEXT("标头"), MB_OK);
return w;
}
bRet = WriteFile(fileHandle, &c,1, &d1, NULL);
return 0;
}
Client:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <iostream>
#include <thread>
#include<vector>
#include<queue>
#include<string>
#include<mutex>
#include<future>
#define PORT 65432
#define MSG_SHOW 1
#define MSG_CREATE 2
#define MSG_JOIN 3
#define MSG_LEAVE 4
#define MSG_QUIT 5
#define MSG_EXIT 5
#define MSG_TALK 6
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
class MsgHead
{
public:
int msgType;
int dataLength;
};
class MsgShow : public MsgHead
{
public:
MsgShow()
{
msgType = MSG_SHOW;
dataLength = sizeof(MsgShow);
}
};
class MsgCreate : public MsgHead
{
public:
MsgCreate()
{
msgType = MSG_CREATE;
dataLength = sizeof(MsgCreate);
}
};
class MsgJoin : public MsgHead
{
public:
MsgJoin(int number)
{
msgType = MSG_JOIN;
dataLength = sizeof(MsgJoin);
this->number = number;
}
int number;
};
class MsgLeave : public MsgHead
{
public:
MsgLeave()
{
msgType = MSG_LEAVE;
dataLength = sizeof(MsgLeave);
}
};
class MsgExit : public MsgHead
{
public:
MsgExit()
{
msgType = MSG_EXIT;
dataLength = sizeof(MsgExit);
}
};
class MsgTalk : public MsgHead
{
char buff[1024];
public:
MsgTalk()
{
memset(buff, '\0', sizeof(buff));
msgType = MSG_TALK;
dataLength = sizeof(MsgTalk);
}
char *getBuff()
{
return buff;
}
};
LPVOID chat(LPVOID ptr)
{
SOCKET sClient = *((SOCKET *) ptr);
cout<<sClient<<endl;
cout << "please input: show create join leave exit/quit" << endl;
while (1)
{
char buff[1024];
cin>>buff; //会清掉最后输的换行符
if (strcmp(buff, "show") == 0)
{
MsgShow msgShow;
send(sClient, (const char *) &msgShow, msgShow.dataLength, 0);
//显示聊天室
} else if (strcmp(buff, "join") == 0)
{
cout << "input digital:" << endl;
int number;
cin >> number;
MsgJoin msgJoin(number);
send(sClient, (const char *) &msgJoin, msgJoin.dataLength, 0);
//加入一个聊天室
} else if (strcmp(buff, "create") == 0)
{
MsgCreate msgCreate;
send(sClient, (const char *) &msgCreate, msgCreate.dataLength, 0);
//创建一个聊天室
} else if (strcmp(buff, "talk") == 0)
{
cout << "start talk" << endl;
MsgTalk msgTalk;
while (1)
{
cin >> msgTalk.getBuff();
send(sClient, (const char *) &msgTalk, msgTalk.dataLength, 0);
}
}
}
}
int main()
{
/*定义相关变量*/
SOCKET sock_client; //定义客户端套接字
struct sockaddr_in server_addr; //定义存放服务器端地址的结构变量
int addr_len = sizeof(struct sockaddr_in); //地址结构变量长度
/*初始化*/
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "加载winsock.dll失败!\n";
return 0;
}
/*创建套接字*/
if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
/*输入服务器IP地址以及填写服务器地址结构*/
char IP[20];
cout << "please input server ip:";
cin >> IP;
memset((void *) &server_addr, 0, addr_len);//地址结构清零
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
in_addr a;
inet_pton(AF_INET, IP, &a);
server_addr.sin_addr.s_addr = a.S_un.S_addr;//服务器IP地址
/*Connecting Server*/
SOCKET_ERROR;
if (connect(sock_client, (struct sockaddr *) &server_addr, addr_len) != 0)
{
cout << "Connecting Error!!" << WSAGetLastError() << endl;
closesocket(sock_client);
WSACleanup();
return 0;
}
future<HANDLE> futureResult = async(chat, &sock_client);
// 等待线程结束
while (1)
{
char buff[1000];
int ret = recv(sock_client, buff, 1024, 0);
if (ret > 0)
{
cout << ((MsgTalk *) buff)->getBuff() << endl;
} else
{
cout << "failed ! " << endl;
}
}
//HANDLE = futureResult.get();
//WaitForSingleObject();
closesocket(sock_client);
WSACleanup();
return 0;
}