WinSocket——局域网聊天室

目录

1 聊天室

1.1 common.h

1.2 服务端

1.3 客户端

1.4 遇到的一些问题

2 附录

2.1 线程


 

聊天室具体知识点

1、基本Socket API函数及使用方法

socket,bind,connect,accept,listen,send,recv,closesocket,htonl(host to network long),ntohl(network to host long),inet_addr(点分十进制IP转成长整型),inet_ntoa(网络地址转成点分十进制IP),getsocketname(获取一个套接字名字),getpeername(获取与套接口相连的端地址)

2、用户验证登陆,用户私聊,用户之间传文件等

3、自定义协议,粘包问题,心跳包处理异常掉线,文件续传功能

1 聊天室

服务端

1、WinSock环境加载

WSAStartup(MAKEWORD(2, 2), &wsaData)——MAKEWORD,宏产生一个WORD值,标识版本

2、创建套接字——socket

SOCKET WSAAPI socket( int af, int type, int protocol );——参数分别是IP地址簇,传输模式,传输协议

成功则返回一个套接字——套接字与IP协议,传输协议,传输模式绑定在一起。

3、套接字绑定IP和端口——bind

int WSAAPI bind( SOCKET s, const sockaddr *name, int namelen );

把socket与IP地址和端口绑定在一起。

sockaddr_in和sockaddr二者字节数相同,都是16字节

struct sockaddr_in{
    short sin_family; //2字节
    u_short sin_port;  //2字节
    struct in_addr sin_addr;  //IPv4是4字节
    char sin_zero[8];  
};

struct sockaddr{
    u_short sa_family;  //2字节
    char sa_data[14];  //2+4+8
};

4、

ReadMe


+++++++++++++++++++++++++++++++
+                             +
+          聊天室              +
+                             +
+++++++++++++++++++++++++++++++


1 服务端
1.1 服务端程序流程
加载环境(WSAStartup)
——创建SOCKET(socket)
——SOCKET绑定IP和端口(bind)
——监听SOCKET(listen)
——等待客户端连接(accept)
——发送数据(send)
——接收数据(recv)
——关闭SOCKET(clostsocket)
卸载环境(WSACleanup)
PS:
    
1.2 socket链表
对于连接到服务器端的客户端(socket),服务器端要定义一个链表来管理它们——因为客户端的连接的断开具有随机性,所以使用链表

1.3 服务器监听线程
当服务器端调用accept函数等待客户端连接时,是出于阻塞状态的,所以了避免阻塞主线程,为该过程创建一个线程。——问题是如何让主线程等待该线程结束

1.4 客户消息处理线程
每来一个客户端连接的socket,就创建一个处理线程来与客户端进行交互

1.5 自定义协议
服务器端与客户但进行交互需要定义协议
协议分为协议头和协议体——协议头中定义了交互的消息类型,长度等信息;协议体中则是交互的数据。


2 客户端
2.1 客户端程序流程
加载环境(WSAStartup)
——创建SOCKET(socket)
——连接服务器(connect):需要IP地址和端口
——发送数据(send)
——接收数据(recv)
——关闭SOCKET(closesocket)
卸载环境(WSACleanup)

PS:

2.2 登陆与验证
客户端登陆需要发送用户信息以及密码等给服务器端——这里也要用到自定义协议来承载这些信息
服务器端收到用户信息,检查是否合法,合法则向客户端返回验证通过信息

2.3 显示其他在线用户信息,显示聊天信息
(1)连接服务器并收发消息的线程
(2)显示接收信息的线程


3 自定义协议
这一部分可以先设计简单一些——协议头包含消息类型,协议体的大小
对于更复杂的协议,可以学习参考TCP协议,HTTP协议等
3.1 协议头

3.2 协议体


4 同步I/O和异步I/O



5 聊天室的一些特性
5.1 用户上线/下线通知
(1)某用户上线,则把该用户信息发送给其他用户;并把其他用户在线信息发送给该用户
(2)用户下线,则把该用户的下线信息发送给其他用户

 

1.1 common.h

服务端和客户端所共有的一部分。

1、加载/卸载socket环境

2、发送数据,接收数据

3、服务端和客户端通信的消息

common.h


WinSocket——局域网聊天室
17/100
weixin_43390831


C++
#ifndef __COMMON_H__
#define __COMMON_H__

#include 
#include 
#include 
#include 
#include 
#pragma comment(lib, "ws32_lib")

#define MAX_IP_LEN 16  //放在common.cpp中

#define MAX_USER_LEN 50  //用户名最大长度
#define MAX_PASS_LEN 50  //密码最大长度


//消息类型
#define MSG_TYPE_LOGIN 1  

#define MSG_TYPE_CLIENT_ONLINE 4
#define MSG_TYPE_CLIENT_OFFLINE 5

#define MSG_TEXT_TOALL 6


//消息类
class CMsgHead{
public:
    char m_type;  //当前消息类型
	UINT m_cnLen;  //协议头之后的消息长度——body的长度
	
	CMsgHead()
	{
		m_type = -1;
		m_cnLen = 0;
	}
	
	CMsgHead(char nType)
	{
		m_type = nType;
		m_cnLen = 0;
	}
	
	CMsgHead(char nType, UINT cnLen)
	{
		m_type = nType;
		m_cnLen = cnLen;
	}
};


// 登陆信息 
struct CLoginInfo
{
    TCHAR m_szUser[MAX_USER_LEN];
    TCHAR m_szPass[MAX_PASS_LEN];
    
    CLoginInfo()
    {
        memset(m_szUser, 0, MAX_USER_LEN);
        memset(m_szPass, 0, MAX_PASS_LEN);
    }
    
    CLoginInfo(TCHAR * lpszUser, TCHAR * lpszPass)
    {
        if (!lpszUser || !lpszPass) return;
        memcpy(m_szUser, lpszUser, MAX_USER_LEN);
        memcpy(m_szPass, lpszPass, MAX_PASS_LEN);
    }
};


//客户信息
struct CClientInfo
{
    TCHAR m_szIpAddr[MAX_IP_LEN];
    CClientInfo()
	{
	    memset(m_szIpAddr, 0, MAX_IP_LEN);
	} 
	
	CClientInfo(TCHAR * lpszIpAddr)
	{
	    if (!lpszIpAddr) return;
	    memset(m_szIpAddr, 0, MAX_IP_LEN);
	    memcpy(m_szIpAddr, lpszIpAddr, MAX_IP_LEN);
	}
	
	CClientInfo(CClientInfo *tInfo)
	{
	    if (!tInfo) return;
	    memset(m_szIpAddr, 0, MAX_IP_LEN);
	    memcpy(m_szIpAddr, tInfo->m_szIpAddr, MAX_IP_LEN);
	}
}; 


//加载socket环境
BOOL WinSockInit();
//卸载socket环境
void WinSockUnload();


//select
//探测一个或多个socket的状态,去执行同步I/O 
//如果必要,可以等待 
BOOL SOCKET_Select(SOCKET hSocket, int nTimeOut = 100, BOOL bRead = TRUE);

//发送数据
int SendData(SOCKET sock, char * buf, unsigned int len);
//接收数据
int RecvData(SOCKET sock, char * buf, unsigned int len);


#endif



 

common.cpp


#include "common.h"

BOOL WinSockInit()
{
    WSADATA wsaData = {0};
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) == NO_ERROR){
        return TRUE;
    }
    else{
        return FALSE;
    }
}

void WinSockUnload()
{
    WSACleanup();
}

BOOL SOCKET_Select(SOCKET hSocket, int nTimeOut, BOOL bRead)
{
    //int WSAAPI select (int nfds/*for compatibility*/, fd_set *readfds, 
	//                     fd_set *writefds, fd_set *exceptfds, const timeval *timeout); 
	
	// readfds——listen and accept succeed,data is available for reading
	//            connection has been closed/reset/terminated
	//            说明缓冲的数据是可读的 ,此时可以调用recv函数 
	// writefds——connect succeed,data can be sent 
	//            说明缓冲可写 ,此时可以调用send函数 
    fd_set readfds;
	fd_set writefds;
	fd_set exceptfds;
	
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	
	FD_SET(hSocket, &readfds);
	
	timeval timeout;
	timeout.tv_sec = nTimeOut;
	timeout.tv_usec = 0;
	
	int iRet = 0;  //返回socket句柄的总数
	               //在timeout时间内,监测socket的状态 
	iRet = select(0, &readfds, NULL, NULL, &timeout);
	
	if (iRet <= 0)
	{
	    std::cout << "error or timeout" << std::endl;
	    return FALSE;
	}
	 
    return TRUE;
}


//发送数据
//int send(__in SOCKET s, __out char * buf/*发送缓冲区*/, __in int len/*缓冲区大小*/, __in int flags);  //返回发送的字节数
int SendData(SOCKET sock, char * buf, unsigned int len)
{
	unsigned int offset = 0;
	while (offset < len)
	{
		int nSend = send(sock, buf + offset, len - offset, 0);
		if (SOCKET_ERROR == nSend)
		{
			return -1;
		}
		offset += nSend;
	}
	return 1;
}


//接收数据
//int recv(__in SOCKET s, __out char * buf, __in int len/*缓冲区大小*/, __in int flags);  //返回接收了多少字节数据,接收失败则返回SOCKET_ERROR(值为-1)	
int RecvData(SOCKET sock, char * buf, unsigned int len)
{
	unsigned int offset = 0;  //buf起点的偏移,之后接收数据存放在offset之后
	while (offset < len)
	{
	    int nRecv = recv(sock, buf + offset, len - offset, 0);  //把数据存放在偏移位置之后,避免之前的数据被覆盖
        if (SOCKET_ERROR == nRecv)
		{
            return -1;
        }
        if (0 == nRecv)  //接收完所有数据
		{
            return 0;
        }
        
        offset += nRecv;		
	}
	return 1;
}

 

1.2 服务端

1、服务端编码流程

CServer.h

#ifndef __CSERVER_H__
#define __CSERVER_H__

#include "common.h"

#define __COMMON_H__

//#define __COMMON_H__
using std::list;
using std::string;

/*
1、加载Socket环境
2、新建Socket
3、绑定Socket
4、监听Socket
5、等待Socket连接
6、发送数据/接收数据
7、卸载Socket环境 
*/

//服务端的设计——

//(1) 一个监听线程,等待客户端的连接

//(2) 一个处理线程,接收客户端数据,并处理

//(3) 有些接口需要暴露,有些接口不能暴露

//(4) 如何只提供全局唯一服务类对象——static

//(5) void*空间可以强制转换成想要的类型

//(6) 对于错误码,可以把它特化成类——继承自基类错误码,然后定义个处理函数,这样就可以用多态来处理不同的错误码(上报错误信息)


//服务器端管理每个客户端节点
//记录每个连接的客户端节点
class CClientItem
{
public:
    SOCKET m_Socket;  //accept返回的客户端socket
	TCHAR m_szIpAddr[MAX_IP_LEN];  //IP地址
	HANDLE m_hThread;  //处理该客户单socket的线程
	
	CClientItem()
	{
		m_Socket = INVALID_SOCKET;
		ZeroMemory(m_szIpAddr, MAX_IP_LEN);
		m_hThread = NULL;
	}
};


//一个服务器类应该具备哪些接口和成员 
class CServer{
private:
    void RemoveTargetClient(CClientItem *pItem);


public:
    CServer();
    ~CServer();
    
    BOOL StartServer(UINT nPort);
    void StopServer();
    
    //处理客户端发来的请求数据 
    BOOL ProcessMsg(CMsgHead &msg, TCHAR *pCnt, CClientItem *pClient);  //入参为协议头,协议体,客户端信息

//--------------两个线程的参数是固定的,所以设置成static,无需this参数-------------------//
    //静态的原因:acceptProc只有一个参数,static可以没有this参数 
    static DWORD WINAPI acceptProc(LPVOID lpParameter);  //开了线程,用于监听端口等待客户端的连接 
    
    //用于处理不同的连接,每来一个连接,则开启一个线程 
    static DWORD WINAPI ClientThreadProc(LPVOID pParameter);  //入参为客户端节点信息——socket,IP,线程ID
//---------------------------------------------------------------------------------------//
	
	//检查用户的登陆信息,正确则授权 
	BOOL CheckUserInfo(TCHAR * lpszUser, TCHAR * lpszPass);
	
	//把其他用户的在线消息发送给该上线用户——全量 
	BOOL SendOtherUserInfo(CClientItem * pClient);
	
	//把登陆用户发送给其他用户
	
	//当有用户退出时,把用户退出的消息发送给其他玩家——增量 
	BOOL SendTargetToOnlineUser(CClientItem * pClient, BOOL bOnLine);
	
	//等待其他线程完成 
	DWORD WaitForAllThreads(UINT nCount, HANDLE * lpHandles, 
	                            BOOL bWaitAll, DWORD dwMillionseconds); 
private:
	SOCKET m_ListenSock;  //套接字 
	UINT m_nPort;  //端口
	//定义该静态变量后,需要在源文件中进行初始化,而不能在头文件中进行初始化
	//否则会报——重复定义错误。如果不在头文件中初始化,且也不在源文件中初始化,则无法使用该变量 
	static list m_ClientList;  //一个队列或链表来管理客户端的连接
};



//获取全局唯一服务器对象
CServer * GetServer();



#endif

CServer.cpp


#include "CServer.h"
#define __CSERVER_H__

CServer * GetServer(){  //这里使用的是单例模式,尽管多次调用,也只会有一个CServer实例
	static CServer * pServer = NULL;
	if (!pServer){
		pServer = new CServer();  //没有考虑到多线程,即只有一个线程会调用这个函数,主线程
	}
	return pServer;
}

CServer::CServer(){
	m_ListenSock = INVALID_SOCKET;
	m_nPort = 0;
}

CServer::~CServer(){

}

list CServer::m_ClientList;

//---------------------------//
//         具体功能实现      //
//---------------------------//

//-------------------------------//
// 
//启动服务 
//启动接收服务,避免阻塞主界面 
//有多个客户端来连接,如果不用线程,使用while循环则会阻塞在这里 
//-------------------------------//
BOOL CServer::StartServer(UINT nPort){
	m_nPort = nPort;
    //启动一个线程
    DWORD threadId = 0;
    //启动accept线程,等待连接,避免阻塞主界面 
	HANDLE hThread = CreateThread(
	                        NULL, 
	                        0, 
							CServer::acceptProc, 
	                        this, //这里用了this指针目的是什么 
							0, 
							&threadId);
	if (hThread == NULL)
	{
		std::cout << "创建监听线程失败----" << std::endl; 
	    return FALSE;
	}
	
	WaitForSingleObject(hThread, INFINITE);
	return TRUE;
}


//删除客户端连接——这里的代码要注意一下
void CServer::RemoveTargetClient(CClientItem *pItem)
{
	if (!pItem) return;
	//首先找这个节点,如果找到则删除链表中的这个节点
	
    //CServer::m_ClientList.remove(pItem);	
	
	//当不使用该socket时,则关闭socket
	closesocket(pItem->m_Socket);  //关闭 socket
	CloseHandle(pItem->m_hThread);  //关闭线程句柄
	delete pItem;
}

//把其他用户的在线消息发送给该上线用户 
BOOL CServer::SendOtherUserInfo(CClientItem * pClient)
{
    if (m_ClientList.size() <= 1)
    {
        return TRUE;
    }
    
    std::vector vecInfo;
    
    for (list::iterator itr = m_ClientList.begin(); itr != m_ClientList.end(); ++itr)
    {
        if (!*itr || *itr == pClient) continue;
        CClientInfo info = CClientInfo((*itr)->m_szIpAddr);
        vecInfo.push_back(info);
    }
    //发送的是vec,接收的时候也要用vec解释 
    UINT nLen = vecInfo.size() * sizeof(CClientInfo);
    CMsgHead msgHead(MSG_TYPE_CLIENT_ONLINE, nLen);
    
    int iHeadSend = SendData(pClient->m_Socket, (char *)&msgHead, sizeof(msgHead));
	int iBodySend = SendData(pClient->m_Socket, (char *)&vecInfo, nLen);
	
	if (iHeadSend <= 0 || iBodySend <= 0)
	{
	    RemoveTargetClient(pClient);
	    return FALSE;
	} 
    
    return TRUE;
    
}

//发送某用户的上线或下线消息给其他用户 
//把发送事件(即其他在线用户)放入到一个队列中,单独一个线程负责发送 
BOOL CServer::SendTargetToOnlineUser(CClientItem * pClient, BOOL bOnLine)
{
    if (m_ClientList.size() <= 1)
    {
        return TRUE;
    }
    
    CClientInfo clientInfo = CClientInfo(pClient->m_szIpAddr);
    
    //发送类型 
    UINT nType = 0;
	if (bOnLine)
	{
	    nType = MSG_TYPE_CLIENT_ONLINE;
	}
	else
	{
	    nType = MSG_TYPE_CLIENT_OFFLINE;
	}
    CMsgHead msgHead(nType, sizeof(clientInfo));
    
    for (list::iterator itr = m_ClientList.begin(); itr != m_ClientList.end(); ++itr)
    {
        if (!*itr || *itr == pClient) continue;
	    int iHeadSend = SendData((*itr)->m_Socket, (char *)&msgHead, sizeof(msgHead));
		int iBodySend = SendData((*itr)->m_Socket, (char *)&clientInfo, sizeof(clientInfo));
	}
    
    return TRUE;
}

//客户端消息处理程序
//根据客户端发送过来的消息类型进行不同的处理 
BOOL CServer::ProcessMsg(CMsgHead &msg, TCHAR *pCnt, CClientItem *pClient)
{
    //客户端发送Msg 
	//
	switch (msg.m_type)
	{
	    case MSG_TYPE_LOGIN:
	        {
	            //验证登陆信息
				//如果验证成功,则把授权结果返回给客户端 
	            BOOL bAuthResult = FALSE; 
	            CLoginInfo * pLoginInfo = (CLoginInfo *)pCnt; 
	            BOOL bRet = CheckUserInfo(pLoginInfo->m_szUser, pLoginInfo->m_szPass);
	            
	            if (bRet)
	            {
	                bAuthResult = TRUE;
	            }
	            
	            CMsgHead msgHead(MSG_TYPE_LOGIN, sizeof(bAuthResult));
	            int iHead = SendData(pClient->m_Socket, (char *)&msgHead, sizeof(msgHead));
	            int iBody = SendData(pClient->m_Socket, (char *)&bAuthResult, sizeof(bAuthResult));
	            if (iHead <= 0 || iBody <= 0)
	            {
	                SendTargetToOnlineUser(pClient, FALSE); 
	                RemoveTargetClient(pClient);
	                return TRUE;
	            }
	            
	            //如果该用户认证成功,则把其他在线用户的信息发送给该用户
				if (bAuthResult == TRUE)
				{
				    SendOtherUserInfo(pClient);
				    SendTargetToOnlineUser(pClient, TRUE);
				}
	        }
	        break;
	    
	    default: break;
	    
	} 
	
	return TRUE;
}


//客户端连接处理程序
DWORD WINAPI CServer::ClientThreadProc(LPVOID pParameter)
{
	CClientItem *pClient = (CClientItem *)pParameter;
	while (TRUE)
	{
        if (SOCKET_Select(pClient->m_Socket))
		{
			CMsgHead msg;
			//首次接收数据,先接收协议头
			int iRet = RecvData(pClient->m_Socket, (char *)&msg, sizeof(msg));
			if (iRet < 0)  //小于0说明接收数据异常,把客户端删除
			{
			    m_ClientList.remove(pClient);  //——移除无效节点 
				return -1;  //错误返回一个数字
			}
			
			//如果接收协议头成功,则继续接收数据
			TCHAR * pszBuf = new TCHAR(msg.m_cnLen);  //分配一个协议体——body,那么大的长度缓冲区
			ZeroMemory(pszBuf, msg.m_cnLen);
			
			iRet = RecvData(pClient->m_Socket, (char *)pszBuf, msg.m_cnLen);  //接收数据
			if (iRet <= 0){
				//接收数据失败
				delete [] pszBuf;
				m_ClientList.remove(pClient);  //——移除无效节点 
				return -1;  //错误返回一个数字
			}
			
			GetServer()->ProcessMsg(msg, pszBuf, pClient);  //执行客户端信息处理函数
			delete [] pszBuf;
		}
	}
	return 1;
}

BOOL CServer::CheckUserInfo(TCHAR * lpszUser, TCHAR * lpszPass)
{
    return TRUE;  //可以到数据库中校验,也不可在服务器本地校验 
}


//---------------------------//
//
//--------------------------//
DWORD CServer::acceptProc(LPVOID lpParameter)
{
    CServer* pThis = (CServer*)lpParameter;
    
    //----建立一个套接字----// 
    pThis->m_ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (pThis->m_ListenSock == INVALID_SOCKET){
	    std::cout << "建立socket失败----create socket failed : " << GetLastError() << std::endl;
		return 1;
	}
	std::cout << "socket success" << std::endl;
	
	//----套接字绑定IP地址和端口----//
	// int bind(_In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen) 
	sockaddr_in sServer = {0};
	sServer.sin_family = AF_INET;
	//sServer.sin_addr.s_addr = htonl(INADDR_ANY);  //host to network long, sin_addr是一个u_long(IPv4地址是一个无符号long)
	                                              //INADDR_ANY——绑定本地的所有IP
	//unsigned long WSAAPI inet_addr(__in IN const char FAR * cp); 
	sServer.sin_addr.s_addr = inet_addr("127.0.0.1");  //绑定特定IP地址 
			
	sServer.sin_port = htons(pThis->m_nPort);  //host to network short
	//no error:return 0;error:return SOCKET_ERROR 
	if (bind(pThis->m_ListenSock, (sockaddr*)&sServer, sizeof(sServer)) == SOCKET_ERROR){
		std::cout << "绑定端口失败----bind error : " << GetLastError() << std::endl; 
		return 1;
	}
	std::cout << "bind success" << std::endl;
	
	//----监听套接字----//
	// int listen(_In_ SOCKET s/*绑定了IP和端口的套接字*/,  _In_ int backlog/*监听队列中允许保持的尚未处理的最大连接数*/);
	//backlog——多个客户端同时连接服务器的限制(是一个并发限制),最大值是 SOMAXCONN 
	//no error : return 0; error : return SOCKET_ERROR 
	if (listen(pThis->m_ListenSock, SOMAXCONN) == SOCKET_ERROR){
		std::cout << "监听套接字失败----listen error : " << GetLastError() <m_ListenSock, (sockaddr *)&clientAddr, &iLen);
		if (accSock == INVALID_SOCKET){
			continue;
		}
			
		//把接收到的客户端放在一个链表里,用于客户端的收发	
		  //放在accept调用后面
	    {
		    //在accept函数接收到socket后,加入队列
		    CClientItem *pItem = new CClientItem();
			pItem->m_Socket = accSock;
			
			char* strIp = new char[MAX_IP_LEN]; 
			//strIp = inet_ntoa(clientAddr.sin_addr);  //返回char*类型,inet_ntoa将网络地址转成点分十进制IP字符串
			                                         //inet_addr,将点分十进制IP转成网络字节IP(一个u_long类型)
			
			memcpy(strIp, inet_ntoa(clientAddr.sin_addr), MAX_IP_LEN);
			memcpy(pItem->m_szIpAddr, strIp, MAX_IP_LEN);
			
		    //创建新线程并挂起 
			pItem->m_hThread = CreateThread(NULL, 0, ClientThreadProc, pItem, CREATE_SUSPENDED, NULL);
		    m_ClientList.push_back(pItem);
		    ResumeThread(pItem->m_hThread);  //唤醒线程 
	    }
	}
	
	std::cout << "acceptPro will be done" << std::endl; 
	return 0;
}

DWORD CServer::WaitForAllThreads(UINT nCount, HANDLE * lpHandles, 
	                            BOOL bWaitAll = TRUE, DWORD dwMillionseconds = INFINITE)
{
    //这个函数在哪里调用比较合适呢
	//必须确保线程开启后,该函数才会被调用 
    HANDLE * hThreads = new HANDLE[m_ClientList.size()];
    
    int i = 0;
    for (list::iterator itr = m_ClientList.begin(); itr != m_ClientList.end(); ++itr, ++i)
    {
        if (!*itr) continue;
	    hThreads[i] = (*itr)->m_hThread;
	}
	
	return WaitForAllThreads(i, hThreads, bWaitAll, dwMillionseconds);
	
}


 

1.3 客户端

CClient.h

#ifndef __CCLIENT_H__
#define __CCLIENT_H__ 
#include 
#include "common.h"
using std::string;

//客户端,创建一个sokcet
//然后就可以使用该socket连接到服务器端的IP和port 
class CClient{

public:
    CClient();
	~CClient();
	
	//
	BOOL WINAPI ConnectServer(char * lpszIpAddr, UINT nPort, 
	                                 char * lpszUser, char * lpszPswd);
    
	//把登陆的用户名,密码发送给服务器端
	//此时就需要自定义协议——协议头和协议体 
    BOOL LoginProcess();
    
    //处理服务器端发送过来的程序 
    void ProcessMsg(CMsgHead msg, TCHAR * body);
    
    //发送聊天消息给服务端
	BOOL SendText();
    
private:

    TCHAR m_szServerIp[MAX_IP_LEN];
    UINT m_nPort;
    TCHAR m_szUser[MAX_USER_LEN];
    TCHAR m_szPass[MAX_PASS_LEN];
    
    SOCKET m_ConnectSocket;
};

#endif

CClient.cpp


#include "CClient.h"


CClient::CClient()
{
    memset(m_szServerIp, 0, MAX_IP_LEN);
    memset(m_szUser, 0, MAX_USER_LEN);
    memset(m_szPass, 0, MAX_PASS_LEN);
    
    m_ConnectSocket = INVALID_SOCKET;
}

CClient::~CClient()
{

} 

void CClient::ProcessMsg(CMsgHead msg, TCHAR * body)
{
    switch (msg.m_type)
    {
    case MSG_TEXT_TOALL : 
        {
            //服务器收到聊天记录,并把该聊天记录发送其他客户端 
            //客户但接收到这个消息,说明发过来的是线上某个客户聊天信息 
			 
        }
        break;
    case MSG_TYPE_CLIENT_ONLINE : 
        {
            //服务器收到上线消息,并把上线用户的信息发送其他给客户端 
            //客户端收到这个消息,说明发过来的是上线用户的信息
			 
        }
        break;
    case MSG_TYPE_CLIENT_OFFLINE : 
        {
            //服务器收到下线消息,并把下线消息发送给其他客户端
			//客户端收到这个消息,说明发过来的是下线用户的信息 
        }
        break;
    default : break; 
    }
}

BOOL CClient::LoginProcess()
{
    CMsgHead msgHead(MSG_TYPE_LOGIN, sizeof(CLoginInfo));
    CLoginInfo loginInfo(m_szUser, m_szPass);
    
    //先发送协议头,再发送协议体 
    int iHeadSend = SendData(m_ConnectSocket, (char *)&loginInfo, sizeof(msgHead));
    int iBodySend = SendData(m_ConnectSocket, (char *)&loginInfo, sizeof(loginInfo));
    
    if (iHeadSend >= 0 && iBodySend >= 0)
    {
        return TRUE;
    } 
    return FALSE;
}


BOOL CClient::ConnectServer(char * lpszIpAddr, UINT nPort, 
                                 char * lpszUser, char * lpszPass)
{
    if (!lpszIpAddr || nPort < 1 || nPort > 65535 || !lpszUser || !lpszPass)
    {
        return FALSE;
    }
    
    m_nPort = nPort;
	memcpy(m_szServerIp, lpszIpAddr, MAX_IP_LEN);
	memcpy(m_szUser, lpszUser, MAX_USER_LEN);
	memcpy(m_szPass, lpszPass, MAX_PASS_LEN);
	
	m_ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_ConnectSocket == INVALID_SOCKET)
	{
	    std::cout << "login error" << std::endl;
	    return FALSE;
	}
	
	//本地地址和网络地址的转换 
	sockaddr_in server = {0};
	server.sin_family = AF_INET;
	server.sin_port = htons(m_nPort);  //u_int -> u_short
	server.sin_addr.s_addr = inet_addr(lpszIpAddr); 
	
	
	if (connect(m_ConnectSocket, (sockaddr*)&server, sizeof(sockaddr)) == SOCKET_ERROR)
	{  //连接失败,则关闭socket 
	    std::cout << "connect failed" << std::endl;
	    closesocket(m_ConnectSocket);
		return FALSE;
	}
	
	BOOL bRet = LoginProcess();  //登陆把用户名和密码传给服务器端 
	if (!bRet)
	{
	   //连接失败 
	    std::cout << "Login failed" << std::endl;
	    closesocket(m_ConnectSocket);
		return FALSE;
	}
	
	while (TRUE)
	{
	    //判断socket是否为空,为空,则执行后面的步骤,避免阻塞 
	    if (SOCKET_Select(m_ConnectSocket))
	    {
	        //接收服务端发送过来的验证信息 
	        CMsgHead msg;
	        int iRet = RecvData(m_ConnectSocket, (char *)&msg, sizeof(msg));
	        if (iRet <= 0)
	        {
	            //接收协议头失败 
	            std::cout << "recv server msg head failed" << std::endl;
	            closesocket(m_ConnectSocket);
	            return FALSE;
	        }
	        
	        //验证成功后接收服务器端消息 
	        TCHAR * pszBuf =new TCHAR[msg.m_cnLen];
	        memset(pszBuf, 0, msg.m_cnLen);
	        
	        //接收协议体失败 
	        iRet = RecvData(m_ConnectSocket, (char *)pszBuf, msg.m_cnLen);
	        
	        if (iRet <= 0)
	        {
	            delete [] pszBuf;
	            std::cout << "recv server msg body failed" << std::endl;
	            closesocket(m_ConnectSocket);
	            return FALSE;
	        }
	        
	        //验证成功,则交给处理过程去处理 
			ProcessMsg(msg, pszBuf);
			delete [] pszBuf;   
	    }
	}
	
	return TRUE;	 
}


BOOL CClient::SendText()
{
    printf("%s : ", m_szUser);
	std::string str;
	std::cin >> str;
	
	UINT nLen = str.length() + 1;
	char * pText = new char[nLen];
	memcpy(pText, str.c_str(), nLen);
	
	CMsgHead msgHead(MSG_TEXT_TOALL, nLen);
	
	int iHeadSend = SendData(m_ConnectSocket, (char *)&msgHead, sizeof(msgHead));
	int iBodySend = SendData(m_ConnectSocket, (char *)pText, nLen);
	
	if (iHeadSend <= 0 || iBodySend <= 0)
	{
	    std::cout << "send text failed" << std::endl;
	    return FALSE;
	}
	return TRUE;
}

 

1.4 遇到的一些问题

1、对于static数据成员,只能在类中声明,在源文件中定义

2、对于static函数成员,不能访问non-static成员,因为没有this指针

 

 

2 附录

2.1 线程

1、线程创建

(1)CreateThread

WINBASEAPI HANDLE WINAPI 
CreateThread (
             LPSECURITY_ATTRIBUTES lpThreadAttributes,   //安全属性
             SIZE_T dwStackSize,   //线程堆栈大小
             LPTHREAD_START_ROUTINE lpStartAddress,  //线程函数地址
             LPVOID lpParameter,   //线程函数参数
             DWORD dwCreationFlags,   //创建标志
             LPDWORD lpThreadId);  //返回线程ID

(2)ThreadProc——线程函数原型要求

DWORD WINAPI ThreadPro(
                      LPVOID lpParameter);

线程函数必须符合这个原型。WINAPI,在声明函数时添加该宏即可。

PS:

WINAPI——是一种宏,声明了一种调用方式。 调用约定 定义函数从调用方接收参数的方式(msdn解释)调用约定有 _ _ pascal/ _ pascal/pascal、 _ _ cdecl/ _ cdecl/cdecl 或 _ _ stdcall/ _ stdcall/stdcall 。

当不加WINAPI是报错: [Error] invalid conversion from 'DWORD (*)(void*) {aka long unsigned int (*)(void*)}' to 'LPTHREAD_START_ROUTINE {aka long unsigned int (__attribute__((__stdcall__)) *)(void*)}' [-fpermissive]

从报错可以看出,在编译器时添加了函数签名:stdcall,这种

2、线程同步对象

(1)、互斥对象(Mutex)

(2)、事件对象(Event)

(3)、信号量(Semaphore)

(4)、临界区(critical section)、

(5)、可等待计时器(Waitable Timer)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(#,Windows)