严格的来说吧,这个算是我学习C++后第一个写的不成熟的小程序,现在还没有毕业,一切的东西都算是学习中的小小动手玩乐。
这个是基于SOCKET完成端口而实现的C/S简易聊天工具,没有前端界面,就代码而言应该来说是很冗余和漏洞百出的。贴出来的目的在于将来有所成长后,回头看看曾经写的所谓“学生式的代码”。
这份代码也是具体学了小猪前辈的代码而有所得的东西。
原文:http://blog.csdn.net/piggyxp/article/details/6922277
具体的介绍就不多说了,毕竟这是写给以后的自己看的。
大体思路为:
1、构建IO操作结构体以及对应的数组
2、构建Socket操作的结构体以及对应的数组
3、用AcceptEx指针来获取相应的客户端信息
1.对于结构体的定义
在IO操作结构体当中,定义重叠结构体,然后再后面接上每个网络操作对应的Socket以及缓冲区等信息。
typedef struct _PER_IO_CONTEXT
{
OVERLAPPED m_Overlapped; //每个socket有一个重叠结构
SOCKET m_SocketAccept; //这个网络操作对应的socket
WSABUF m_wsaBuf; //缓冲区
char m_szBuffer[MAX_BUFFER_LEN]; //WSABUF存放字符的缓冲区
OPERATION_TPYE m_OpType; //枚举对象,标识网络操作的类型
//初始化资源
_PER_IO_CONTEXT()
{
ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
m_SocketAccept = INVALID_SOCKET;
m_wsaBuf.buf = m_szBuffer;
m_wsaBuf.len = MAX_BUFFER_LEN;
m_OpType = NONE;
}
//释放socket资源
~_PER_IO_CONTEXT()
{
if (m_SocketAccept != INVALID_SOCKET)
{
ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
ZeroMemory(&m_szBuffer, MAX_BUFFER_LEN);
m_wsaBuf.buf = m_szBuffer;
m_wsaBuf.len = MAX_BUFFER_LEN;
m_OpType = NONE;
}
}
// 重置缓冲区内容
void ResetBuffer()
{
ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
}
}PER_IO_CONTEXT;
然后就是Socket对应的结构体了
typedef struct _PER_SOCKET_CONTEXT
{
SOCKET m_Socket; //每个客户端连接的socket
SOCKADDR_IN m_ClientAddr; //每个客户端的地址
char m_username[40]; //存放用户名
//初始化
_PER_SOCKET_CONTEXT() {
m_Socket = INVALID_SOCKET;
//将m_ClientAddr这个位置后面的m_ClientAddr长度个字节用0填上
//即对m_ClientAddr进行清零
memset(&m_ClientAddr, 0, sizeof(m_ClientAddr));
}
//释放资源
~_PER_SOCKET_CONTEXT()
{
if (m_Socket != INVALID_SOCKET)
{
closesocket(m_Socket);
m_Socket = INVALID_SOCKET;
}
}
}PER_SOCKET_CONTEXT;
当然,每个结构体也有对应的操作,因为对于代码编程理解有限,所以采用比较笨拙的方式定义相应的方法
class PER_IO_CONTEXT_ARR
{
private:
//创建io结构体数组,用来存放每个socket对应的io操作
PER_IO_CONTEXT *IO_CONTEXT_ARR[2048];
public:
int num=0; //计数
//获取编号
PER_IO_CONTEXT * GetARR(int i)
{
return IO_CONTEXT_ARR[i];
}
//循环遍历IO操作数组,通过遍历所有位置,如果为0.则表示可以存放新的IO操作,后面只需要将数组全部填0,就可以初始化数组,并且在移除IO操作后,可以在原来位置上放心的IO操作。
PER_IO_CONTEXT* GetNewIoContext()
{
for (int i = 0; i < 2048; i++)
{
//如果某一个IO_CONTEXT_ARRAY[i]为0,表示哪一个位可以放入PER_IO_CONTEXT
if (IO_CONTEXT_ARR[i] == 0)
{
IO_CONTEXT_ARR[i] = new PER_IO_CONTEXT();
num++;
return IO_CONTEXT_ARR[i];
}
}
}
//如果IO操作数组中某个io操作完成,那么移除该IO操作,并且用0替换该位置的值,以方便新的IO操作存放
//新增了一个IO操作,NUM++ 所以只需要循环遍历前num个成员就可以了
void RemoveContext(PER_IO_CONTEXT * RContext)
{
for (int i = 0; i < num; i++)
{
if (IO_CONTEXT_ARR[i] == RContext)
{
IO_CONTEXT_ARR[i]->~_PER_IO_CONTEXT();
IO_CONTEXT_ARR[i] = 0;
//移除一个IO操作,计数-1
num--;
break;
}
}
return;
}
};
class PER_SOCKET_CONTEXT_ARR
{
private:
//创建socket结构体数组
PER_SOCKET_CONTEXT * SOCKET_CONTEXT_ARR[2048];
public:
int num = 0; //计数
//循环判定socket结构体数组中那个位置为0,表示该位置可以存放新的socket信息,同IO操作数组一样,初始化后,就可以从0位置开始存放,并且因为和操作是一对一关系,因此socket结构体数组和io操作数组下标应该是对应关系,这样在后续相关操作过程中,通过下标查询相应的数组,就可以得到例如客户端的地址,端口,和相对应的socket。
PER_SOCKET_CONTEXT* GetNewSocketContext(SOCKADDR_IN* addr, char *u)
{
for (int i = 0; i < 2048; i++)
{
//如果某个位置上的值为0,表示该位置可以存放新的socket结构体信息
if (SOCKET_CONTEXT_ARR[i] == 0)
{
SOCKET_CONTEXT_ARR[num] = new PER_SOCKET_CONTEXT();
//该位置存放新的socket结构体信息,同时我们也把客户端的相应信息写入进去
//将传入进来的addr拷贝进SOCKET_CONTEXT_ARR指针指向的位置,长度为地址长度
memcpy(&(SOCKET_CONTEXT_ARR[num]->m_ClientAddr),addr,sizeof(SOCKADDR_IN));