【C++简单项目】基于socket实现的聊天室Chat_Room

一、需求分析

聊天室中如果有人说话,服务器将内容传送给聊天室的其他人。
那么就需要客户端和服务端两个程序,一个人发送一个消息,服务器向所有人发送一遍消息,所有人的客户端接收消息,也就是说客户端负责发送和接受消息服务端负责接收和转发消息

1.客户端Client:
可以主动连接服务端;
可以与服务器之间完成接收和发送消息;

2.服务端Server:
可以接受来自客户端的连接请求;
将客户端发来的信息发送给对应的客户(广播或者私聊);

二、实现逻辑

1、服务端Server

创建服务器套接字:socket
绑定本机IP和端口:bind
监听客户端:listen
等待客户端连接:accept
发送消息:send
接收消息:recv

1)创建套接字socket

sockfd = socket(PF_INET,SOCK_STREAM,0);

2)使用bind() 将套接字与本IP和某一端口绑定

//绑定端口号和IP
serverAddr.sin_family = PF_INET;
serverAddr.sin_port = htons(SERVER_PORT);    
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));

3)监听套接字listen()

int ret = listen(sockfd,5);

4)接受客户端的连接请求 accept(),返回一个对此连接的新套接字

int clientfd = accept( sockfd,(struct sockaddr*)&client_address,&client_addrLenth);

sockfd:套接字描述符,
client_address:提出连接请求的客户端的主机地址
client_addrLenth:客户端的地址长度

5)用新的套接字与客户端通信,发送send() 和接受recv() 客户端数据

int send(int sockfd, const void * data, int data_len, unsigned int flags)

sockfd:套接字描述符
data:指向要发送数据的指针
data_len:数据长度,flags:通常为0

6)关闭套接字close()

2、客户端Client

客户端的端口号是系统自动分配的,所以客户端并不需要 bind 绑定地址,而且也不需要设置监听的套接字,因此也不需要 listen。

客户端在用 socket 创建套接字后直接调用 connect 向服务器发起连接即可,connect 函数通知 Linux 内核完成 TCP 三次握手连接,最后把连接的结果作为返回值。成功建立连接后我们就可以调用 sendrecv 来发送数据、接收数据,最后调用 close 来断开连接释放资源。

创建客户端套接字:socket
向服务器发起连接:connect
发送消息:send
接收消息:recv

在这里插入图片描述

三、提升服务器的处理能力?

服务器需要维护与多个客户端的连接。对于一个服务器,要是聊天的人一多就会出现严重延迟是绝对不可以的,也就是一个个轮询的方式是费时费力的,那么我们会想办法解决这个问题。

1、为什么用epoll?

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select/poll的实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

epoll的底层维护是一颗红黑树,查找和删除修改等等操作都是log级别的,所有很快,具体来说就是一颗红黑树,里面有很多fd,此时来了一个事件,我在树上快速查找有没有与之对应的fd,有就将其添加至list里。然后由下面讲的epoll_wait去等,等待list不为空、收到信号、超时这三种条件后返回一个值。

2、epoll的接口

epoll的实现非常简单,一共就三个接口函数:

  1. int epfd = epoll_create(int size);
    首先通过create_epoll(int size)来创建一个epoll的句柄,其中size为要监听的数目。这个函数会返回一个新的epoll句柄 epfd ,之后的所有操作将通过 epfd 来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

  2. int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
    epoll的事件注册函数,它不同于是select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
    其中,第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
    EPOLL_CTL_ADD:注册新的fd到epfd中;
    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd;
    第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,

  3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    等待事件的产生,通过调用收集在句柄为epfd的epoll中监控到的已发生的事件,把这些事件放入事件队列events里面去。

3、关于LT和ET两种工作模式:

对于水平触发(LT) 模式,一个事件只要有,就会一直触发。
对于边缘触发(ET) 模式,在一个事件从无到有时才会触发。
水平触发是每次epoll_wait() 会将所有可读写的fd返回,系统开销比较大,而边沿触发则是只会返回一次,如果这次我们没有及时处理,那么下一次调用epoll_wait() 则不会有这个fd,除非这个fd再次被触发事件。

对于socket的读事件
水平模式——只要在socket上有未读完的数据,就会一直产生EPOLLIN事件;
于边缘模式——socket上每新来一次数据就会触发一次,如果上一次触发后未将socket上的数据读完,也不会再触发,除非再新来一次数据。

对于socket写事件
水平模式——如果socket的TCP窗口一直不饱和,就会一直触发EPOLLOUT事件;
边缘模式——只会触发一次,除非TCP窗口由不饱和变成饱和再一次变成不饱和,才会再次触发EPOLLOUT事件。

结论:
ET模式仅当状态发生变化的时候才获得通知,这里说的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;
而LT模式是只要有数据没有处理就会一直通知下去的.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值