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

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

        下面是我自己用的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;}

服务端

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

//在指定端口上监听
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检查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;//输出到标准输出而不是socketint 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;}

完整代码


#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"
#endifnamespace 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
#endifclass CmySocket{public:bool isDebug;//调试输出private:bool isSTDOUT;//输出到标准输出而不是socketint 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_VCreturn close(_s);
#elsereturn 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 _HPOSi = select(s + 1, (int *)&fd, NULL, NULL, &timeout);
#elsei = 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

(这里是结束)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/132205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 8 新特性 Stream 的使用场景(不定期更新)

方便在写代码的过程中直接使用&#xff0c;好记性不如好文章&#xff0c;直接 CV 改了直接用。提高 办&#xff08;摸&#xff09;公&#xff08;鱼&#xff09;效&#xff08;时&#xff09;率&#xff08;间&#xff09;&#xff0c; 不然就直接问 GPT 也不是说不行。 只符合…

Mysql学习文档笔记

文章目录 基础篇通用语法及分类DDL&#xff08;数据定义语言&#xff09;数据库操作注意事项 表操作 DML&#xff08;数据操作语言&#xff09;添加数据注意事项 更新和删除数据 DQL&#xff08;数据查询语言&#xff09;基础查询条件查询聚合查询&#xff08;聚合函数&#xf…

【C语法学习】18 - fread()函数

文章目录 1 函数原型2 参数3 返回值4 示例 1 函数原型 fread()&#xff1a;从与指定流stream相关联的二进制文件中读取数据块储存在str指向的内存空间中&#xff0c;函数原型如下&#xff1a; size_t fread(const void *ptr, size_t size, size_t count, FILE *stream)2 参数…

[动态规划] (七) 路径问题:LCR 166.剑指offer 47. 珠宝的最高价值

[动态规划] (七) 路径问题&#xff1a;LCR 166./剑指offer 47. 珠宝的最高价值 文章目录 [动态规划] (七) 路径问题&#xff1a;LCR 166./剑指offer 47. 珠宝的最高价值题目解析解题思路状态表示状态转移方程初始化和填表顺序 返回值代码实现总结 LCR 166. 珠宝的最高价值 题目…

python web框架 flask基础入门教程

python web框架 flask基础入门教程 今天我们写一个flask基础入门教程&#xff0c;当然也会覆盖很多重要的知识点&#xff0c;在这篇博客中&#xff0c;我们主要会讲解如下内容&#xff1a; 1、通过flask框架向web传输和接收参数 2、实现静态图片插入和图书上传 3、实现搭建…

宝塔面板使用Supervisor进程守护插件,配置守护Mysql的操作教程。

本篇文章主要讲解&#xff0c;在宝塔面板中使用Supervisor进程守护插件&#xff0c;配置守护Mysql的操作教程。 作者&#xff1a;任聪聪 日期&#xff1a;2023年11月5日 一、安装守护进程插件 安装插件一、进程守护插件 安装说明&#xff1a;在软件商店中搜索“进程守护”&am…

VC++常用命名法和宏定义

匈牙利命名法规则 一般情况下&#xff0c;变量的取名方式为&#xff1a; <scope_> <prefix_> <qualifier>。 范围前缀_&#xff0c;类型前缀_&#xff0c;限定词。 特殊的类型命名,前缀表示&#xff1a; 类、接口 前缀 类型 例子 备注 Lm Class …

【Vue】使用v-model实现控制子组件显隐

v-model 可以实现双向绑定的效果&#xff0c;允许父组件控制子组件的显示/隐藏&#xff0c;同时允许子组件自己控制自身的显示/隐藏。以下是如何使用 v-model 实现这个需求&#xff1a; 在父组件中&#xff0c;你可以使用 v-model 来双向绑定一个变量&#xff0c;这个变量用于…

2019 ICPC 银川题解(A,H,L)

赛时没发挥好6题金尾&#xff08;rank38&#xff09;&#xff0c;剩下很多能写的题&#xff0c;其中四个dp&#xff0c;傻眼ing The 2019 ICPC Asia Yinchuan Regional Contest A Girls Band Party&#xff08;背包&#xff09; 有点迷惑的题&#xff0c;当时看只要 5 5 5 张…

电脑时间校对怎么做?看这里,分享4个方法!

“我的电脑时间总是和手机的时间不一样&#xff0c;应该是电脑的时间不准确了&#xff0c;想知道大家遇到这种情况时是如何校对电脑时间的呀&#xff1f;” 随着电脑在我们日常生活中的广泛应用&#xff0c;确保电脑时间准确性变得至关重要。电脑时间校对不仅有助于同步文件和通…

爬虫之爬虫介绍、requests模块、携带请求参数、url 编码和解码、携带请求头

爬虫介绍 爬虫是什么&#xff1f; 网页蜘蛛&#xff0c;网络机器人&#xff0c;spider在互联网中 通过 程序 自动的抓取数据 的过程根上&#xff1a;使用程序 模拟发送http请求 ⇢ \dashrightarrow ⇢ 得到http响应 ⇢ \dashrightarrow ⇢ 把响应的数据解析出来 ⇢ \dashr…

小程序如何设置自动预约快递

小程序通过设置自动预约功能&#xff0c;可以实现自动将订单信息发送给快递公司&#xff0c;快递公司可以自动上门取件。下面具体介绍如何设置。 在小程序管理员后台->配送设置处&#xff0c;选择首选配送公司。为了能够支持自动预约快递&#xff0c;请选择正常的快递公司&…

S4.2.4.5 Fast Training Sequence (FTS)

一 本章节主讲知识点 1.1 FTS的用途和实现注意 二 本章节原文翻译 Fast Training Sequence (FTS) 主要用于在L0s->L0跳转的过程中&#xff0c;让Receiver 检测到电气空闲退出&#xff0c;以及实现bit 和 symbol lock。 2.1 Gen1 and Gen2 速率 对于Gen1/2 FTS的组成如下…

自定义element-ui plus 函数式调用,在API,js中直接使用全局组件

npm方式: npm install -D unplugin-vue-components unplugin-auto-import yarn 方式 : yarn add unplugin-vue-components; yarn add unplugin-auto-import; 使用官方的这个&#xff1a; vite.config.js中配置 plugins: [vue(),AutoImport({resolvers: [ElementPlusResolve…

02-Sping事务实现之声明式事务基于XML的实现方式

声明式事务之XML实现方式 开发步骤 第一步: 引入AOP相关的aspectj依赖 <!--aspectj依赖--> <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.0-M2</version> <…

docker安装(超详细)

一.引言 本安装教程参考Docker官方文档&#xff0c;地址如下&#xff1a;https://docs.docker.com/engine/install/centos/ 二.卸载旧版docker(第一次安装可忽略) 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \docker-client \docker…

电脑投屏到TCL电视鼠标延迟

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 笔记本电脑使用HDMI投屏到TCL电视&#xff0c;页面显示正常但是鼠标延迟反应太慢了 解决方案&#xff1a; 提示&#xff1a;这里填写该问题的具体解决方案&#xff1a; TCL电视设置中选择图像 → 图像类型改…

Flink源码解析二之执行计划⽣成

JobManager Leader 选举 首先flink会依据配置获取RecoveryMode,RecoveryMode一共两两种:STANDALONE和ZOOKEEPER。 如果用户配置的是STANDALONE,会直接去配置中获取JobManager的地址如果用户配置的是ZOOKEEPER,flink会首先尝试连接zookeeper,利用zookeeper的leadder选举服务发现…

如何处理前端本地存储和缓存

前端本地存储和缓存的处理是一种重要的技术&#xff0c;它可以帮助改善应用程序的性能和用户体验。下面是一些处理前端本地存储和缓存的常用方法&#xff1a; 1. 使用Web Storage API&#xff1a; 这是一种在浏览器中存储数据的方法&#xff0c;包括两种类型&#xff1a;loca…

【漏洞复现】Django -SQL注入漏洞复现(CVE-2019-14234)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 复现环境&#xff1a;Vulhub 环境启动后&#xff0c;访问http://192.168.80.141:8000即可看到Django默认首页 漏洞复现 首先登陆后台http://192.168.80.141:8000/admin/&#xff0c;用…