socket实现类群聊功能

使用socket做了一个类似群聊功能的demo,来记录这两天学习socket的成果。

在实现功能之前先看下sockect通信的基本流程:

socket服务端:

1.socket():创建套接字

2.bind():将创建的套接字绑定到一个本地地址和端口上

3.listen():监听套接字,准备接受客户请求

4.accept():接收客户端请求,返回一个对应此连接新套接字

5.用accept()返回的套接字和客户端进行通信,recv()/send() 接受/发送信息

6.返回,等待另一个客户请求

7.关闭套接字

 

socket客户端:

1.socket():创建套接字

2.connect():建立到达服务器的连接

3.与服务器进行通信,recv()/send()接受/发送信息

5.close():关闭客户连接

 

服务器的关键实现有两点:

1.群聊即一个客户端发送的消息,服务器广播给该群内在线的所有客户端,那么服务器需要用一个容器记录群内客户端的在线情况:链接上来的客户端放入容器,断开链接从容器中移除;

2.方便管理每收到一个链接创建一个线程来管理对应的通信;

实现代码如下:


#include <iostream>
#include <strstream>
#include <set>
#include <string>
#include <thread>
#include "winsock.h"

#pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
using namespace std;

std::set<SOCKET> m_socketSet;
std::string intToString(int v){
	std::strstream ss;
	ss << v;
	std::string str;
	ss >> str;
	return str;
}

int main()
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	if (::WSAStartup(sockVersion, &wsaData) != 0)
	{
		exit(0);
	}

	SOCKET s_handle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s_handle == INVALID_SOCKET)
	{
		printf("failed socket()");
		return 0;
	}
	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(4567); //1024 ~ 49151:普通用户注册的端口号
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (::bind(s_handle, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		printf("failed bind()");
		return 0;
	}
	if (::listen(s_handle, 10) == SOCKET_ERROR)
	{
		printf("failed listen()");
		return 0;
	}

	sockaddr_in remoteAddr;
	int nAddrLen = sizeof(remoteAddr);
	char szText[] = "hello client...\n";
	void handleClient(SOCKET sClient);
	while (true)
	{
		SOCKET sClient = ::accept(s_handle, (SOCKADDR*)&remoteAddr, &nAddrLen);
		if (sClient == INVALID_SOCKET)
		{
			printf("Failed accept()");
		}
		else
		{
			m_socketSet.insert(sClient);
			//记录链接上来的客户端
			printf("connect sucess:%s:%s \n", inet_ntoa(remoteAddr.sin_addr),intToString(sClient).c_str());
			::send(sClient, szText, strlen(szText), 0);
			//通知客户端链接成功
			auto pt = std::thread(std::bind(handleClient, sClient));
			pt.detach();
			//创建子线程 处理对应客户端的逻辑
		}
	}

	closesocket(s_handle);
	//关闭服务器套接字
	getchar();
	return 0;
}

void handleClient(SOCKET sClient)
{
	std::string msg = "hello " + intToString(sClient);
	while (true)
	{
		char buff[256] = { 0 };
		int nRecv = ::recv(sClient, buff, 256, 0);
		if (nRecv > 0)
		{
			buff[nRecv] = '\0';
			printf("recv data:%s\n", buff);
			int code = strcmp(buff, "hello everyone ...\0");
			//判断是否广播
			if (code == 0)
			{
				std::string notice = "hi everybody i am " + intToString(sClient);
				for (auto handle:m_socketSet)
				{
					//便利在线的客户端广播
					::send(handle, notice.c_str(),notice.size(), 0);
				}
			}
			else{
				//与对应客户端通讯
				::send(sClient, msg.c_str(), msg.size(), 0);
			}
		}
		else{
			break;
		}
	}
	m_socketSet.erase(sClient);
	//链接断开 从容器中删除
	closesocket(sClient);
	//关闭套接字
}

客户端部分主要是通络通信线程与主线程分离,实现代码如下:

// SocketClient.cpp : 定义控制台应用程序的入口点。
//

#include <stdio.h>
#include <tchar.h>
#include "wrapper.h"

int _tmain(int argc, _TCHAR* argv[])
{
	wrapper::getInstance()->createSocket();
	char buff[1024] = { 0 };
	while (true)
	{
		if (wrapper::getInstance()->getServerRespond())
		{
			printf("please input msg:\n");
			gets(buff);
			wrapper::getInstance()->sendMsg(buff, strlen(buff));
		}
	}
	getchar();
	return 0;
}

客户端封装的网络通信类:

wrapper.h

#ifndef __wrapper__
#define __wrapper__

#include <thread>
#include "winsock.h"

class wrapper
{
public:
	wrapper();
	~wrapper();

	static wrapper *getInstance();
	void createSocket();
	bool sendMsg(const char *msg,int len);
	bool getServerRespond();
protected:
	void init();
	void listenCheck();
private: 
	SOCKET          m_sockId;
	std::thread     m_thread;
	bool            m_serverRespond;
};

#endif // __wrapper_SCENE_H__

wrapper.cpp

#include "wrapper.h"

#pragma comment(lib, "WS2_32") // 链接到WS2_32.lib

static wrapper *instance=nullptr;
wrapper *wrapper::getInstance()
{
	if (!instance)
	{
		instance = new wrapper;
	}
	return instance;
}

wrapper::wrapper()
:m_sockId(INVALID_SOCKET)
, m_serverRespond(false)
{
	this->init();
}

wrapper::~wrapper()
{

}

void wrapper::init()
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	if (::WSAStartup(sockVersion, &wsaData) != 0)
	{
		printf("init socket failed \n");
	}
}

void wrapper::createSocket()
{
	if (m_sockId != INVALID_SOCKET){
		return;
	}
	m_sockId = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_sockId == INVALID_SOCKET)
	{
		printf("failed socket() \n");
		return;
	}
	// 也可以在这里调用bind函数绑定一个本地地址
	// 否则系统将会自动安排
	// 填写远程地址信息
	sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(4567);
	// 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
	// 如果你的计算机没有联网,直接使用127.0.0.1即可
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (::connect(m_sockId, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
	{
		printf("failed connect() \n");
		return;
	}

	m_thread = std::thread(std::bind(&wrapper::listenCheck,this));
	m_thread.detach();
	//创建网络线程 并与主线程分离
}
	
void wrapper::listenCheck()
{
	char buff[256];
	while (TRUE)
	{
		//从服务器端接收数据
		int nRecv = ::recv(m_sockId, buff, 256, 0);
		if (nRecv > 0)
		{
			buff[nRecv] = '\0';
			printf("%s\n", buff);
			m_serverRespond = true;
		}
		else{
			break;
		}
	}
	// 关闭套节字
	::closesocket(m_sockId);
	m_sockId = INVALID_SOCKET;
}

bool wrapper::sendMsg(const char *msg, int len)
{
	if (m_sockId != INVALID_SOCKET)
	{
		// send msg to server
		m_serverRespond = false;
		int size= ::send(m_sockId, msg, len, 0);
		return size;
	}
	else{
		this->createSocket();
		printf("reconnect server... \n");
	}
	return false;
}

bool wrapper::getServerRespond()
{
	return m_serverRespond;
}

这样一个简单的类群聊功能就实现了,打开两个客户端,一个客户端输入hello everyone ... 并回车,另一个客户端就收到了消息。

demo地址:https://github.com/jjinglover/SocketDemo

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值