1、基础的一个C++版本的服务器代码
依照C语言服务器的基础流程进行更改,整个的TCP链接流程的实现:接上篇
Socket编程-IO模型-CSDN博客
先上代码结构图,因为是示例
1、首先,这个示例的基本流程就是使用Socket,创建sockfd,进行获取sockfd'等操作,供之后的流程使用
2、InetAddress : 这个文件中只做和地址相关的信息,比如初始化 IP,Port还有,获取IP信息等的操作方法(sockaddr_in )。
3、Acceptor :这个类文件里面只做和accept相关的操作,包括以下几个流程(基础功能,可以最终实现accept的操作)
设定地址和断开可复用后,流程开始,注意哦,这几个都是这个类的类成员函数,基于基础的函数进行设置。
绑定和监听。这在c语言流程中,监听之后就是accept了
但是在这里,将accept单独的设定成这个类的成员函数,自己可以决定什么时候去调用accept功能
前面的内容就是根据基本的流程更改后,C++版本的实现,我们只需要创建对象,进行调用各函数就可以了,功能实现也是函数之间的相互联系和嵌套。
4、InitConnetion : 这个就是对整体的链接进行操作和控制,(而创建一个对象,就相当于是创建了一个链接,然后我们执行这个链接所包含的操作),每个链接都是不同的。
与之类似的,就是这些类凸显了细分的功能:每一个功能,我们都将它设定成了对应的类,当我们需要创建大量的链接时,基于这些类创建出我们需要的对象,不同对象执行不同的操作。这里最明显的就是InetConnection ,可以使用其创建不同的对象,那么不同的对象是不是意味着不同的链接。只不过本例。只能实现单链接,其他的后面写。下面上代码
5、其中ProcessIO ,是为了进行程序的IO操作,读写功能的实现。
//写这个的时候,更多的还是注意类功能的实现和整体实现目标结构的设定(比如这里,就是服务器,就按照服务器结构来就行),不要盯着某一个成员函数的功能去看。我们需要调用谁,实现什么功能,就在合适的时候将它调用出来就行。
因为C++类的特征明显,每个类都是要解决一部分相关问题和操作的(类似这个,类的不同对应着服务器流程中的不同阶段或者某一模块功能)
其他代码也一样。之前看过很多代码文件命名不清晰或者名字相近还没有说明,只有一堆代码,也没有注释,理解上有歧义的情况下,特容易导致方向上的错误,看起来就太累了。
//这个可能写的也不太好。。后面多改进,在核心主线的基础上,拓展功能
代码示例:
Socket:
#ifndef __SOCKET_H__ #define __SOCKET_H__ #include "NonCopyable.h" class Socket : NonCopyable { public: Socket(); explicit Socket(int fd); ~Socket(); int fd() const; void shutDownWrite(); private: int _fd; }; #endif
#include "Socket.h" #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> Socket::Socket() { _fd = ::socket(AF_INET, SOCK_STREAM, 0); if(_fd < 0) { perror("socket"); return; } } Socket::Socket(int fd) : _fd(fd) { } Socket::~Socket() { close(_fd); } int Socket::fd() const { return _fd; } void Socket::shutDownWrite() { //关闭写端 int ret = shutdown(_fd, SHUT_WR); if(ret) { perror("shutdown"); return; } }
InetAddress
#ifndef __INETADDRESS_H__ #define __INETADDRESS_H__ #include <arpa/inet.h> #include <string> #include <iostream> using std::string; class InetAddress{ private: struct sockaddr_in _serverAddr; string _ip; int _port; public: InetAddress(const string &ip,unsigned short port); InetAddress(const struct sockaddr_in &addr); ~InetAddress(); string getServerIP() const; unsigned short getServerPort() const; const sockaddr_in *getInetAddrPtr() const; // 因为我们的类通常需要和其他类进行关联协作 // 在必要时候需要获取到类中的数据信息(如果是私有的,就需要设定一个公共权限的获取函数) }; #endif
InetAddress.cc
#include "InetAddress.h" #include <string.h> //初始化服务器的IP,端口,和协议 InetAddress::InetAddress(const string &ip, unsigned short port){ ::bzero(&_serverAddr, sizeof(struct sockaddr_in)); _serverAddr.sin_addr.s_addr = inet_addr(ip.c_str()); _serverAddr.sin_port = htons(port); _serverAddr.sin_family = AF_INET; } InetAddress::InetAddress(const struct sockaddr_in &addr){ _serverAddr.sin_addr.s_addr = addr.sin_addr.s_addr; _serverAddr.sin_family = addr.sin_family; _serverAddr.sin_port = addr.sin_port; } InetAddress::~InetAddress(){ } //返回sockaddr_in sockaddr的地址族信息 const sockaddr_in * InetAddress::getInetAddrPtr() const { return &_serverAddr; } string InetAddress::getServerIP() const{ return string(inet_ntoa(_serverAddr.sin_addr)); } unsigned short InetAddress::getServerPort()const{ return _serverAddr.sin_port; }
Acceptor
#ifndef __ACCEPTOR_H__ #define __ACCEPTOR_H__ #include "Socket.h" #include "InetAddress.h" #include <string> using std::string; class Acceptor { public: Acceptor(const string &ip, unsigned short port); ~Acceptor(); void ready(); void setReuseAddr(); void setReusePort(); void bind(); void listen(); int accept(); int fd() const; private: Socket _listenSock; InetAddress _servAddr; }; #endif
Acceptor.cc
#include "Acceptor.h" #include <stdio.h> Acceptor::Acceptor(const string &ip, unsigned short port) : _listenSock() , _servAddr(ip, port) { } Acceptor::~Acceptor() { } void Acceptor::ready() { setReuseAddr(); setReusePort(); bind(); listen(); //accept(); } void Acceptor::setReuseAddr() { int on = 1; int ret = setsockopt(_listenSock.fd(), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if(ret) { perror("setsockopt"); return; } } void Acceptor::setReusePort() { int on = 1; int ret = setsockopt(_listenSock.fd(), SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); if(-1 == ret) { perror("setsockopt"); return; } } void Acceptor::bind() { int ret = ::bind(_listenSock.fd(), (struct sockaddr *)_servAddr.getInetAddrPtr(), sizeof(struct sockaddr)); if(-1 == ret) { perror("bind"); return; } } void Acceptor::listen() { int ret = ::listen(_listenSock.fd(), 128); if(-1 == ret) { perror("listen"); return; } } int Acceptor::accept() { int connfd = ::accept(_listenSock.fd(), nullptr, nullptr); if(-1 == connfd) { perror("listen"); return -1; } return connfd; } int Acceptor::fd() const { return _listenSock.fd(); }
InitConnection
/* /* 这个类就是用来实现初始化创建链接的整个过程(从socket 到accept 成功创建链接),或者说用来实现整个流程的枢纽,运行实现就行了 */ #ifndef __INITCONNETION_H__ #define __INITCONNETION_H__ #include "Socket.h" #include "InetAddress.h" #include "ProcessIO.h" #include <string> using std::string; class InitConnection{ private: Socket _sock; ProcessIO _processio; InetAddress _localaddr; InetAddress _peeraddr; private: InetAddress getLocalAddress(); InetAddress getpeerAddress(); public: InitConnection(int fd); ~InitConnection(); void send(const string &msg); string receive(); string printConnetionInfo(); }; #endif
#include "InitConnection.h" #include <iostream> #include <sstream> using std::cout; using std::endl; using std::ostringstream; InitConnection::InitConnection(int fd) :_sock(fd) ,_localaddr(getLocalAddress()) ,_peeraddr(getpeerAddress()) ,_processio(_sock.fd()) //实际上运行到这里,_sock的 fd已经时经过accept处理的已连接文件描述符,直接用就行 { } InitConnection::~InitConnection(){ } InetAddress InitConnection::getLocalAddress(){ struct sockaddr_in addr; socklen_t socklen = sizeof(struct sockaddr); int ret = getsockname(_sock.fd(),(struct sockaddr *)&addr,&socklen); if(ret == -1){ perror("InitConnection getsockaddr "); } return InetAddress(addr); //因为我们使用服务器的地址进行初始化时, } InetAddress InitConnection::getpeerAddress(){ struct sockaddr_in addr; socklen_t socklen = sizeof(struct sockaddr); int ret = getpeername(_sock.fd(),(struct sockaddr *)&addr,&socklen); if(ret == -1){ perror("InitConnection getpeersockaddr "); } return InetAddress(addr); } string InitConnection::printConnetionInfo(){ ostringstream oss1; oss1 << "服务端地址 "<< _localaddr.getServerIP() << ": " << _localaddr.getServerPort() << "----> 客户端地址" << _peeraddr.getServerIP() << ": " << _peeraddr.getServerPort() << ": "; return oss1.str(); } void InitConnection::send(const string &msg) { _processio.writen(msg.c_str(), msg.size()); } string InitConnection::receive() { char buff[65535] = {0}; //这里设定的足够大,才能在我们真正进行读写IO操作时,可以保证正常读取,但是注意不要过大,毕竟系统存储空间是有限的,资源合理利用 _processio.readLine(buff, sizeof(buff)); return string(buff); }
ProcessIO
#ifndef __SOCKETIO_H__ #define __SOCKETIO_H__ class ProcessIO{ public: explicit ProcessIO(int fd); ~ProcessIO(); int readn(char *buf,int len); int readLine(char *buf, int len); int writen(const char *buf, int len); private: int _fd; //这个就是链接 accept后生成的文件描述符 }; #endif
#include "ProcessIO.h" #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <error.h> #include <stdio.h> ProcessIO::ProcessIO(int fd){ _fd = fd; } ProcessIO::~ProcessIO(){ //因为这个只进行基于数据读写的操作,所以不在这里进行释放 _fd,会在这个访问链接整体断开后的情况下,进行释放。 //因为系统的文件描述符是有限的,最好是释放后再重新创建 //close(_fd); } int ProcessIO::readn(char *buf,int len){ //此处考虑从客户端接收的数据大小问题,buf大小可以设定大一些 int left = len; char * pstr = buf; //这个函数设定中能实现的基础时这个buf 足够大,可放得下读取的客户端信息信息 int ret = 0; while(left > 0){ ret = read(_fd, buf, len); if(-1 == ret){ perror("read error -1"); return len-ret; } else if(0 == ret){ break; } else{ pstr += ret; //一次不能读完的情况下,将buf指针指向当前读取到数据的最后一个,方便将数写入buf中 left -= ret; //读取的数据总量 } } return len - left; //这里,因为上面的循环读取,正常情况下一定会将数据读取完后退出,这里返回下buf中读取到的数据大小 } int ProcessIO::readLine(char *buf, int len){ //因为直接使用read,我们没办法控制读取一行数据,只能将数据读取后,进行处理 //如果使用read,那我们就需要多开辟一块缓存空间去存储数据,不然无法有效将原buf中 某一行数据读取出来 //但是可以使用recv(),这个不会清空缓存区,我们只要再次调用读写就行,不过就是对字节数有限制 int left = len-1; char * pstr = buf; int total = 0,linesize = 0; //读取的总字节数和行字数 while(left > 0){ int ret = recv(_fd, buf, len, MSG_PEEK); //这个属性不会清空缓存区域,也就是读取后,本缓存区内容在下一次调用这同一个函数时,buf内容相同 if( 0 == ret){ break; } else if(-1 == ret){ //这里原来写了错误判断 errno, 但是头文件有问题删了。产生eintr系统调用错误时,本系统调用可以再次调用进行使用,可以重启此调用,但是有的不行 continue; } if(-1 == ret){ perror("readline error -1"); return len - left; //这里表示本次循环虽然出错了,之前可能正常读取到了信息,返回下数量 } else{ for(int idx = 0; idx < ret ; idx++){ if(pstr[idx] == '\n'){ linesize = idx+1; //读取一行时,最后一个字符一定时换行符, readn(pstr,linesize); //这里就是直接调用上面的函数,相当于执行一个read,读取一行数据,存储在buf中 pstr += linesize; *pstr = '\n'; return total+linesize; } } //如果没有执行上面循环中的if结构,那就说明,还没有读到换行符,没有读够一行 total += ret; //这里因为 readn中也有ret ,有时注意下变量命名 readn(pstr, ret); //直接将数据读入到缓冲区中 pstr += ret; //pstr位置更行 left -= ret; //剩余可读取数量更新 ,之后执行下一次读写循环 } } *pstr = '\0'; //只要buf够大,最后一个元素一定时 \0 return total =- left; } int ProcessIO::writen(const char *buf, int len){ int left = len; const char *pstr = buf; int ret = 0; while(left > 0){ ret = write(_fd, pstr, left); if(-1 == ret) { continue; } else if(-1 == ret) { perror("writen error -1"); return len - ret; } else if(0 == ret) { break; } else { pstr += ret; left -= ret; } } return len - left; }
NonCopyable
#ifndef __NONCOPYABLE_H__ #define __NONCOPYABLE_H__ class NonCopyable{ protected: NonCopyable(){}; ~NonCopyable(){}; NonCopyable (const NonCopyable &) = delete; //赋值运算符 NonCopyable &operator&=(const NonCopyable) = delete; }; #endif
testServer
#include "Acceptor.h" #include "InitConnection.h" #include <iostream> #include <unistd.h> //这个案例 目前只能链接一个客户端 using std::cout; using std::endl; void test1(){ Acceptor acceptor1("127.0.0.1",8888); acceptor1.ready(); //这里就是已经到accept 步骤了 InitConnection conn(acceptor1.accept()); //这里呢,实际上,就是connection 类中需要已经链接好的fd参数,进行Connection中的操作 //InitConnection conn2(acceptor1.accept()); cout<< conn.printConnetionInfo() << "链接成功"<<endl; //cout<< conn2.printConnetionInfo() << "链接成功"<<endl; //测试同时开启两个不行,还是需要写多路复用后才可以 while(1) { cout << ">>recv msg from client: " << conn.receive() << endl; conn.send("hello baby\n"); } } int main(){ test1(); return 0; }
测试结果
使用telnet进行测试,当我测试发送中文时,telnet 默认发送了数字
//有问题或者发现问题,可以直接留言