UDP服务端
协议
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
enum CHAT_COMMAND
{
CHAT_LOGIN, //登录
CHAT_ACK, //确认
CHAT_LOGIN_OK, //成功
CHAT_LOGIN_ERROR, //失败
CHAT_ALL_USER, //通知所有用户
CHAT_LOGOUT, //登录
CHAT_SAY_ALL, //对所有人说
CHAT_SAY_TO, //对人说
CHAT_HEART, //心跳
};
struct CHAT_HEAD_INFO
{
int version;
int cmd;
int len;
};
struct CHAT_LOGIN_INFO
{
char name[20];
};
struct CHAT_USER_INFO
{
int id;
char name[20];
bool operator==(const CHAT_USER_INFO& info)
{
return id == info.id;
}
};
struct CHAT_CLIENT_INFO
{
int id;
char name[20];
sockaddr_in addr;
//int time;
bool operator==(const CHAT_CLIENT_INFO& info)
{
return id == info.id;
}
};
//2 udp len 65535
//1500 mtu
struct CHAT_SAY_ALL_INFO
{
int id;
char msg[200];
};
struct CHAT_SAY_TO_INFO
{
int sourceID;
int destID;
char msg[200];
};
ServerChat.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#include <windows.h>
#include <string>
#include <list>
#include "Protocol.h"
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
//心跳包
//丢包
class ServerChat
{
public:
ServerChat();
~ServerChat();
void reportError(std::string s); //
void showMessage(std::string msg);
void createSocket();
void bindSocket();
void destroySocket();
int getCommand();
int parseCommand(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr);
private:
SOCKET socket_;
static int id_;
public:
static list<CHAT_CLIENT_INFO> clients_;
public:
int clientLogin(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr);
bool nameExist(const char* buffer);
int insertClientInfo(const char* buffer, sockaddr_in& caddr);
int brocastUserList();
int remoteClientInfo(CHAT_CLIENT_INFO& info);
int brocastUserListHeart();//服务器发送心跳进行广播
int clientReplyHeart(CHAT_USER_INFO& stUserInfo, sockaddr_in& caddr);//那个客户端回复了心跳
//dxfgsdf
int sengPacket(int cmd, char *buffer, int len, sockaddr& addr, int version = 1);
int clientSayAll(CHAT_SAY_ALL_INFO& info);
int clientSayTo(CHAT_SAY_TO_INFO& info);
};
ServerChat.cpp
#include "stdafx.h"
#define _CRT_SECURE_NO_WARNINGS
#include "ServerChat.h"
#include <algorithm>
int ServerChat::id_ = 0;
list<CHAT_CLIENT_INFO> ServerChat::clients_;
ServerChat::ServerChat()
{
socket_ = INVALID_SOCKET;
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
createSocket();
bindSocket();
}
ServerChat::~ServerChat()
{
destroySocket();
WSACleanup();
}
void ServerChat::reportError(std::string s)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
printf("%s==>%s", s.c_str(), (char*)lpMsgBuf);
LocalFree(lpMsgBuf);
}
void ServerChat::showMessage(std::string msg)
{
printf(msg.c_str());
}
void ServerChat::createSocket()
{
/*
1.创建socket
*/
int result;
socket_ = socket(AF_INET, SOCK_DGRAM, 0); //udp
if (INVALID_SOCKET == socket_)
{
reportError("socket\n");
return;
}
showMessage("socket ok\n");
}
void ServerChat::bindSocket()
{
//2.绑定端口
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//addr.sin_addr.S_un.S_addr = ADDR_ANY; //任何地址
int result = bind(socket_, (sockaddr*)&addr, sizeof(addr));
if (result == SOCKET_ERROR)
{
reportError("bind\n");
return;
}
showMessage("bind ok\n");
showMessage("server start...\n");
}
void ServerChat::destroySocket()
{
if (socket_ != INVALID_SOCKET)
{
//关闭
shutdown(socket_, SD_BOTH);
showMessage("shutdown ok\n");
closesocket(socket_);
showMessage("closesocket ok\n");
}
}
int ServerChat::getCommand()
{
sockaddr_in caddr;
int len = sizeof(caddr);
CHAT_HEAD_INFO head;
int result;
char *buffer = NULL;
//接受消息
result = recvfrom(socket_, (char*)&head, sizeof(head), 0, (sockaddr*)&caddr, &len);
if (result <= 0)
{
reportError("recvfrom\n");
return 0;
}
buffer = new char[head.len];
result = recvfrom(socket_, buffer, head.len, 0, (sockaddr*)&caddr, &len);
if (result <= 0)
{
delete[] buffer;
reportError("recvfrom\n");
return 0;
}
if (parseCommand(head, buffer, caddr) < 0)
{
delete[] buffer;
return -1;
}
delete[] buffer;
return 0;
}
int ServerChat::parseCommand(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr)
{
int result = 0;
switch (head.cmd)
{
case CHAT_LOGIN:
result = clientLogin(head, buffer, caddr);
break;
case CHAT_LOGOUT:
result = remoteClientInfo(*(CHAT_CLIENT_INFO*)buffer);
break;
case CHAT_SAY_ALL:
result = clientSayAll(*(CHAT_SAY_ALL_INFO*)buffer);
break;
case CHAT_SAY_TO:
result = clientSayTo(*(CHAT_SAY_TO_INFO*)buffer);
case CHAT_HEART:
result = clientReplyHeart(*(CHAT_USER_INFO*)buffer,caddr);
break;
}
return result;
}
int ServerChat::clientLogin(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr)
{
CHAT_HEAD_INFO sendHead;
sendHead.version = 1;
sendHead.len = 0;
int result;
if (nameExist(buffer))
{
sendHead.cmd = CHAT_LOGIN_ERROR;
result = sendto(socket_, (char*)&sendHead, sizeof(sendHead), 0, (sockaddr*)&caddr, sizeof(caddr));
if (result <= 0)
{
reportError("sendto");
return -1;
}
}
//save client info
insertClientInfo(buffer, caddr);
return 0;
}
bool ServerChat::nameExist(const char* buffer)
{
//遍历名字是否重复
for (auto info : clients_)
{
if (strcmp(buffer, info.name) == 0)
{
return true;
}
}
return false;
}
int ServerChat::insertClientInfo(const char* buffer, sockaddr_in& caddr)
{
CHAT_CLIENT_INFO info;
info.id = ++id_;
strcpy(info.name, buffer);
info.addr = caddr;
clients_.push_back(info);
printf("%s:%d==>%s login\n",
inet_ntoa(caddr.sin_addr),
htons(caddr.sin_port),
info.name);
CHAT_HEAD_INFO sendHead;
sendHead.version = 1;
sendHead.cmd = CHAT_LOGIN_OK;
sendHead.len = sizeof(CHAT_USER_INFO);
int result = sendto(socket_, (char*)&sendHead, sizeof(sendHead), 0, (sockaddr*)&caddr, sizeof(caddr));
if (result <= 0)
{
reportError("sendto");
return -1;
}
result = sendto(socket_, (char*)&info, sizeof(CHAT_USER_INFO), 0, (sockaddr*)&caddr, sizeof(caddr));
if (result <= 0)
{
reportError("sendto");
return -1;
}
//brocast user list
brocastUserList();
return 0;
}
int ServerChat::remoteClientInfo(CHAT_CLIENT_INFO& info)
{
list<CHAT_CLIENT_INFO>::iterator it;
clients_.remove(info);
//brocast user list
brocastUserList();
return 0;
}
int ServerChat::brocastUserList()
{
CHAT_HEAD_INFO head;
head.version = 1;
head.cmd = CHAT_ALL_USER;
head.len = clients_.size() * sizeof(CHAT_USER_INFO);
CHAT_USER_INFO *userInfo = new CHAT_USER_INFO[clients_.size()];
int i = 0;
for (auto info : clients_)
{
userInfo[i].id = info.id;
strcpy(userInfo[i++].name, info.name);
}
//send
for (auto info : clients_)
{
sendto(socket_, (char*)&head, sizeof(head), 0, (sockaddr*)&info.addr, sizeof(sockaddr));
sendto(socket_, (char*)userInfo, head.len, 0, (sockaddr*)&info.addr, sizeof(sockaddr));
}
return 0;
}
int ServerChat::brocastUserListHeart()
{
CHAT_HEAD_INFO head;
head.version = 1;
head.cmd = CHAT_HEART;
head.len = 1;
char ch = 'F';
//send
if (clients_.size() > 0)
{
for (auto info : clients_)
{
int nRet = sendto(socket_, (char*)&head, sizeof(head), 0, (sockaddr*)&info.addr, sizeof(sockaddr));
if (nRet <= 0)
{
return -1;
}
nRet = sendto(socket_, &ch, sizeof(ch), 0, (sockaddr*)&info.addr, sizeof(sockaddr));
if (nRet <= 0)
{
return -1;
}
}
}
return 0;
}
extern CRITICAL_SECTION cs;
int ServerChat::clientReplyHeart(CHAT_USER_INFO& stUserInfo, sockaddr_in& caddr)
{
EnterCriticalSection(&cs);
CHAT_CLIENT_INFO info;
info.id = stUserInfo.id;
strcpy(info.name, stUserInfo.name);
info.addr = caddr;
clients_.push_back(info);
printf("%s:%d==>%s Online\n",
inet_ntoa(caddr.sin_addr),
htons(caddr.sin_port),
info.name);
LeaveCriticalSection(&cs);
return 0;
}
int ServerChat::clientSayAll(CHAT_SAY_ALL_INFO& info)
{
for (auto cinfo : clients_)
{
int result = sengPacket(CHAT_SAY_ALL, (char*)&info, sizeof(info), (sockaddr&)cinfo.addr);
}
return 0;
}
int ServerChat::sengPacket(int cmd, char *buffer, int len, sockaddr& addr, int version)
{
CHAT_HEAD_INFO head;
head.version = version;
head.cmd = cmd;
head.len = len;
//代码重构
int result = sendto(socket_, (const char*)&head, sizeof(head), 0, (sockaddr*)&addr, sizeof(addr));
if (result <= 0)
return -1;
result = sendto(socket_, buffer, len, 0, (sockaddr*)&addr, sizeof(addr));
if (result <= 0)
return -1;
return 0;
}
int ServerChat::clientSayTo(CHAT_SAY_TO_INFO& info)
{
for (auto cinfo : clients_)
{
if (cinfo.id == info.destID)
{
int result = sengPacket(CHAT_SAY_TO, (char*)&info, sizeof(info), (sockaddr&)cinfo.addr);
break;
}
}
return 0;
}
main.cpp
// Server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "ServerChat.h"
CRITICAL_SECTION cs;
//心跳包处理
//心跳包直接放在cmd中就可以,客户端回个id和名字就可以,回复的放在新的链表中,对比旧的链表,如果旧的链表中没有新的,说明不在线,进行链表清理
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
ServerChat* pstServer = (ServerChat*)lpParameter;
while (true)
{
Sleep(20000);
EnterCriticalSection(&cs);
//发送心跳包
int nRet=pstServer->brocastUserListHeart();
if (nRet < 0)
{
printf("广播心跳时出错\n");
return -1;
}
pstServer->clients_.clear();//发送心跳后清理,统计还有谁在线,把在线的人重新放入链表中
LeaveCriticalSection(&cs);
}
return 0;
}
int main()
{
//初始化临界区
InitializeCriticalSection(&cs);
ServerChat server;
HANDLE hTread = CreateThread(
NULL, //安全属性
0, //使用默认栈大小,一般是1M,在链接选项(堆栈保留大小)可以修改
ThreadProc, //线程回调函数
(LPVOID)&server, //自定义参数
0, //线程是否立即运行
NULL); //传出参数:线程ID
if (hTread == NULL)
{
printf("创建线程失败\n");
return 0;
}
while (true)
{
if (server.getCommand() < 0)
break;
}
return 0;
}
UDP客户端
ClientChat.h
#pragma once
#include <WinSock2.h>
#include <windows.h>
#include <string>
#include <list>
#include "../../Server/Server/Protocol.h"
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
class ClientChat
{
public:
ClientChat();
~ClientChat();
void createSocket();
void destroySocket();
int getCommand();
int parseCommand(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr);
void reportError(std::string s); //
void showMessage(std::string msg);
void shoWelcom();
void createWorkThread();
int inputCommand();
private:
static DWORD WINAPI workThread(LPVOID lpParameter);
private:
SOCKET socket_;
sockaddr_in addr_;
static CHAT_USER_INFO myuser_;
static list<CHAT_USER_INFO> users_;
public:
int login();
int chatUserInfo(CHAT_USER_INFO *userInfo, int count);
int showUserList();
int logout();
int sengPacket(int cmd, char *buffer, int len, int version = 1);
int sayAll();
int sayTo();
int sendHeart();
};
ClientChat.cpp
#include "stdafx.h"
#include "ClientChat.h"
list<CHAT_USER_INFO> ClientChat::users_;
CHAT_USER_INFO ClientChat::myuser_;
ClientChat::ClientChat()
{
socket_ = INVALID_SOCKET;
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
createSocket();
addr_.sin_family = AF_INET;
addr_.sin_port = htons(5566);
addr_.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK); //127.0.0.1
}
ClientChat::~ClientChat()
{
destroySocket();
WSACleanup();
}
void ClientChat::createSocket()
{
/*
1.创建socket
*/
int result;
socket_ = socket(AF_INET, SOCK_DGRAM, 0); //udp
if (INVALID_SOCKET == socket_)
{
reportError("socket\n");
return;
}
showMessage("socket ok\n");
}
void ClientChat::destroySocket()
{
if (socket_ != INVALID_SOCKET)
{
//关闭
shutdown(socket_, SD_BOTH);
showMessage("shutdown ok\n");
closesocket(socket_);
showMessage("closesocket ok\n");
}
}
int ClientChat::getCommand()
{
CHAT_HEAD_INFO head;
sockaddr_in caddr;
int len = sizeof(caddr);
int result;
char *buffer = NULL;
//接受消息
result = recvfrom(socket_, (char*)&head, sizeof(head), 0, (sockaddr*)&caddr, &len);
if (result < 0)
{
return 0;
}
if (head.len > 0)
{
buffer = new char[head.len];
result = recvfrom(socket_, buffer, head.len, 0, (sockaddr*)&caddr, &len);
if (result < 0)
{
delete[] buffer;
reportError("recvfrom");
return -1;
}
}
if (parseCommand(head, buffer, caddr) < 0)
{
delete[] buffer;
return -1;
}
delete[] buffer;
return 0;
}
int ClientChat::parseCommand(CHAT_HEAD_INFO& head, char* buffer, sockaddr_in caddr)
{
int result = 0;
//回复消息
switch (head.cmd)
{
case CHAT_LOGIN_OK:
{
showMessage("login ok");
myuser_ = *(CHAT_USER_INFO*)buffer;
break;
}
case CHAT_LOGIN_ERROR:
{
showMessage("login error");
break;
}
case CHAT_SAY_ALL:
{
CHAT_SAY_ALL_INFO *info = (CHAT_SAY_ALL_INFO*)buffer;
for (auto user : users_)
{
if (user.id == info->id)
{
printf("%s say:%s\n", user.name, info->msg);
break;
}
}
break;
}
case CHAT_SAY_TO:
{
CHAT_SAY_TO_INFO *info = (CHAT_SAY_TO_INFO*)buffer;
for (auto user : users_)
{
if (user.id == info->sourceID)
{
printf("%s say:%s\n", user.name, info->msg);
break;
}
}
break;
}
case CHAT_ALL_USER:
{
CHAT_USER_INFO *userInfo = (CHAT_USER_INFO*)buffer;
int count = head.len / sizeof(CHAT_USER_INFO);
result = chatUserInfo(userInfo, count);
showUserList();
break;
}
case CHAT_HEART:
{
//发送心跳包
result = sendHeart();
break;
}
}
return result;
}
void ClientChat::reportError(std::string s)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
printf("%s==>%s", s.c_str(), (char*)lpMsgBuf);
LocalFree(lpMsgBuf);
}
void ClientChat::showMessage(std::string msg)
{
puts(msg.c_str());
}
void ClientChat::shoWelcom()
{
printf("#################################\n");
printf("# #\n");
printf("# #\n");
printf("# Welcom BB Talk #\n");
printf("# #\n");
printf("# #\n");
printf("#################################\n");
}
DWORD WINAPI ClientChat::workThread(LPVOID lpParameter)
{
ClientChat *obj = (ClientChat*)lpParameter;
while (true)
{
if (obj->getCommand() < 0)
break;
}
return 0;
}
void ClientChat::createWorkThread()
{
CreateThread(NULL, 0, workThread, (LPVOID)this, 0, NULL);
}
int ClientChat::inputCommand()
{
int input;
printf("1.Login 2.Brocast 3.Talk 4.Logout\n");
scanf("%d", &input);
switch (input)
{
case 1:
login();
break;
case 2:
sayAll();
break;
case 3:
sayTo();
break;
case 4:
logout();
break;
}
return 0;
}
int ClientChat::login()
{
//登录
CHAT_HEAD_INFO head;
CHAT_LOGIN_INFO login;
int result;
head.version = 1;
head.cmd = CHAT_LOGIN;
head.len = sizeof(login);
printf("name:");
scanf("%s", login.name);
result = sendto(socket_, (const char*)&head, sizeof(head), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
result = sendto(socket_, (const char*)&login, sizeof(login), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
return 0;
}
int ClientChat::chatUserInfo(CHAT_USER_INFO *userInfo, int count)
{
users_.clear();
for (int i = 0; i < count; i++)
users_.push_back(userInfo[i]);
return 0;
}
int ClientChat::showUserList()
{
printf("list:");
for (auto info : users_)
printf("[%d]%s ", info.id, info.name);
printf("\n");
return 0;
}
int ClientChat::logout()
{
//登录
CHAT_HEAD_INFO head;
int result;
head.version = 1;
head.cmd = CHAT_LOGOUT;
head.len = sizeof(myuser_);
//代码重构
result = sendto(socket_, (const char*)&head, sizeof(head), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
result = sendto(socket_, (const char*)&myuser_, sizeof(myuser_), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
return 0;
}
int ClientChat::sengPacket(int cmd, char *buffer, int len, int version)
{
CHAT_HEAD_INFO head;
head.version = version;
head.cmd = cmd;
head.len = len;
//代码重构
int result = sendto(socket_, (const char*)&head, sizeof(head), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
result = sendto(socket_, buffer, len, 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
return -1;
return 0;
}
int ClientChat::sayAll()
{
CHAT_SAY_ALL_INFO info;
info.id = myuser_.id;
printf("msg:");
scanf("%20s", info.msg);
return sengPacket(CHAT_SAY_ALL, (char*)&info, sizeof(info));
}
int ClientChat::sayTo()
{
CHAT_SAY_TO_INFO info;
info.sourceID = myuser_.id;
char name[20];
printf("name:");
scanf("%20s", name);
for (auto user : users_)
{
if (strcmp(user.name, name) == 0)
{
info.destID = user.id;
printf("msg:");
scanf("%20s", info.msg);
return sengPacket(CHAT_SAY_TO, (char*)&info, sizeof(info));
break;
}
}
return 0;
}
int ClientChat::sendHeart()
{
CHAT_HEAD_INFO head;
int result;
head.version = 1;
head.cmd = CHAT_HEART;
head.len = sizeof(myuser_);
result = sendto(socket_, (const char*)&head, sizeof(head), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
{
return -1;
}
result = sendto(socket_, (const char*)&myuser_, sizeof(myuser_), 0, (sockaddr*)&addr_, sizeof(addr_));
if (result <= 0)
{
return -1;
}
return 0;
}
main.cpp
// Client.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "ClientChat.h"
int main()
{
ClientChat client;
client.shoWelcom();
client.createWorkThread();
while (true)
{
if (client.inputCommand() < 0)
break;
}
return 0;
}