每个程序员都应该自己写一个的:socket包装类

初级代码游戏的专栏介绍与文章目录-优快云博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


 

github位置:codetoys/ctfc.git src/function/mysocket.h

        每个程序员都应该有自己的网络类。

        基本上可以这么说,所有的系统接口都是复杂的,因为要支持所有情形,而大部分程序需要的功能都是简单的,几个简单的函数,每个带一两个参数就可以了。

        下面是我自己用的socket类,支持所有我自己常用的功能,支持windows和unix/linux。

目录

客户端

服务端

非阻塞

获取socket信息

完整代码


客户端

        作为socket客户端,只需要如下几个功能:

//连接到指定的域名/地址和端口
bool Connect(const string & host, unsigned short port);


//发送数据
bool Send(const string & str);//发送文本
bool Send(char const * buf, long count);//发送二进制数据


//接收数据
bool Recv(char * buf, int buflen, long * pReadCount);


//断开连接
bool Close();

        比较复杂的是连接到服务器,因为要处理域名和端口,还要处理主机字节序和网络字节序的转换,但是写好以后就可以简单地用域名/地址和端口调用了。判断传入的参数是域名还是地址可以直接尝试转换成地址,如果失败再进行域名解析。

        Connect代码如下:

		bool Connect(const string & host, unsigned short port)//连接到指定的目标
		{
			if (isSTDOUT)return false;
			struct hostent *ph;
			T_SA_SIZE len_sa = sizeof(struct sockaddr_in);

			if (s >= 0)
			{
				cout << "不能在已打开的socket上操作 " << s << endl;
				return false;
			}
			if (!CreateSocket())
			{
				cout << "socket创建失败 " << s << endl;
				return false;
			}
			peersa.sin_family = AF_INET;
			peersa.sin_port = htons(port);
			if (-1 == (long)(peersa.sin_addr.s_addr = inet_addr(host.c_str())))
			{
				if (NULL == (ph = gethostbyname(host.c_str())))return false;
				memcpy(&peersa.sin_addr.s_addr, ph->h_addr_list[0], ph->h_length);
			}
			if (connect(s, (sockaddr*)(void*)&peersa, sizeof(struct sockaddr_in)) < 0)
			{
				Close();
				return false;
			}
			getsockname(s, (sockaddr*)(void*)&mysa, &len_sa);
			getpeername(s, (sockaddr*)(void*)&peersa, &len_sa);
			int iKeepAlive = 1;
			setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&iKeepAlive, sizeof(iKeepAlive));
			return true;
		}

        API级的接口确实很复杂,其中一部分原因是制定这些接口的时候考虑了很多不同的协议,不过现在,当初的很多协议已经消失了,很多参数显得莫名其妙。他们考虑的东西和我们考虑的不一样。 

服务端

        作为服务端,关键是这两个功能:

//在指定端口上监听
bool Listen(unsigned short portnum);


//接受一个连接请求,返回一个新CmySocket对象
CmySocket Accept();

        Listen其实相当简单,只要先bind到端口即可,当然还可能需要指定使用的IP,不过我的程序没有用到,所以没有写:

bool Bind(unsigned short portnum)//绑定到端口
{
    if (s < 0 && !CreateSocket())return false;

    int on = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

    mysa.sin_port = htons(portnum);
    if (bind(s, (sockaddr*)(void*)&mysa, sizeof(struct sockaddr_in)) < 0)
    {
        Close();
        return false;
    }
    return true;
}


bool Listen(unsigned short portnum)
{
    return (Bind(portnum)) && (!(listen(s, SOMAXCONN) < 0));
}

非阻塞

        一般用select查询socket是否可读就可以解决阻塞问题(写操作不会阻塞)。但是,有一个坑:

        如果并发执行select,会同时得到可读,从而所有进程或线程进入读操作(通常是服务端的accept操作),但是,只有其中一个能读到,其它进程或线程会被阻塞。这种现象叫做“惊群”。

        对select加锁可以解决问题,但是会降低性能。服务端应该通过别的方式来解决(一般要解决的只是让服务端能遵照指令退出而已)。

        select机制对Unix和Windows是兼容的,这套代码可以同时工作在Unix/Linux/Windows上。

        通过select检查socket是否可读的代码如下:

		bool IsSocketReadReady(struct timeval & timeout, bool & ret)
		{
			fd_set fd;
			int i;

			FD_ZERO(&fd);
			FD_SET(s, &fd);
			i = select(s + 1, &fd, NULL, NULL, &timeout);
			if (1 == i)
			{
				ret = true;
				return true;
			}
			else if (0 == i)
			{
				ret = false;
				return true;
			}
			else if (-1 == i)
			{
				ret = false;
				return false;
			}

			return false;
		}

        还有一个能够快速退出的版本:

		//检查套接字是否可读,seconds为负不设超时,但仍可根据pfNeedBrek跳出
		bool IsSocketReadReady2(long seconds, bool & ret, bool(*pfNeedBrek)() = NULL)
		{
			struct timeval timeout;
			timeout.tv_sec = (0 == seconds ? 0 : 1);
			timeout.tv_usec = 0;

			time_t t1 = time(NULL);
			do
			{
				//LOG<<seconds<<" "<<time(NULL) - t1<<ENDI;
				if (!IsSocketReadReady2(timeout, ret))
				{
					return false;
				}
				if (NULL != pfNeedBrek && pfNeedBrek())
				{
					if (isDebug)cout << "need break:" << s << endl;
					return false;
				}
				if (ret)
				{
					return true;
				}
			} while (seconds < 0 || time(NULL) - t1 < seconds);

			return true;
		}

        每次阻塞一秒钟,然后检查是否设置了退出命令。当然我们知道UNIX/Linux的标准的机制是使用信号来中断,不过信号机制可能不同模块冲突,不如靠自己。

获取socket信息

        程序调试经常要知道本地的端口和对方的地址端口,这是通过调用getsockname和getpeername来实现的:

//成员变量
	public:
		bool isDebug;//调试输出
	private:
		bool isSTDOUT;//输出到标准输出而不是socket
		int s;//socket -1表示无效
		unsigned long sendcount;//发送计数
		unsigned long recvcount;//接收计数
		struct sockaddr_in mysa;//本地半相关
		struct sockaddr_in peersa;//远程半相关


//服务端接受连接或客户端建立连接后执行:
getsockname(cs.s, (sockaddr*)(void*)&cs.mysa, &len_sa);
getpeername(cs.s, (sockaddr*)(void*)&cs.peersa, &len_sa);


//输出内部信息
		string debuginfo()//输出内部数据结构
		{
			string str;
			char buf[256];
			str = "";
			if (isSTDOUT)str += "STDOUT\n";
			if (-1 != s)
			{
				sprintf(buf, "%d", s);
				str += buf;
			}
			else str += "未连接";
			str += "\n";
			sprintf(buf, "send: %ld\nrecv: %ld\n", sendcount, recvcount);
			str += buf;

			if (AF_INET == mysa.sin_family)str += "AF_INET";
			else
			{
				sprintf(buf, "%d", mysa.sin_family);
				str += buf;
			}
			str += "\n";
			str += inet_ntoa(mysa.sin_addr);
			str += "\n";
			sprintf(buf, "%d", ntohs(mysa.sin_port));
			str += buf;
			str += "\n";
			if (AF_INET == peersa.sin_family)str += "AF_INET";
			else
			{
				sprintf(buf, "%d", peersa.sin_family);
				str += buf;
			}
			str += "\n";
			str += inet_ntoa(peersa.sin_addr);
			str += "\n";
			sprintf(buf, "%d", ntohs(peersa.sin_port));
			str += buf;
			str += "\n";
			return str;
		}

完整代码

        这个代码兼容winodws、linux和Sun、IBM、HP的小型机,注意一下区分机型的几个宏。


#ifndef MYSTD_MYSOCKET_H
#define MYSTD_MYSOCKET_H

#ifndef _MS_VC
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#else
#include "winsock.h"
#endif

namespace ns_my_std_2
{

#define T_SA_SIZE int

#ifdef _HPOS
#undef T_SA_SIZE
#define T_SA_SIZE int
#endif

#ifdef _IBMOS
#undef T_SA_SIZE
#define T_SA_SIZE unsigned int
#endif

#ifdef _LINUXOS
#undef T_SA_SIZE
#define T_SA_SIZE socklen_t
#endif

	class CmySocket
	{
	public:
		bool isDebug;//调试输出
	private:
		bool isSTDOUT;//输出到标准输出而不是socket
		int s;//socket -1表示无效
		unsigned long sendcount;//发送计数
		unsigned long recvcount;//接收计数
		struct sockaddr_in mysa;//本地半相关
		struct sockaddr_in peersa;//远程半相关

		bool Init()//初始化,s被设置为-1,计数清零,半相关清零
		{
			char myname[256];
			struct hostent *ph;
			s = -1;
			isDebug = false;
			isSTDOUT = false;
			sendcount = 0;
			recvcount = 0;
			memset(&mysa, 0, sizeof(struct sockaddr_in));
			memset(&peersa, 0, sizeof(struct sockaddr_in));
			if (0 != gethostname(myname, 256))return false;
			myname[255] = '\0';
			if (NULL == (ph = gethostbyname(myname)))return false;
			mysa.sin_family = ph->h_addrtype;
			return true;
		}
		bool CreateSocket()//初始化并建立一个socket
		{
			if (Init() && (s = socket(AF_INET, SOCK_STREAM, 0)) > 0)return true;
			else return false;
		}
		int CloseSocket(int _s)
		{
#ifndef _MS_VC
			return close(_s);
#else
			return closesocket(_s);
#endif
		}
		bool Bind(unsigned short portnum)//绑定到端口
		{
			if (s < 0 && !CreateSocket())return false;

			int on = 1;
			setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

			mysa.sin_port = htons(portnum);
			if (bind(s, (sockaddr*)(void*)&mysa, sizeof(struct sockaddr_in)) < 0)
			{
				Close();
				return false;
			}
			return true;
		}
	public:
		CmySocket(int _s = -1)//构造函数,s默认被设置为-1
		{
			T_SA_SIZE len_sa = sizeof(struct sockaddr_in);
			Init();
			s = _s;
			if (-1 != s)
			{
				getsockname(s, (sockaddr*)(void*)&mysa, &len_sa);
				getpeername(s, (sockaddr*)(void*)&peersa, &len_sa);
			}
		}
		void SetSTDOUT() { isSTDOUT = true; }//设置为标准输出
		bool Listen(unsigned short portnum)//在指定端口上监听,如果s为-1会先建立socket然后bind
		{
			if (isSTDOUT)return false;
			return (Bind(portnum)) && (!(listen(s, SOMAXCONN) < 0));
		}
		bool Accept(int * pNewSocket)//接受一个连接请求
		{
			if (isSTDOUT)return false;
			return -1 != ((*pNewSocket) = accept(s, NULL, NULL));
		}
		CmySocket Accept()//接受一个连接请求,返回一个新CmySocket对象
		{
			if (isSTDOUT)return false;
			CmySocket cs;
			T_SA_SIZE len_sa = sizeof(struct sockaddr_in);
			cs.Init();
			cs.s = accept(s, (sockaddr*)(void*)&cs.mysa, &len_sa);
			if (-1 != cs.s)
			{
				getsockname(cs.s, (sockaddr*)(void*)&cs.mysa, &len_sa);
				getpeername(cs.s, (sockaddr*)(void*)&cs.peersa, &len_sa);
				int iKeepAlive = 1;
				setsockopt(cs.s, SOL_SOCKET, SO_KEEPALIVE, (void *)&iKeepAlive, sizeof(iKeepAlive));
			}
			return cs;
		}
		bool Send(const string & str)//发送文本
		{
			return Send(str.c_str(), str.size());
		}
		bool Send(char const * buf, long count)//发送二进制数据
		{
			if (isSTDOUT)
			{
				std::cout << buf << std::flush;
				sendcount += count;
				return true;
			}
			long i = 0;
			while (i < count)
			{
				int n = send(s, buf + i, count - i, 0);
				if (isDebug)cout << "socket " << s << " send " << count - i << " return " << n << endl;
				if (n != count)
				{
					cout << "socket " << s << " send " << count - i << " return " << n << endl;
				}
				if (n < 0)
				{
					return false;
				}
				i += n;
				sendcount += n;
			}
			return true;
		}
		bool Recv(char * buf, int buflen, long * pReadCount)//接收数据
		{
			if (isSTDOUT)return false;
			if ((*pReadCount = recv(s, buf, buflen, 0)) < 0)
			{
				if (isDebug)cout << "socket " << s << " recv  return " << *pReadCount << endl;
				return false;
			}
			if (isDebug)cout << "socket " << s << " recv  return " << *pReadCount << endl;
			recvcount += (*pReadCount);
			return true;
		}
		bool Close()//close socket 设置s为-1,但其它数据会保持到下一次用这个对象建立新socket时才清除
		{
			if (isSTDOUT)return true;
			if (isDebug)cout << "socket 关闭:" << s << endl;
			shutdown(s, 2);
			if (0 == CloseSocket(s))
			{
				s = -1;
				return true;
			}
			else return false;
		}
		bool Connect(const string & host, unsigned short port)//连接到指定的目标
		{
			if (isSTDOUT)return false;
			struct hostent *ph;
			T_SA_SIZE len_sa = sizeof(struct sockaddr_in);

			if (s >= 0)
			{
				cout << "不能在已打开的socket上操作 " << s << endl;
				return false;
			}
			if (!CreateSocket())
			{
				cout << "socket创建失败 " << s << endl;
				return false;
			}
			peersa.sin_family = AF_INET;
			peersa.sin_port = htons(port);
			if (-1 == (long)(peersa.sin_addr.s_addr = inet_addr(host.c_str())))
			{
				if (NULL == (ph = gethostbyname(host.c_str())))return false;
				memcpy(&peersa.sin_addr.s_addr, ph->h_addr_list[0], ph->h_length);
			}
			if (connect(s, (sockaddr*)(void*)&peersa, sizeof(struct sockaddr_in)) < 0)
			{
				Close();
				return false;
			}
			getsockname(s, (sockaddr*)(void*)&mysa, &len_sa);
			getpeername(s, (sockaddr*)(void*)&peersa, &len_sa);
			int iKeepAlive = 1;
			setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&iKeepAlive, sizeof(iKeepAlive));
			return true;
		}
		bool IsConnected() { if (isSTDOUT)return true; else return -1 != s; }//是否处于连接状态,只对客户socket有意义
		sockaddr_in const * GetPeersa()const { return &this->peersa; }
		//检查套接字是否可读
		bool IsSocketReadReady(long seconds, bool & ret)
		{
			struct timeval timeout;
			timeout.tv_sec = seconds;
			timeout.tv_usec = 0;
			return IsSocketReadReady(timeout, ret);
		}
		bool IsSocketReadReady(struct timeval & timeout, bool & ret)
		{
			fd_set fd;
			int i;

			FD_ZERO(&fd);
			FD_SET(s, &fd);
			i = select(s + 1, &fd, NULL, NULL, &timeout);
			if (1 == i)
			{
				ret = true;
				return true;
			}
			else if (0 == i)
			{
				ret = false;
				return true;
			}
			else if (-1 == i)
			{
				ret = false;
				return false;
			}

			return false;
		}
		//检查套接字是否可读,seconds为负不设超时,但仍可根据pfNeedBrek跳出
		bool IsSocketReadReady2(long seconds, bool & ret, bool(*pfNeedBrek)() = NULL)
		{
			struct timeval timeout;
			timeout.tv_sec = (0 == seconds ? 0 : 1);
			timeout.tv_usec = 0;

			time_t t1 = time(NULL);
			do
			{
				//LOG<<seconds<<" "<<time(NULL) - t1<<ENDI;
				if (!IsSocketReadReady2(timeout, ret))
				{
					return false;
				}
				if (NULL != pfNeedBrek && pfNeedBrek())
				{
					if (isDebug)cout << "need break:" << s << endl;
					return false;
				}
				if (ret)
				{
					return true;
				}
			} while (seconds < 0 || time(NULL) - t1 < seconds);

			return true;
		}
		bool IsSocketReadReady2(struct timeval & timeout, bool & ret)
		{
			ret = false;

			fd_set fd;
			int i;

			FD_ZERO(&fd);
			FD_SET(s, &fd);

			//LOG<<"timeout.tv_sec "<<timeout.tv_sec<<ENDI;
#ifdef _HPOS
			i = select(s + 1, (int *)&fd, NULL, NULL, &timeout);
#else
			i = select(s + 1, &fd, NULL, NULL, &timeout);
#endif
			//LOG<<"timeout.tv_sec "<<timeout.tv_sec<<" select "<<i<<ENDI;
			if (1 == i)
			{
				ret = true;
				return true;
			}
			else if (0 == i)
			{
				ret = false;
				return true;
			}
			else if (-1 == i)
			{
				if (EINTR == errno)
				{
					ret = false;
					return true;//被信号中断不是错误
				}
				else
				{
					ret = false;
					return false;
				}
			}

			return false;
		}
		//接收数据,可以设置函数来终止
		bool Recv2(char * buf, int buflen, long * pReadCount, bool(*pfNeedBrek)())
		{
			bool isReady = false;
			if (!IsSocketReadReady2(-1, isReady, pfNeedBrek))
			{
				if (isDebug)cout << "IsSocketReadReady2 error:" << s << endl;
				return false;
			}
			if (!isReady)
			{
				if (isDebug)cout << "not ready:" << s << endl;
				return false;
			}
			return Recv(buf, buflen, pReadCount);
		}
		int GetMyPort()const
		{
			return ntohs(mysa.sin_port);
		}
		string GetPeerInfo()const
		{
			string str;
			if (-1 != s)
			{
				str += inet_ntoa(peersa.sin_addr);
				str += ":";
				char buf[32];
				sprintf(buf, "%d", ntohs(peersa.sin_port));
				str += buf;
			}
			return str;
		}
		string debuginfo()//输出内部数据结构
		{
			string str;
			char buf[256];
			str = "";
			if (isSTDOUT)str += "STDOUT\n";
			if (-1 != s)
			{
				sprintf(buf, "%d", s);
				str += buf;
			}
			else str += "未连接";
			str += "\n";
			sprintf(buf, "send: %ld\nrecv: %ld\n", sendcount, recvcount);
			str += buf;

			if (AF_INET == mysa.sin_family)str += "AF_INET";
			else
			{
				sprintf(buf, "%d", mysa.sin_family);
				str += buf;
			}
			str += "\n";
			str += inet_ntoa(mysa.sin_addr);
			str += "\n";
			sprintf(buf, "%d", ntohs(mysa.sin_port));
			str += buf;
			str += "\n";
			if (AF_INET == peersa.sin_family)str += "AF_INET";
			else
			{
				sprintf(buf, "%d", peersa.sin_family);
				str += buf;
			}
			str += "\n";
			str += inet_ntoa(peersa.sin_addr);
			str += "\n";
			sprintf(buf, "%d", ntohs(peersa.sin_port));
			str += buf;
			str += "\n";
			return str;
		}
	};
}

#endif

(这里是结束)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

初级代码游戏

知识究竟是有价还是无价

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

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

打赏作者

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

抵扣说明:

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

余额充值