用C++实现最基本的Socket通讯(一)

前言闲聊

冬去春来,想着今年定个小目标,逐步实现TCP和UDP的通讯连接,自定义通讯协议,几种I/O模型(阻塞,非阻塞,I/O多路复用,IOCP),当中会涉及到许多知识,就我目前的知识水平大概了解有多线程,线程锁,生产者消费者模式,观察者模式,Json序列化,心跳监听等。在此分类的文章我尽量使用面向对象的思想来写,因为本人也才接触服务器不久,希望在文中出现了错误,或是有一些更好的方法或建议,希望不吝赐教,共同进步。如果所写文章能够对你有所帮助,点个赞便是对我最大的鼓励。

简单的服务器

我用win32控制台创建一个SocketServerTest类

//SocketServerTest.h
    
#ifndef _H_SERVERTEST_
#define _H_SERVERTEST_

#include <WinSock2.h>

#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

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

	bool createSocket();//创建socket
	bool bandSocket(const char* ip, const unsigned short prot);//绑定本地地址和端口号
	bool listenSocket();//初始化监听并设置最大连接等待数量
	SOCKET acceptSocket();//接受请求连接的请求,并返回句柄
	void recvSocket(SOCKET& recvsocket);//接受客服端数据
	void sendSocket(SOCKET& sendsocket);//发送客户端数据
	void closeMySocket();//关闭连接

private:
	SOCKET m_nServerSocket;//绑定本地地址和端口号的套接口
};

#endif

下面我打算先实现一个简单的功能,就是一个客服端与我连接成功,我返回一个连接服务器成功消息,然后接下来等待客服端发来的消息,接受到之后,我返回一个消息接受成功,并关闭连接。

//ServerTest.cpp
#include "ServerTest.h"
#include <iostream>

using namespace std;

SocketServerTest::SocketServerTest():m_nServerSocket(-1)
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		cout << "Socket版本错误" << endl;
}

SocketServerTest::~SocketServerTest()
{
	closeMySocket();
}

void SocketServerTest::closeMySocket()
{
	if (m_nServerSocket != -1)
		closesocket(m_nServerSocket);	//关闭socket连接

	m_nServerSocket = -1;
	WSACleanup();	//终止ws2_32.lib的使用
}

bool SocketServerTest::createSocket()
{
	if (m_nServerSocket == -1)
	{
		m_nServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//设定TCP协议接口;失败返回INVALID_SOCKET
		if (m_nServerSocket != INVALID_SOCKET) 
		{
			cout << "服务器启动成功" << endl;
			return true;
		}
	}
	return false;
}

bool SocketServerTest::bandSocket(const char* ip, const unsigned short prot)
{
	int nRet = -1;
	if (m_nServerSocket != -1)
	{
		sockaddr_in Serveraddr;
		memset(&Serveraddr, 0, sizeof(sockaddr_in*));
		Serveraddr.sin_family = AF_INET;
		Serveraddr.sin_addr.s_addr = inet_addr(ip);
		Serveraddr.sin_port = htons(prot);
		nRet = bind(m_nServerSocket, (sockaddr *)&Serveraddr, sizeof(Serveraddr));	//绑定服务器地址和端口号;成功,返回0,否则为SOCKET_ERROR
	}

	if (nRet == 0)
	{
		cout << "绑定IP和端口成功" << endl;
		return true;
	}

	cout << "绑定IP和端口失败" << endl;
	return false;
}

bool SocketServerTest::listenSocket()
{
	int nRet = -1;
	if (m_nServerSocket != -1)
	{
		nRet = listen(m_nServerSocket, 5);//设定接受连接的套接字,以及设定连接队列长度;成功返回0,失败返回-1
	}
	if (nRet == SOCKET_ERROR)
	{
		cout << "监听绑定失败" << endl;
		return false;
	}
		
	cout << "监听绑定成功" << endl;
	return true;
}

SOCKET SocketServerTest::acceptSocket()
{
	SOCKET sClient = 0;
	sockaddr_in nClientSocket;
	int nSizeClient = sizeof(nClientSocket);
	while (m_nServerSocket != -1)
	{
		sClient = accept(m_nServerSocket, (sockaddr*)&nClientSocket, &nSizeClient);//接受客户端连接,阻塞状态;失败返回-1
		if (sClient == SOCKET_ERROR)
		{
			cout << "accept连接失败" << endl;
			return 0;
		}

		cout << "连接一个客户端成功" << endl;

		char mess[] = "sercer:与服务器连接成功!";
		send(sClient, mess, sizeof(mess), 0);//发送消息给客户端
		recvSocket(sClient);//接受客户端的消息
	}
	return 0;
}

void SocketServerTest::recvSocket(SOCKET& recvsocket)
{
	while (recvsocket != 0)
	{
		// 从客户端接收数据
		char buff[256];
		int nRecv = recv(recvsocket, buff, 256, 0);//从客户端接受消息
		if (nRecv > 0)
		{
			cout << buff << endl;
			char mess[] = "server:收到了你的消息,欢迎下次使用!";
			send(recvsocket, mess, sizeof(mess), 0);
			closesocket(recvsocket);
			recvsocket = 0;
		}
		else
		{
			cout << "客户端退出" << endl;
			return;
		}
	}
}

简单的客户端

我用win32控制台创建一个ClientSocketTest类,跟服务器结构大同小异,主要在于listen()和connect()上

//ClientSocketTest.h
#ifndef _CHIENTSOCKET_H_
#define _CHIENTSOCKET_H_

#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

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

	bool createSocket();
	void closeSocket();

	bool Myconnect(const char* ip, const unsigned short prot);
	void Mysend();
	void Myrecv();

	void outputMessage(const char* outstr);

private:
	SOCKET m_nLocalSocket;
	char m_message[100];
};

#endif

接下来我的想法是,首先连接服务器,收到服务器的确认消息后,发送消息给服务器,等待服务器返回,然后断开连接。

//ClientSocketTest.cpp
#include "ClientSocketTest.h"
#include <iostream>
#include <thread>

ClientSocketTest::ClientSocketTest()
{
	m_nLocalSocket = -1;
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		std::cout << "Socket版本加载失败" << std::endl;
}

ClientSocketTest::~ClientSocketTest()
{
	closeSocket();
}

void ClientSocketTest::closeSocket()
{
	if (m_nLocalSocket != -1)
		closesocket(m_nLocalSocket);	//关闭socket连接

	m_nLocalSocket = -1;
	WSACleanup();	//终止ws2_32.lib的使用
}

//创建一个socket
bool ClientSocketTest::createSocket()
{
	if (m_nLocalSocket == -1)
	{
		m_nLocalSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (m_nLocalSocket != INVALID_SOCKET)
		{
			outputMessage("客服端socket创建成功");
			return true;
		}
	}
	
	outputMessage("客服端socket创建成功");

	return false;
}

bool ClientSocketTest::Myconnect(const char* ip, const unsigned short prot)
{
	int nRet = SOCKET_ERROR;
	if (m_nLocalSocket != -1)
	{
		sockaddr_in m_nServeraddr;
		memset(&m_nServeraddr, 0, sizeof(m_nServeraddr));
		m_nServeraddr.sin_family = AF_INET;
		m_nServeraddr.sin_port = htons(prot);
		m_nServeraddr.sin_addr.s_addr = inet_addr(ip);
		nRet = connect(m_nLocalSocket, (sockaddr*)&m_nServeraddr, sizeof(m_nServeraddr));//成功返回0。否则返回SOCKET_ERROR

		if (nRet == SOCKET_ERROR)
		{
			outputMessage("服务器连接失败!");
			return false;
		}

		outputMessage("服务器连接成功!");
		Myrecv();

		return true;
	}

	return false;
}

void ClientSocketTest::Myrecv()
{
	if (m_nLocalSocket != -1)
	{
		int resultRecv = -1;
		while (true)
		{
			resultRecv = recv(m_nLocalSocket, m_message, sizeof(m_message), 0);
			if (resultRecv > 0)
			{
				//输出消息
				outputMessage(m_message);
				memset(m_message, '\0', sizeof(m_message));
				Mysend();
			}
			else
			{
				//这几种错误码,认为连接是正常的,继续接收
				if ((resultRecv < 0) && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
				{
					continue;//继续接收数据
				}
				outputMessage("与服务器连接中断!");
				break;//跳出接收循环
			}
		}
	}
	else
	{
		outputMessage("当前与服务器未连接!");
	}
}

void ClientSocketTest::Mysend()
{
	if (m_nLocalSocket != -1)
	{
		char buffer5[100] = "client:客户端连接测试成功!";
		send(m_nLocalSocket, buffer5, sizeof(buffer5), 0);
	}
	else
	{
		outputMessage("当前与服务器未连接");
	}
}

void ClientSocketTest::outputMessage(const char * outstr)
{
	std::cout << outstr << std::endl;
}

启动程序

在此之前,我们还有个头文件myDefine.h没有写,下面我把代码贴上

//myDefine.h
#ifndef _MYDEFINE_H_
#define _MYDEFINE_H_

#define MYSERVERIP	"127.0.0.1"
#define MYPROT	16000

#endif

然后依次启动服务器和客户端代码

   //服务器启动
    #include "stdafx.h"
    #include <iostream>
    #include "ServerTest.h"
    #include "myDefine.h"
    
    int main()
    {
    	SocketServerTest* sserverTest = new SocketServerTest();
    	sserverTest->createSocket();
    	sserverTest->bandSocket(MYSERVERIP, MYPORT);
    	sserverTest->listenSocket();
    	sserverTest->acceptSocket();
    	delete sserverTest;
    
    	system("pause");
    	return 0;
    }

//客户端启动
#include "stdafx.h"
#include "ClientSocketTest.h"
#include "myDefine.h"

int main()
{
	ClientSocketTest* sockettest = new ClientSocketTest();
	sockettest->createSocket();
	sockettest->Myconnect(MYSERVERIP, MYPROT);
	delete sockettest;

	system("pause");
	return 0;
}

运行后结果

总结

这是最简单的阻塞socket通讯,服务器既不能满足并发,客户端也没有使用多线程,会在主线程中阻塞,而且通讯也只是一对一的,因为你会发现服务器必须断开第一个客户端连接之后才会接着处理第二个客户端连接。在下一章我会实现一对多的通讯,当然还是阻塞模式,结构跟上述代码不会相差太大,觉得能学到东西的朋友可以自己尝试一下。以后的代码如果实在过多,我会挑一些重点的部分粘贴出来,然后源码会提交在gitHub上,代码太累赘大家看着也比较吃力。

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值