用socket编写聊天软c语言件,Socket封装之聊天程序(一)

之前使用IPC编写过聊天程序,但是这样仅能在同一台计算机上进行聊天;要使得在不同的计算机(不同的IP+端口)上也能进行通信,就需要用到socket编程。前面说到,要处理多客户端的响应问题,需要I/O复用,即调用select或者epoll。通常我们使用epoll函数,以下例子也是。

接下来,我们需要封装一个地址类。为什么要封装这样一个类呢?

在前面的练习中,我们可以看到,在socket规程中,需要反复用到struct sockaddr_in 这个地址,包括以下的bind绑定过程也是经常出现的,而且这些方法其实都是系统函数,我们并不希望每次都直接使用,不进繁琐难记,而且可读性差。所以,我们需要封装一个地址类CAdress,将这些步骤在函数内部实现。

4bed6b1fb123b6e7435c7e73eec51f39.png

编写CAdress类

4a554b548de36f58f5bb06ea70446b85.png

CAdress.h:

#ifndef _ADRESS_H_

#define _ADRESS_H_

#include //sockaddr_in

#include

class CAdress

{

public:

CAdress(char *ip,unsigned short port);

CAdress();

~CAdress();

void setIP(char *ip);

void setPort(unsigned short port);

char *getIP();

unsigned short getPort();

struct sockaddr *getAddr();

socklen_t getAddrLen();

socklen_t *getAddrLenPtr();

private:

struct sockaddr_in m_addr;

socklen_t m_addrLen;

};

#endif

CAdress.cpp:

#include "adress.h"

CAdress::CAdress( char *ip,unsigned short port )

{

m_addr.sin_family = AF_INET;

m_addr.sin_port = htons(port);

m_addr.sin_addr.s_addr = inet_addr(ip);

m_addrLen = sizeof(struct sockaddr_in);

}

CAdress::CAdress()

{

m_addrLen = sizeof(struct sockaddr_in);

}

CAdress::~CAdress()

{

}

void CAdress::setIP( char *ip )

{

m_addr.sin_addr.s_addr = inet_addr(ip);

}

void CAdress::setPort( unsigned short port )

{

m_addr.sin_port = htons(port);

}

char * CAdress::getIP()

{

return inet_ntoa(m_addr.sin_addr);

}

unsigned short CAdress::getPort()

{

return ntohs(m_addr.sin_port);

}

struct sockaddr * CAdress::getAddr()

{

return (struct sockaddr *)&m_addr;

}

socklen_t CAdress::getAddrLen()

{

return m_addrLen ;

}

socklen_t *CAdress::getAddrLenPtr()

{

return &m_addrLen ;

}

编写主程序

common.h:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "adress.h"

#include //sockaddr_in

#include

using namespace std;

//包头

typedef struct pack_head

{

char type; //1-登录包 2-聊天包

int size; //包体的长度

}PK_HEAD;

//登录包

typedef struct pack_login

{

char name[10];

char pwd[8];

}PK_LOGIN;

//消息包

typedef struct pack_chat

{

char fromName[10];

char toName[10];

char msg[100];

}PK_CHAT;

#define HEAD_SIZE sizeof(PK_HEAD)

#define LOGIN_SIZE sizeof(PK_LOGIN)

#define CHAT_SIZE sizeof(PK_CHAT)

#define LOGIN_OK 1

#define LOGIN_FAIL 0

#endif

server.cpp:

#include "common.h"

#define MAX_LISTEN_SIZE 10

#define MAX_EPOLL_SIZE 1000

#define MAX_EVENTS 20

int 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}; //聊天包

map userMap; //

map::iterator it;

int reply; //登录应答包。 1-成功 0-失败

//1.socket()

sockfd = socket(PF_INET,SOCK_STREAM,0);

if(sockfd == -1)

{

perror("socket");

return -1;

}

//2.bind()

char ip[20] = "192.168.159.6";

CAdress addr(ip,1234);

ret = bind(sockfd,addr.getAddr(),addr.getAddrLen());

if(ret == -1)

{

perror("bind");

return -1;

}

//3.listen()

ret = listen(sockfd,MAX_LISTEN_SIZE);

if(ret == -1)

{

perror("listen");

return -1;

}

//4.epoll初始化

epfd = epoll_create(MAX_EPOLL_SIZE); //创建

event.data.fd = sockfd;

event.events = EPOLLIN ;

epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event); //添加sockfd

CAdress connAddr;

//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++)

{

//如果是 sockfd

if( rtlEvents[i].data.fd == sockfd )

{

connfd = accept(sockfd,connAddr.getAddr(),connAddr.getAddrLenPtr());

//添加到事件集合

event.data.fd = connfd;

event.events = EPOLLIN;

epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event);

printf("client ip:%s ,port:%u connect.\n",connAddr.getIP(),connAddr.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",connAddr.getIP(),connAddr.getPort());

cout<

}

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(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.");

memset(chat.toName,0,10);

memcpy(acbuf + HEAD_SIZE, &chat, CHAT_SIZE);

write(rtlEvents[i].data.fd, acbuf, HEAD_SIZE + CHAT_SIZE);

}

}

break;

}

}

}

}

}

}

return 0;

}

运行结果

70f8a5b1765b6c2512451895858b2b17.png

至此,我们完成了简单的聊天功能,接下来我们将进一步学习,如何封装socket,并逐步完善功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值