今天,学习一下socket的封装。

类图

  首先,我们把需要封装的各个类初步的设计如下:


  接下来,我们建立类与类之间的关系:

  其中,CStream类可有可无,这个类是用来封装各种读写流的。

socket封装

stream类

stream.h:

class CStream
{
public:CStream(int fd = -1);~CStream();void SetFd(int fd);int GetFd();int Read(char *buf, int count);     //阻塞读   int Read(char *buf, int count, int sec);    //超时读
//  int Read(char *buf, int count, CAddress &addr);     //UDP读bool Write(char *buf,int count);    //普通写private:int m_fd;
};

stream.cpp:

include "stream.h"CStream::CStream( int fd /*= -1*/ )
{this->m_fd = fd;
}CStream::~CStream()
{}void CStream::SetFd( int fd )
{this->m_fd = fd;
}int CStream::GetFd()
{return this->m_fd;
}int CStream::Read( char *buf, int count )
{int nByte;nByte = read(m_fd,buf,count);if (nByte == -1){perror("read");}return nByte;
}int CStream::Read( char *buf, int count, int sec )
{//用selectfd_set set;int nEvent;struct timeval time = {0};FD_ZERO(&set);FD_SET(m_fd,&set);time.tv_sec = sec;nEvent = select(m_fd + 1, &set, NULL, NULL, &time);if (nEvent == -1){perror("select");}else if (nEvent == 0) {printf("time out.\n");}else{return Read(buf,count);}return nEvent;
}bool CStream::Write( char *buf,int count )
{int nByte;nByte = write(m_fd,buf,count);if (nByte == -1){perror("write");return false;}return true;
}

socket基类

SocketBase.h:

typedef enum socket_type
{tcp_sock,udp_sock
}SOCKET_TYPE;class CSocketBase
{
public:CSocketBase(int fd = -1);CSocketBase(char *ip, unsigned short port, int fd);void SetFd(int fd);int GetFd();void SetAddr(char *ip, unsigned short port);void SetAddr(CAddress &addr);CAddress GetAddr();bool Socket(int type = tcp_sock);bool Bind();virtual int Read(char *buf,int count);virtual bool Write(char *buf,int count);bool Close();protected:int m_fd;CAddress m_addr;CStream m_stream;
private:
};

SocketBase.cpp:

CSocketBase::CSocketBase( int fd /*= -1*/ )
{m_fd = fd;
}CSocketBase::CSocketBase( char *ip, unsigned short port, int fd )
{m_addr.SetIP(ip);m_addr.SetPort(port);m_fd = fd;
}void CSocketBase::SetFd( int fd )
{m_fd = fd;
}int CSocketBase::GetFd()
{return m_fd;
}void CSocketBase::SetAddr( char *ip, unsigned short port )
{m_addr.SetIP(ip);m_addr.SetPort(port);
}void CSocketBase::SetAddr( CAddress &addr )
{m_addr.SetIP(addr.GetIP());m_addr.SetPort(addr.GetPort());
}CAddress CSocketBase::GetAddr()
{return m_addr;
}bool CSocketBase::Socket( int type /*= TCP_SOCK*/ )
{if (type == tcp_sock){m_fd = socket(PF_INET, SOCK_STREAM ,0);}else{m_fd = socket(PF_INET, SOCK_DGRAM ,0);}if (m_fd == -1){perror("socket");return false;}m_stream.SetFd(m_fd);return true;
}bool CSocketBase::Bind()
{int ret = bind(m_fd,m_addr.GetAddr(),m_addr.GetAddrSize());if (ret == -1){perror("bind");return false;}return true;
}int CSocketBase::Read( char *buf,int count )
{}bool CSocketBase::Write( char *buf,int count )
{}bool CSocketBase::Close()
{if (close(this->m_fd) == -1){return false;}return true;
}

TcpSocket类

TcpSocket.h:

class CTcpServer:public CTcpSocket
{
public:CTcpServer(char *ip,unsigned short port,int fd);bool Listen(int backlog);CTcpClient Accept();protected:
private:
};

TcpSocket.cpp:

CTcpSocket::CTcpSocket( int fd /*= -1*/ )
:CSocketBase(fd)
{}CTcpSocket::CTcpSocket( char *ip,unsigned short port, int fd)
:CSocketBase(ip,port,fd)
{}int CTcpSocket::Read( char *buf, int count )
{return m_stream.Read(buf,count);
}bool CTcpSocket::Write( char *buf, int count )
{return m_stream.Write(buf,count);
}

客户端、服务器封装

TcpServer类

TcpServer.h:

class CTcpServer:public CTcpSocket
{
public:CTcpServer(char *ip,unsigned short port,int fd);bool Listen(int backlog);CTcpClient Accept();protected:
private:
};

TcpServer.cpp:

CTcpServer::CTcpServer( char *ip,unsigned short port, int fd)
:CTcpSocket(ip,port,fd)
{}bool CTcpServer::Listen( int backlog )
{int ret = listen(m_fd,backlog);if(ret == -1){perror("listen");return false;}return true;
}CTcpClient CTcpServer::Accept()
{CTcpClient client;int conn_fd;CAddress conn_addr;conn_fd = accept(m_fd,conn_addr.GetAddr(),conn_addr.GetAddrSizePtr());if (conn_fd == -1){perror("accept");//抛出异常}client.SetFd(conn_fd);client.SetAddr(conn_addr);return client;  //记得重载拷贝构造??深拷贝
}

TcpClient类

TcpClient.h:

class CTcpClient:public CTcpSocket
{
public:CTcpClient();CTcpClient(char *ip,unsigned short port,int fd);bool Connect(CAddress &ser_addr);
protected:
private:
};

TcpClient.cpp:

CTcpClient::CTcpClient()
:CTcpSocket()
{}CTcpClient::CTcpClient( char *ip,unsigned short port,int fd )
:CTcpSocket(ip,port,fd)
{}bool CTcpClient::Connect( CAddress &ser_addr )
{int ret = connect(m_fd,ser_addr.GetAddr(),ser_addr.GetAddrSize());if(ret == -1){perror("connect");return false;}return true;
}

主函数

  接下来,只要修改我们之前的server.cpp和client.cpp就可以了。
servercpp:

#include "common.h"#define MAX_LISTEN_SIZE 10
#define MAX_EPOLL_SIZE 1000
#define MAX_EVENTS 20int main()
{   int sockfd;int connfd;int reuse = 0;int epfd; int nEvent = 0;struct epoll_event event = {0};struct epoll_event rtlEvents[MAX_EVENTS] = {0};char acbuf[100] = "";int ret;PK_HEAD head = {0};     //包头PK_LOGIN login ={0};    //登录包PK_CHAT chat = {0};     //聊天包int reply;              //登录应答包。 1-成功 0-失败//1.socket()char ip[20] = "192.168.159.6";unsigned short port = 1234;CTcpServer server(ip,port,sockfd);SOCKET_TYPE type = tcp_sock;server.Socket(type);//2.bind()server.Bind();//3.listen()server.Listen(MAX_LISTEN_SIZE);//4.epoll初始化epfd = epoll_create(MAX_EPOLL_SIZE);    //创建event.data.fd = server.GetFd();event.events = EPOLLIN ;    epoll_ctl(epfd,EPOLL_CTL_ADD,server.GetFd(),&event);    //添加sockfdCTcpClient conn;//5.通信while(1){nEvent = epoll_wait(epfd,rtlEvents,MAX_EVENTS,-1);   //阻塞if(nEvent == -1){perror("epoll_wait");return -1;}else if(nEvent == 0){printf("time out.");}else{//有事件发生,立即处理for(int i = 0; i < nEvent; i++){//如果是 sockfdif( rtlEvents[i].data.fd == server.GetFd() ){conn = server.Accept();//添加到事件集合event.data.fd = conn.GetFd();event.events = EPOLLIN;  epoll_ctl(epfd,EPOLL_CTL_ADD,conn.GetFd(),&event);printf("client ip:%s ,port:%u connect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());}else    //否则 connfd {ret = read(rtlEvents[i].data.fd,acbuf,100);if( ret == 0) //客户端退出{close(rtlEvents[i].data.fd);//从集合里删除epoll_ctl(epfd,EPOLL_CTL_DEL,rtlEvents[i].data.fd,rtlEvents);//从用户列表删除string username;for (it = userMap.begin(); it != userMap.end(); it++){if (it->second == rtlEvents[i].data.fd){username = it->first;userMap.erase(it);break;}}printf("client ip:%s ,port:%u disconnect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());cout<<"client "<<username<<" exit."<<endl;}else{   //解包memset(&head,0,sizeof(head));memcpy(&head,acbuf,HEAD_SIZE);switch(head.type){case 1:memset(&login,0,sizeof(login));memcpy(&login,acbuf + HEAD_SIZE,LOGIN_SIZE);//通过connfd,区分不同客户端//如果重复登录,失败,让前一个账号下线 ; 如果登录成功,服务器要发送一个应答包给客户端。if ( (it = userMap.find(login.name)) != userMap.end()){reply = LOGIN_FAIL;memset(acbuf,0,100);head.size = 4;memcpy(acbuf,&head,HEAD_SIZE);memcpy(acbuf + HEAD_SIZE , &reply , 4);write(it->second,acbuf,HEAD_SIZE + 4);  //登录失败应答包printf("client %s relogin.\n",login.name);}else{printf("client %s login.\n",login.name);}reply = LOGIN_OK;memcpy(acbuf + HEAD_SIZE , &reply , 4);write(rtlEvents[i].data.fd,acbuf,HEAD_SIZE + 4);    //登录成功应答包userMap.insert(pair<string,int>(login.name,rtlEvents[i].data.fd));break;case 2: memset(&chat,0,CHAT_SIZE);memcpy(&chat,acbuf + HEAD_SIZE,CHAT_SIZE);if(strcmp(chat.toName,"all") == 0){//群聊for (it = userMap.begin(); it != userMap.end(); it++){//转发消息if (it->second != rtlEvents[i].data.fd){write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);}}}else{//私聊if ( (it = userMap.find(chat.toName)) != userMap.end()) //找到了{//转发消息write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);}else    //用户不存在{memset(&chat.msg,0,100);strcpy(chat.msg,"the acccount is not exist.");memcpy(acbuf + HEAD_SIZE, &chat, CHAT_SIZE);write(rtlEvents[i].data.fd, acbuf, HEAD_SIZE + CHAT_SIZE);}}break;case 3:memcpy(acbuf + HEAD_SIZE,&userMap,USERS_SIZE);write(rtlEvents[i].data.fd, acbuf, HEAD_SIZE + USERS_SIZE);break;}}}}}}return 0;
}

client.cpp:

#include "common.h"void handler(int no)
{exit(0);
}int main()
{int ret;int sockfd;short int port = 0;char ip[20] = "";char acbuf[100] = "";PK_HEAD head = {0};     //包头PK_LOGIN login ={0};    //登录包PK_CHAT chat = {0};     //聊天包int reply;              //登录应答包pid_t pid;printf("input ip: ");fflush(stdout);scanf("%s",ip);printf("input port: ");fflush(stdout);scanf("%d",&port);//1.socket();CTcpClient client;client.Socket();sockfd = client.GetFd();//2.连接connect() 服务器的地址CAddress serAddr(ip,port);if (client.Connect(serAddr)){printf("connect OK.\n");}//登录printf("input username: ");fflush(stdout);scanf("%s",login.name);head.type = 1;head.size = LOGIN_SIZE;memcpy(acbuf,&head,HEAD_SIZE);memcpy(acbuf + HEAD_SIZE,&login,LOGIN_SIZE);client.Write(acbuf, HEAD_SIZE + LOGIN_SIZE);//登录成功之后,主进程负责发包,子进程负责收包pid  = fork();if (pid == -1){perror("fork");return -1;}else if (pid == 0) //子进程{while(1){//一直读memset(acbuf,0,100);client.Read(acbuf,100);//解包memset(&head,0,HEAD_SIZE);memcpy(&head,acbuf,HEAD_SIZE);switch(head.type){case 1: //登录memcpy(&reply,acbuf + HEAD_SIZE,4);if (reply == LOGIN_FAIL){printf("your account has been logged in elsewhere.\n");kill(getppid(),SIGINT); //结束程序return 0;}else if (reply == LOGIN_OK){printf("login OK.\n");}else{printf("login failed.\n");kill(getppid(),SIGINT); //结束程序return 0;}break;case 2://聊天memset(&chat,0,CHAT_SIZE);memcpy(&chat,acbuf + HEAD_SIZE,CHAT_SIZE);printf("from %s: %s\n",chat.fromName,chat.msg); break;case 3://用户列表
//              memset(&userMap,0,USERS_SIZE);
//              memcpy(&userMap,acbuf + HEAD_SIZE,USERS_SIZE);
//              printf("========= online users =========\n");
//              for (it = userMap.begin(); it != userMap.end() ; it++)
//              {
//                  cout<<it->first<<endl;
//              }
//              printf("================================\n");}}}else{signal(SIGINT,handler);//3.通信while(1){printf("#");fflush(stdout);scanf("%s",acbuf);if(strcmp(acbuf,"exit") == 0){kill(pid,SIGINT);break;}else if(strcmp(acbuf,"chat") == 0){printf("to who: ");fflush(stdout);scanf("%s",chat.toName);printf("say: ");fflush(stdout);scanf("%s",chat.msg);strcpy(chat.fromName ,login.name);memset(acbuf,0,100);head.type = 2;memcpy(acbuf,&head,HEAD_SIZE);memcpy(acbuf + HEAD_SIZE ,&chat, CHAT_SIZE);client.Write(acbuf, HEAD_SIZE + CHAT_SIZE);}else if (strcmp(acbuf,"users") == 0){head.type = 3;head.size = USERS_SIZE;memset(acbuf,0,100);memcpy(acbuf,&head,HEAD_SIZE);client.Write(acbuf,HEAD_SIZE + USERS_SIZE);}}}//4.close()if (!client.Close()){perror("close");}return 0;
}

  搞定,(保证正常运行就OK了,不过,这里功能还有问题)下次,封装epoll~

转载于:https://blog.51cto.com/13097817/2066440

Socket封装之聊天程序(二)相关推荐

  1. 用c语言编写一个1V1聊天程序,socket多人聊天程序C语言版(二)

    1V1实现了,1V多也就容易了.不过相对于1V1的程序,我经过大改,采用链表来动态管理.这样效率真的提升不少,至少CPU使用率稳稳的在20以下,不会飙到100了.用C语言写这个还是挺费时间的,因为什么 ...

  2. Node.js + Web Socket 打造即时聊天程序嗨聊(上)

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  3. [前端] Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  4. Socket网络编程--聊天程序(3)

    上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...

  5. Socket网络编程--聊天程序(8)

    上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的 ...

  6. Node.js + Web Socket 打造即时聊天程序嗨聊(1)

    2019独角兽企业重金招聘Python工程师标准>>> 本文将使用Node.js加web socket协议打造一个网页即时聊天程序,取名为HiChat,中文翻过来就是'嗨聊',听中文 ...

  7. 利用TCP和UDP协议,实现基于Socket的小聊天程序(初级版)

    TCP TCP (Transmission Control Protocol)属于传输层协议.其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送.可靠性.有效流控.全双工操作和多路复用 ...

  8. 基于Udp的Socket网络编程聊天程序

    1.新建一个工程区Net 在工作区中添加两个工程 NetSrv 和 NetClient 为两个工程添加库文件 (Link中) ws2_32.lib 2.在工程NetSrv中添加Server.cpp文件 ...

  9. socket多人聊天程序C语言版(一)

    ==> 学习汇总(持续更新) ==> 从零搭建后端基础设施系列(一)-- 背景介绍 socket编程client和server直接通信是很简单的,就是一个发送一个接收就完了,但这却是基础. ...

  10. c# WINFORM SOCKET编程-简单聊天程序(服务端)

    初学C#的SOCKET编程,照着网上的代码写下来,程序总是有问题,经过自己长时间的调试,完成程序,以下是原码,有需要可以参考一下,还不完善,欢迎大家批评指正.(这里的代码没更新,附件重新上传更新,在另 ...

最新文章

  1. 函数,游标与存储过程的综合应用
  2. timerfd与epoll
  3. 安川g7接线端子图_图解西门子S7-300plc模拟量模块接线方法
  4. ERROR 2006 (HY000) MySQL server has gone away
  5. Antlr中文文档初稿2(《ANTLR树分析器》)
  6. 指出Linux内核中boot,uBoot和Linux内核中涉及到的几个地址参数的理解
  7. 企业级生产环境CICD入门
  8. mui.ajax中文乱码
  9. java怎么设置颜色_java怎么设置颜色
  10. xp系统简单tcpip服务器,XP系统怎样安装TCP/IP协议
  11. 3.3、怎么通过STLINK下载程序(附STLINK驱动包)
  12. 微软drive服务器,OneDrive:微软云存储服务
  13. ImportError: DLL load failed while importing win32file
  14. matlab的RI是什么,LTE上报的CQI、PMI、RI分别有什么用
  15. dfuse for EOSIO v0.1.0-beta4 版本更新说明
  16. 文华学院计算机专业师资,华中科技大学文华学院“最受欢迎教师”名单
  17. spring boot物联网智能管理平台 毕业设计-附源码211120
  18. 控制系统设计专题(四)——控制分配矩阵的求解及相关问题
  19. onload和ready的不同
  20. 【明哥版】2020最新Android Studio Win10 安装教程

热门文章

  1. 购物车的简单添加与计算
  2. 重大发现: windows下C++ UI库 UI神器-SOUI(转载)
  3. 洛谷1008 三连击
  4. 23 android多线程
  5. Oracle Concepts Guide 中 Oracle 实例 和 数据库 【关系图】
  6. Oracle and MS SQL Server 2005
  7. python第二十二课——list函数
  8. HDU-3664 Permutation Counting(DP)
  9. Mybatis(2)——Mapper映射文件
  10. 深入Linux内核架构(中文版)pdf