C++游戏服务器框架笔记(二)_封装Socket类

笔记目录导航

C++游戏服务器框架笔记(一)_封装数据包类

C++游戏服务器框架笔记(二)_封装Socket类

C++游戏服务器框架笔记(三)_封装ByteBuffer类

C++游戏服务器框架笔记(四)_封装Select

C++游戏服务器框架笔记(五)_封装Epoll类 

......

        网络通信可以直接调用socket系统api接口进行通信,不过在不同的系统中,部分代码是有区别的,Windows和Linux系统中 一些接口或者接口参数的类型是有区别的,需要进行兼容,所以通常会将底层的socket api接口进行封装,已达到跨平台的目的,因为这里是C++语言,所以封装成一个类。

        差别之处利用宏判断是windows系统或者是其他系统,这里只处理Windows系统和Linux系统

        #ifdef _WIN32

                这里是Windows系统下的接口调用

        #else

                这里是Linux系统下的接口调用

        #endif

                                      

       Socket类头文件如下:

#ifndef _SOCKET_H_
#define _SOCKET_H_
/*
基础socket类,封装c socket接口
*/
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <assert.h>
#ifdef _WIN32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//由于bzero在windows系统下没有 这里定义一个bzero
#define bzero(point, size) memset(point, 0, size)
#else
#include <string.h>
//统一socket句柄的类型名
typedef int SOCKET;
#define INVALID_HANDLE_VALUE (-1)
#endif

class Socket
{
public:
	Socket();
	Socket(SOCKET sock, const char* ip, int port);
	~Socket();

	//Windows系统中使用socket函数需要先调用WSAStartup需要初始化版本
	static bool InitSocket();
	//Windows系统中使用socket函数完毕后需要调用WSACleanup释放
	static void UnInitSocket();

	//设置可重用地址
	void SetReuseAddr();
	//设置非阻塞
	void SetSockNonBlock();
	//设置接收缓冲区大小
	void SetRecvBuffSize(int size);
	//设置发送缓冲区大小
	void SetSendBuffSize(int size);
	//地址绑定
	bool Bind(const char* ip, int port);
	//启动监听
	bool Listen(int backlog);

	//接收连接,返回一个已分配内存上的Socket指针, 需要手动管理释放,失败返回 nullptr
	Socket* Accept();
	//接收连接, 返回连接的socket
	SOCKET Accept(struct sockaddr_in * addr, int * addrlen);

	//发起连接
	int Connect(const char* ip, int port);
	//发起连接
	int Connect(SOCKET sock, const char* ip, int port);
	//发送数据
	int Send(const char* buf, int size);
	//接收数据
	int Recv(char* buf, int size);
	//关闭套接字
	int Close();

public:
	SOCKET m_Sock;
	char m_Ip[16];
	short m_Port;
};
#endif

        InitSocket() 和UnInitSocket() 内部有做分平台处理,因为在windows下 socket通信需要一个初始化和释放的步骤,需要进行兼容

       SetReuseAddr():设置可重用地址接口, 设置端口可以被重复使用,可以被多个进程bind,  防止服务器端先调用close关闭socket后系统处于TIME_WAIT状态下还没有释放端口,这时重新启服的时候 在调用bind时 会绑定失败,提示ADDR已经在使用中。

        SetSockNonBlock():设置非阻塞,socket默认是阻塞模式,例如调用recv的时候 程序会阻塞在这里,一直等到读取到数据后,才会返回读取到的字节数,设置了非阻塞后,调用recv函数的时候会立即返回读取到的字节数,如果没有字节可读取,则会返回对应的错误码, 后续会涉及到使用多路IO复用机制中的Epoll,需要设置socket为非阻塞模式

        SetRecvBuffSize():设置socket的接收缓冲区大小

        SetSendBuffSize():设置socket的发送缓冲区大小

        系统中会为每个创建的socket分配一个接收缓冲区和一个发送缓冲区,例如调用send()函数发送一段数据,函数返回时,数据实际上是被加到了socket的发送缓冲区中,并没有实时通过底层网卡发送出去,会由系统在合适的时候再来将发送缓冲区中的数据发送出去。可以根据程序对网络通信数据量和网络的情况设置合理大小来提高效率和避免数据错误等问题。

      

        Socket类的Cpp文件如下:

#include "Socket.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#if _WIN32
#else
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#endif

Socket::Socket()
{
	m_Sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	assert(m_Sock != -1);
	SetReuseAddr();
}

Socket::Socket(SOCKET sock, const char* ip, int port) {
	m_Sock = sock;
	strncpy(m_Ip, ip, sizeof(ip));
	m_Port = port;
	SetReuseAddr();
}

Socket::~Socket(){
	Close();
}	

bool Socket::InitSocket() {
#if _WIN32
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData;
	int nErrorID = ::WSAStartup(wVersionRequested, &wsaData);
	if (nErrorID != 0) return false;

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		UnInitSocket();
		return false;
	}
#endif
	return true;
}

void Socket::UnInitSocket() {
#if _WIN32
	::WSACleanup();
#endif
}

void Socket::SetReuseAddr() {
	int on = 1;
	setsockopt(m_Sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
}

void Socket::SetSockNonBlock()
{
	int ret = -1;
#if _WIN32
	u_long argp = 1;
	ret = ioctlsocket(m_Sock, FIONBIO, &argp);
#else
	int old_flag = fcntl(m_Sock, F_GETFL, 0);
	ret = fcntl(m_Sock, F_SETFL, old_flag | O_NONBLOCK);
#endif
	if (ret == -1) {
		return;
	}
}

void Socket::SetRecvBuffSize(int size)
{
	int ret = -1;
#if _WIN32 
	int len = sizeof(size);
	ret = setsockopt(m_Sock, SOL_SOCKET, SO_RCVBUF, (char *)&size, len);
#else
	socklen_t len = sizeof(size);
	ret = setsockopt(m_Sock, SOL_SOCKET, SO_RCVBUF, &size, len);
#endif
	if (ret < 0){
		return;
	}
}

void Socket::SetSendBuffSize(int size)
{
	int ret = -1;
#if _WIN32 
	int len = sizeof(size);
	ret = setsockopt(m_Sock, SOL_SOCKET, SO_SNDBUF, (char *)&size, len);
#else
	socklen_t len = sizeof(size);
	ret = setsockopt(m_Sock, SOL_SOCKET, SO_SNDBUF, &size, len);
#endif
	if (ret < 0) {
		return;
	}
}

bool Socket::Bind(const char * ip, int port)
{
	struct sockaddr_in addr;
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	if (ip == nullptr)
		addr.sin_addr.s_addr = htonl(INADDR_ANY);

	else
#if _WIN32
		addr.sin_addr.s_addr = inet_addr(ip);
#else
		inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
#endif

	int ret = bind(m_Sock, (struct sockaddr *)&addr, sizeof(addr));
	if (ret < 0) {
		return false;
	}
	return true;
}

bool Socket::Listen( int backlog)
{
	int ret = listen(m_Sock, backlog);
	if (ret == -1) {
		return false;
	}
	return true;
}

Socket* Socket::Accept()
{
	struct sockaddr_in addr;
#if _WIN32
	int len = sizeof(addr);
#else
	socklen_t len = sizeof(addr);
#endif 
	SOCKET sock = accept(m_Sock, (struct sockaddr*)&addr, &len);
	if (-1 == sock)
		return nullptr;
		
	Socket* pSock = new Socket(sock, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
	return pSock;
}

SOCKET Socket::Accept(struct sockaddr_in* addr, int * addrlen)
{
#if _WIN32
	SOCKET sock = accept(m_Sock, (struct sockaddr*)addr, addrlen);
#else
	SOCKET sock = accept(m_Sock, (struct sockaddr*)addr,(socklen_t *)addrlen);
#endif
	return sock;
}

int Socket::Connect(const char* ip, int port)
{
	return Connect(m_Sock, ip, port);
}

int Socket::Connect(SOCKET Sock, const char* ip, int port)
{
	struct sockaddr_in addr;
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);

#if _WIN32
	int len = sizeof(addr);
	addr.sin_addr.s_addr = inet_addr(ip);
#else
	socklen_t len = sizeof(addr);
	inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
#endif

	return connect(Sock, (struct sockaddr*)&addr, len);;
}

int Socket::Send(const char* buf, int Len)
{
	return send(m_Sock, buf, Len, 0);;
}

int Socket::Recv(char* buf, int Len)
{
	return recv(m_Sock, buf, Len, 0);
}

int Socket::Close()
{
#if _WIN32
	int ret = closesocket(m_Sock);
#else
	int ret = close(m_Sock);
#endif
	return ret;
}

        这部分代码都比较简单,只是在底层api接口上分平台处理了差异之处,测试代码如下:

#include <iostream>
#include "net\Socket.h"

using namespace std;

int main(void)
{
	Socket::InitSocket();
	cout << "init Socket" << endl;
	Socket * pListenSock = new Socket();
	pListenSock->Bind(nullptr, 8888);
	pListenSock->Listen(100);

	Socket * pSock = pListenSock->Accept();
	assert(pSock != nullptr);

	char buf[10240];
	bzero(buf, 10240);
	while (true) {
		int ret = pSock->Recv(buf, 10240);
		if (ret <= 0) break;

		cout << "recv data:" << buf << endl;

		if (buf[0] == '#') break;
		bzero(buf, 10240);
	}
	
	delete pSock;
	delete pListenSock;

	Socket::UnInitSocket();
	cout << "My ServerEngine" << endl;
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春休夏末

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值