c++网络通信多人聊天室Server端
一、描述
1> 采用c++语言
2>编译环境是vs2015
3>使用的是阻塞式套接字
二、功能描述
1>一个服务器对多个客户端
2>通过广播的方式使得客户端与服务器端都能够接收到彼此的消息
3>服务器端负责接收和处理客户端的数据
三、遇到的问和一些解决思路总结
对于初学c++,并且刚接触网络socket套接字编程的小白来说,socket套接字是一个很麻烦的东西。而在我一段时间的学习以来,我也遇到了很多的问题,在解决问题的过程中,我逐渐学会了c++面向对象的编程思想——即把你需要的功能类封装在不同的cpp源文件中,每个封装类将实现某一个具体的功能。比如将套接字的功能全部封装在CSocket类里面。
而我第一个遇见的问题——需要将头文件(.h文件)与源文件(.cpp文件)分开编写!这是一个很现实的编程习惯问题——也是编程的一个框架结构问题,就像我们数据结构的学习中,老师教会我们如何使用一些固定的结构以实现一些逻辑上的功能和算法。 而我遇到的问题即是 如何将头文件(.h文件)与源文件(.cpp文件)分开来编写,以下通过我自己碰到的问题和我解决问题的过程中总结了的一些小技巧和注意事项:
1、我们都知道,在源文件(即.cpp文件)中都会做一些头文件(即.h文件)的引用,例如:include “iostream” 这就是在引入一个c++的输入输出流头文件,那么第一个问题就是:在头文件中编写什么?在源文件中编写什么?
(1)、我们都知道,函数都是先声明后定义,而且每个函数名第一次出现的时候那就是它对应的声明。
(2)、我们可以在头文件中做出我们需要的声明,如类、对象、函数、静态static对象或者函数的声明,也可以在头文件中引入其他的头文件,这样做的目的可以让我们在其对应的源文件的编译中不再需要引入其他头文件,而只需要引入对应的头文件,比如我上文中的CSocket.h与CSocket.cpp中的一样。我们在CSocket.h中声明了我们需要实现的类与函数名以及它的参数,而在CSocket.cpp中来具体的实现这些函数和类,也就是提供给cpp文件我们需要实现和编译的“方法”。
2、编程的思路问题。编程是思路是一个很重要的问题,我们需要对它有一个足够重视和清醒的认识。就我遇见的问题来看,刚开始接触socket套接字的我同样不能有一个很明晰的编程思路,不知道应该先编哪里、先封装哪一个类。以下是我学习中的一些总结:
(1)、我们编程应首先确定你需要的功能即你需要实现的“方法”,如socket套接字中对端口号的设置,对IP地址的设置,对阻塞模式的设置,对其他客户端的监听和接收,以及各种错误信息的设置和反馈。 而在网络编程多人聊天室的编码过程中,我经过多次的尝试和总结以后,理出了一个大概的思路——即首先封装socket套接字,然后按逻辑封装其他的功能类
注:以下包括4个头文件和4个源文件,一共有八个封装文件,下面将按顺序列出各个封装类的代码:
1、Server.cpp
// Server练习版.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#include
#include "SServer.h"
#include "CSocket.h"
#include "ClientList.h"
const int BUF_LEN = 1024;
void recv(PVOID pt)//定义一个接收套接字实例的函数
{
CSocket* csocket = (CSocket*)pt;//定义CSocket指针类型(套接字)
if (csocket != NULL)
{
int count = csocket->Receive(BUF_LEN);
if (count == 0)
{
ClientList* list = ClientList::GetInstance();
list->Remove(csocket);
cout << "一个用户下线,现在的在线人数为:" << list->Count() << endl;
_endthread();//用户下线,终止接收数据线程
}
}
}
void sends(PVOID pt)
{
ClientList* list = (ClientList*)pt;
while (1)
{
char* buf = new char[BUF_LEN];
cin >> buf;
int bufSize = 0;
while (buf[bufSize++] != '\0');
for (int i = list->Count() - 1; i >= 0; i--)
{
(*list)[i]->Send(buf, bufSize);
}
delete buf;
}
}
int main(int argc,char* argv[])
{
SServer server;
bool isStart = server.Start(1986);
if (isStart)
{
cout << "server start success..." << endl;
}
else
{
cout << "server start error" << endl;
}
ClientList* list = ClientList::GetInstance();
_beginthread(sends, 0, list);//启动一个线程广播数据
while(1)
{
CSocket* csocket = server.Accept();
csocket->diaoyong(list);
char* nihao = "holle~";
csocket->Send(nihao, sizeof(&nihao) + 1);
list->Add(csocket);
char* p = "新上线一个用户";
cout << "新上线一个用户,现在在线人数为:" << list->Count() << endl;
list->Broadcast(p, strlen(p), csocket);
_beginthread(recv, 0, csocket);//启动一个接收数据的线程
}
getchar();
return 0;
}
2、CSocket.h
#pragma once
#ifndef __CSOCKET_H__
#define __CSOCKET_H__
#include
#include "SocketEnum.h"
#include
using namespace std;
#include "ClientList.h"
class ClientList;
class CSocket
{
public:
CSocket(SocketEnum::SocketType _socketType = SocketEnum::Tcp);
~CSocket();
bool Connect(const char* ip, int port);//链接 port:端口
int Send(char* pBuf, int len);//发送
int Receive(int strLen);//接收
bool SetBlocking(bool isBlockinng);//设置阻塞模式
bool ShutDown(SocketEnum::ShutdownMode mode);
char* GetData();//获取接收数据
SocketEnum::SocketError GetSocketError();
void SetSocketHandle(SOCKET socket);//设置套接字句柄
void Close();//关闭
bool operator==(const CSocket* socket);
bool IsExit();
void diaoyong(ClientList* Clien2);
ClientList* p;
private:
void SetSocketError(SocketEnum::SocketError error);
void SetSocketError(void);
bool IsSocketValid(void);
SOCKET csocket;
bool isConnected;//链接状态
struct sockaddr_in serverAddress;
char* buffer;//存接收数据
int sendCount;//发送数据长度
int recvCount;//接收数据长度
bool isBlocking;//是否是阻塞模式
SocketEnum::SocketError socketError;
SocketEnum::SocketType socketType;
WSADATA wsa;
//ClientList* p;
};
#endif
3、CSocket.cpp
#include "stdafx.h"
#include "CSocket.h"
CSocket::CSocket(SocketEnum::SocketType _socketType) :csocket(INVALID_SOCKET), isConnected(false), buffer(NULL), sendCount(0), recvCount(0), isBlocking(true), socketError(SocketEnum::InvalidSocket), socketType(_socketType) {}
bool CSocket::Connect(const char* ip, int port)
{
isConnected = true;
socketError = SocketEnum::Success;
if (WSAStartup(MAKEWORD(2, 2),&wsa) != 0)//初始化套接字DLL
{
SetSocketError(SocketEnum::WSAStartupError);//错误
isConnected = false;//失败返回false
}
if (isConnected)//如果true
{
if ((csocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)//
{
SetSocketError();
isConnected = false;//返回失败false
}
}
if (isConnected)//如果true
{
memset(&serverAddress, 0, sizeof(sockaddr_in));//memset:内存
serverAddress.sin_family = AF_INET;
long lip = inet_addr(ip);
if (lip == INADDR_NONE)
{
SetSocketError(SocketEnum::InvaliAddress);
isConnected = false;
}
else
{
if (port < 0)//如果端口号小于0
{
SetSocketError(SocketEnum::InvalidPort);
isConnected = false;//返回失败false
}
else
{
serverAddress.sin_addr.S_un.S_addr = lip;
serverAddress.sin_port = htons(port);
if (connect(csocket, (sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR)
{
SetSocketError();
isConnected = false;//返回失败false
}
}
}
}
return isConnected;//返回成功true
}
//设置阻塞模式
bool CSocket::SetBlocking(bool isBlock)
{
int block = isBlock ? 0 : 1;
if (ioctlsocket(csocket, FIONBIO, (ULONG*)&block) != 0)
{
return false;
}
isBlocking = isBlock;
return true;
}
int CSocket::Send(char* pBuf, int len)
{
if (!IsSocketValid() || !isConnected)
{
return 0;
}
if (pBuf == NULL || len < 1)
{
return 0;
}
sendCount = send(csocket, pBuf, len, 0);
if (sendCount <= 0)
{
cout << GetSocketError() << endl;
}
return sendCount;
}
//Receive:收到,接纳;
int CSocket::Receive(int strLen)//接收套接字
{
recvCount = 0;
if (!IsSocketValid() || !isConnected)
{
return recvCount;
}
if (strLen < 1)
{
return recvCount;
}
if (buffer != NULL)
{
delete buffer;
buffer = NULL;
}
buffer = new char[strLen];
SetSocketError(SocketEnum::Success);
while (1)
{
recvCount = recv(csocket, buffer, strLen, 0);
if (recvCount > 0)
{
buffer[recvCount] = '\0';
if (IsExit())
{
Send(buffer, recvCount);
delete buffer;
buffer = NULL;
recvCount = 0;
break;
}
else
{
cout << "buffer" << endl;
p->Broadcast(buffer, strlen(buffer), this);
cout << buffer << endl;
}
}
}
return recvCount;
}
void CSocket::diaoyong(ClientList* Clien2)
{
p = Clien2;
}
bool CSocket::IsExit()
{
int len = strlen(buffer);
int i = 0;
int size = 4;
if (len == size)
{
char*exit = "EXIT";
for (i = 0; i < size; i++)
{
if (buffer[i] != *(exit + 1) && buffer[i] - 32 != *(exit + i))
{
break;
}
}
}
return i == size;
}
//设置错误信息
void CSocket::SetSocketError(SocketEnum::SocketError error)
{
socketError = error;
}
void CSocket::SetSocketError(void)//枚举socket返回的错误信息
{
int nError = WSAGetLastError();
switch (nError)
{
case EXIT_SUCCESS:
SetSocketError(SocketEnum::Success);
break;
case WSAEBADF:
case WSAENOTCONN:
SetSocketError(SocketEnum::Notconnected);
break;
case WSAEINTR:
SetSocketError(SocketEnum::Interrupted);
break;
case WSAEACCES:
case WSAEAFNOSUPPORT:
case WSAEINVAL:
case WSAEMFILE:
case WSAENOBUFS:
case WSAEPROTONOSUPPORT:
SetSocketError(SocketEnum::InvalidSocket);
break;
case WSAECONNREFUSED:
SetSocketError(SocketEnum::ConnectionRefused);
break;
case WSAETIMEDOUT:
SetSocketError(SocketEnum::Timedout);
break;
case WSAEINPROGRESS:
SetSocketError(SocketEnum::Einprogress);
break;
case WSAECONNABORTED:
SetSocketError(SocketEnum::ConnectionAborted);
break;
case WSAEWOULDBLOCK:
SetSocketError(SocketEnum::Ewouldblock);
break;
case WSAENOTSOCK:
SetSocketError(SocketEnum::InvalidSocket);
break;
case WSAECONNRESET:
SetSocketError(SocketEnum::ConnectionReset);
break;
case WSANO_DATA:
SetSocketError(SocketEnum::InvaliAddress);
break;
case WSAEADDRINUSE:
SetSocketError(SocketEnum::AddressInUse);
break;
case WSAEFAULT:
SetSocketError(SocketEnum::InvalidPointer);
break;
default:
SetSocketError(SocketEnum::UnknownError);
break;
}
}
//valid:有效的
bool CSocket::IsSocketValid(void)//有效的套接字
{
return socketError == SocketEnum::Success;//返回Success信息
}
SocketEnum::SocketError CSocket::GetSocketError()
{
return socketError;
}
CSocket::~CSocket()
{
Close();//关闭
}
void CSocket::Close()
{
if (buffer != NULL)//如果不为空
{
delete buffer;//删除储存的信息
buffer = NULL;//存储段为空
}
ShutDown(SocketEnum::Both);
if (closesocket(csocket) != SocketEnum::Error)
{
csocket = INVALID_SOCKET;
}
/* WSACleanup();//清理套接字占用的资源*/
}
//ShutDown:关机 mode:方式
bool CSocket::ShutDown(SocketEnum::ShutdownMode mode)
{
SocketEnum::SocketError nRetVal = (SocketEnum::SocketError)shutdown(csocket, SocketEnum::Both);
SetSocketError();
return(nRetVal == SocketEnum::Success) ? true : false;
}
char* CSocket::GetData()
{
return buffer;
}
void CSocket::SetSocketHandle(SOCKET socket)
{
if (socket != SOCKET_ERROR)
{
csocket = socket;
isConnected = true;
socketError = SocketEnum::Success;
}
}
bool CSocket::operator==(const CSocket* socket)
{
return csocket == socket->csocket;
}
4、SServer.h
#pragma once
#ifndef __SSERVER_H__
#define __SSERVER_H__
#include
#include "SocketEnum.h"
#include "CSocket.h"
class SServer
{
public:
//启动服务器
bool Start(int port);
//接收客户请求
CSocket* Accept();
void SetSocketError(SocketEnum::SocketError error);
~SServer();
void Close();
bool ShutDown(SocketEnum::ShutdownMode mode);
private:
SOCKET ssocket;
char* buffer;
struct sockaddr_in serverAddress;
SocketEnum::SocketError socketError;
bool isStart;
WSADATA wsa;
};
#endif __SSERVER_H__
5、SServer.cpp
#include "stdafx.h"
#include "SServer.h"
bool SServer::Start(int port)
{
isStart = true;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)//初始化套接字DLL WSAStartup 是windows的异步套接字启动命令(与网络无关的编程接口)
{
SetSocketError(SocketEnum::WSAStartupError); //设置错误信息
isStart = false;//如果错误返回false
}
if (isStart)//如果ture
{
if ((ssocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)//INVALID_SOCKET 是把socket设置成无效套接字
{
SetSocketError(SocketEnum::InvalidSocket);//设置错误信息
isStart = false;//如果错误返回false
}
}
if (isStart)//如果ture
{
//初始化指定的内存区域
memset(&serverAddress, 0, sizeof(sockaddr_in));//serverAddress 是返回内存地址 sizeof(sockaddr_in) 是返回块的大小
serverAddress.sin_family = AF_INET;//初始化
serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//初始化
if (port>0)
{
serverAddress.sin_port = htons(port);//初始化
}
else
{
SetSocketError(SocketEnum::InvalidPort);//设置错误信息
isStart = false;//返回false
}
}
if (isStart)//如果ture
{
//绑定 //bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定值2,...,参数绑定值n)
if (bind(ssocket, (sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR)
{
SetSocketError(SocketEnum::BindError);//设置错误信息
}
else
{
if (listen(ssocket, SOMAXCONN) == SOCKET_ERROR)//进入侦听状态 SOMAXCONN是一个默认值(有最大值)
{
SetSocketError(SocketEnum::ListenError);//设置错误信息
}
}
}
return isStart; //返回ture
}
void SServer::SetSocketError(SocketEnum::SocketError error)//定义的错误代码 Socket 类
{
socketError = error;
}
CSocket* SServer::Accept()//accept()是在一个套接口接受的一个连接,并返回句柄
{
CSocket* csocket = new CSocket();
struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址
int addrlen = sizeof(clientAddress);
memset(&clientAddress, 0, addrlen);//初始化存放客户端信息的内存
SOCKET socket;
if ((socket = accept(ssocket, (sockaddr*)&clientAddress, &addrlen)) != INVALID_SOCKET)
{
csocket->SetSocketHandle(socket);//设置句柄
}
return csocket;//返回句柄
}
SServer::~SServer()//析构
{
Close();
}
bool SServer::ShutDown(SocketEnum::ShutdownMode mode)//关闭socket
{
SocketEnum::SocketError nRetVal = (SocketEnum::SocketError)shutdown(ssocket, SocketEnum::Both);
return (nRetVal == SocketEnum::Success) ? true : false;//判断成功与否,返回ture或者false
}
void SServer::Close()//关闭
{
ShutDown(SocketEnum::Both);//调用shutdown函数执行关闭操作
if (closesocket(ssocket) != SocketEnum::Error)
{
ssocket = INVALID_SOCKET;
}
WSACleanup();//清理套接字占用的资源
}
6、ClientList.h
#pragma once
#ifndef _CLIENTLIST_H_
#define _CLIENTLIST_H_
#include
#include "CSocket.h"
#include //assert.h常用于防御式编程。断言(Assertions),一个断言通常是一个例程(routines)或者一个宏(marcos)。每个断言通常含有两个参数:一个布尔表示式(a boolean expression)和一个消息(a message)。
class CSocket;
class ClientList
{
public:
typedef vector::iterator Iter;//typedef:类型定义 iterator:迭代器
void Add(CSocket* socket);
int Count() const;
CSocket* operator[](size_t index);//index:指针
void Remove(CSocket* socket);//移除
Iter Find(CSocket* socket);//查找
void Clear();
void Broadcast(char* str, int len, CSocket* s3);
void ShowVec(const vector& valList);
static ClientList* GetInstance()//定义一个全局客户端list列表 GetInstance()实例
{
static ClientList instance;
return &instance;//返回实例地址
}
~ClientList();//析构 释放内存
private:
static CRITICAL_SECTION g_cs;
static vector _list;//list列表中是socket静态全局对象
ClientList();
ClientList(const ClientList&);
ClientList& operator=(const ClientList&);
};
#endif
7、ClientList.cpp
#include "stdafx.h"
#include "ClientList.h"
typedef vector::iterator Iter;
ClientList::ClientList()
{
InitializeCriticalSection(&g_cs);//初始化g_cs的成员
}
ClientList::~ClientList()
{
DeleteCriticalSection(&g_cs);//删除关键段
}
void ClientList::Add(CSocket* socket)
{
if (socket != NULL)
{
EnterCriticalSection(&g_cs);//进入关键段
_list.push_back(socket);
LeaveCriticalSection(&g_cs);//退出关键段
}
}
int ClientList::Count() const//计数socket连接数量
{
return _list.size();
}
CSocket* ClientList::operator[](size_t index)//index:指针
{
assert(index >= 0 && index < _list.size());//assert:断言
return _list[index];
}
void ClientList::Remove(CSocket* socket)
{
Iter iter = Find(socket);
EnterCriticalSection(&g_cs);//进入关键段
if (iter != _list.end())
{
delete *iter;
_list.erase(iter);
}
LeaveCriticalSection(&g_cs);//退出关键段
}
Iter ClientList::Find(CSocket* socket)
{
EnterCriticalSection(&g_cs);//进入关键段
Iter iter = _list.begin();
while (iter != _list.end())
{
if (*iter == socket)
{
return iter;
}
iter++;
}
LeaveCriticalSection(&g_cs);//退出关键段
return iter;
}
void ClientList::Clear()
{
EnterCriticalSection(&g_cs);
for (int i = _list.size() - 1; i > +0; i--)
{
delete _list[i];
}
_list.clear();
LeaveCriticalSection(&g_cs);
LeaveCriticalSection(&g_cs);
}
void ClientList::Broadcast(char* str, int len, CSocket* s3)//向客户端广播数据
{
for (int i = 0; i < _list.size(); i++)
{
if (_list[i] != s3)
{
_list[i]->Send(str, len);
}
}
}
void ClientList::ShowVec(const vector& valList)
{
}
CRITICAL_SECTION ClientList::g_cs;
vectorClientList::_list;
8、SocketEnum.h
#pragma once
#ifndef __ENUMTYPE_H__
#define __ENUMTYPE_H__
struct SocketEnum
{
typedef enum //定义类型
{
Invalid,//无效的
Tcp,
Udp
}SocketType;
typedef enum
{
Error = -1,
Success = 0,
InvalidSocket,
InvaliAddress,//无效的地址
InvalidPort,//无效的端口
ConnectionRefused,//连接被拒绝
Timedout,//超过时限
Ewouldblock,
Notconnected,
Einprogress,
Interrupted,
ConnectionAborted,//连接中止
ProtocolError,//协议错误
InvalidBuffer,//无效缓冲区
ConnectionReset,
AddressInUse,
InvalidPointer,
WSAStartupError,
BindError,
ListenError,
UnknownError
}SocketError;
typedef enum
{
Receives = 0,
Sends = 1,
Both = 2,
}ShutdownMode;
};
#endif __ENUMTYPE_H__