Linux聊天室(IO多路复用、多线程)

1、功能描述

建立一个服务器,有很多个客户端加入服务器,就可以实现群聊功能
基于tcp的select服务器与客户端

2、知识点

  • 服务器:建立套接字、设置ip和port、绑定ip和port、监听、连接、收发消息。
  • 客户端:建立套接字、配置服务器ip和port、连接服务器、创建线程收发消息。
  • ​服务器需要很多连接加入,所以用到IO多路复用。

3、功能实现

1、chat_room.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>  
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/select.h>
// 聊天室和人数
static char rooms[20] = {0};
static int pnum=0;

int server_init(char *ip, int port)
{
	int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sfd){
        perror("socket");
        return -1;
    }
    //设置ip port
    struct sockaddr_in sddr, cddr;
    memset(&sddr, 0, sizeof(sddr));
    bzero(&cddr, sizeof(struct sockaddr_in));
    sddr.sin_family = AF_INET;
    sddr.sin_port = htons(8008);
    //设置服务器为自己主机的任意ip == INADDR_ANY
    sddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    /*
     *bind:将ip,port 和 套接字绑定
     *参数:sockfd (struct sockaddr *)  saddr大小
     *返回值  0成功  -1失败
     * */
    if(-1 == bind(sfd,(void *)&sddr,sizeof(sddr)))
    {
        perror("bind");
        return -1;
    }
    //监听
    /*
     *listen:监听套接字有无连接进来
     *sockfd: 目标套接字的文件描述符
     *10: 监听队列的长度
     *return : 0 success  -1 failed
     * */
    if(-1 == listen(sfd, 10))
    {
        perror("listen");
        return -1;
    }
    puts("listen---------");
    return sfd;	
}

int server_wait_client_connect(int sfd)
{
	struct sockaddr_in sddr, cddr;
	socklen_t len = sizeof(cddr);
    int nfd = accept(sfd,(void *)&cddr,&len);
    if(-1 == nfd){
        perror("accept");
        return -1;
    }
    //解析客户端ip 和 port 需要用到inet_ntoa() ntohs()转换
    printf("IP:%s PORT:%d connected.\n",inet_ntoa(cddr.sin_addr),
            ntohs(cddr.sin_port));
            return nfd;
}
int main(int argc, char *argv[])
{ 
    int listenfd = server_init("0.0.0.0", 8008);
    if(-1 == listenfd)
        return -1;
    //构建集合 操作集合 备份集合
    fd_set readfds, backfds;
    FD_ZERO(&backfds);
    //把关注的文件描述符添加到备份集合
    FD_SET(listenfd, &backfds);
    //记录文件描述符的个数
    int nfds = listenfd + 1;
    int ret = -1;
    char buf[100]={0};
    while(1){
        //设置操作的集合
        readfds = backfds;
        //个数 读取 写 时间
        //select会阻塞循环检测关注的IO集合
        ret = select(nfds,&readfds,NULL,NULL,NULL);
        if(ret == -1)
        {
            perror("select");
            return -1;
        }
        //遍历文件描述符是否还在集合中
        for(int fd=0;fd<nfds;fd++){
            if(!FD_ISSET(fd, &readfds))
                continue;//不再集合中结束本次循环
            //判断是否是listenfd准备就绪
            if(fd == listenfd){
                int confd = server_wait_client_connect(fd);
                //将通信套接字confd加入备份集合
                FD_SET(confd, &backfds);
                //集合内的文件描述符+1
                nfds = (confd>=nfds?nfds+1:nfds);
                //聊天室人数+1
                pnum++;
                //加入通信套接字到聊天室
                rooms[pnum-1] = confd;
                printf("聊天室人数:%d\n", pnum);
            }
            else{
                //printf("加入的fd:%d\n", fd);
                //处理通信套接字
                ret = read(fd, buf, 100);
                if(ret <= 0)
                {
                    //将对应通信套接字从集合删除
                    FD_CLR(fd, &backfds);
                    //聊天室人数-1
                    //printf("触发-1\n");
                    int j=0;
                    for(int i=0;i<pnum;i++){
                        if(rooms[i] == fd){
                            pnum--;
                            continue;
                        }
                        rooms[j++] = rooms[i];
                    }
                    printf("聊天室人数:%d\n", pnum);
                    close(fd);
                    continue;
                }
                //把消息转发给聊天室的所有人
                for(int i=0;i<pnum;i++){
                    write(rooms[i], buf, 100);
                }
            }
        }
    }
    close(listenfd);
    return 0;
} 

2、client.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h> 
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

void *send_msg(void *arg);
void *recv_msg(void *arg);

int main(int argc, char *argv[])
{
    if(argc != 3){
        printf("%s ip port.\n", argv[0]);
        return -1;
    }
    //参数:ip协议  TCP   0
    //应用层到底层的通道
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sfd){
        perror("socket");
        return -1;
    }
    //设置ip port
    struct sockaddr_in sddr, cddr;
    memset(&sddr, 0, sizeof(sddr));
    bzero(&cddr, sizeof(struct sockaddr_in));
    sddr.sin_family = AF_INET;
    sddr.sin_port = htons(atoi(argv[2]));
    //设置服务器为自己主机的任意ip == INADDR_ANY
    sddr.sin_addr.s_addr = inet_addr(argv[1]);
    int confd = connect(sfd,(void *)&sddr,sizeof(sddr));
    if(-1 == confd)
    {
        perror("connect");
        return -1;
    }
    printf("connect success.\n");
    //创建两个子线程
    pthread_t sid, rid;
    //发送消息线程
    sid = pthread_create(&sid,NULL, send_msg, &sfd);
    if(sid == -1)
    {
        perror("pthread_create1");
        return -1;
    }
    //接收消息线程
    rid = pthread_create(&rid,NULL, recv_msg, &sfd);
    if(rid == -1)
    {
        perror("pthread_create1");
        return -1;
    }
    char buf[100] = {0};
    while(1){
        sleep(1);
    }
    return 0;
}


void *send_msg(void *arg){
    int fd = *((int *)arg);
    char buf[100] = {0};
    while(1){
        fgets(buf, 99, stdin);
        write(fd, buf, 100);
        if(strncmp(buf,"quit",4)==0)
            exit(0);
    }
}
void *recv_msg(void *arg){
    int fd = *((int *)arg);
    char buf[100] = {0};
    while(1){
        int n = read(fd, buf, 100);
        if(n==0) exit(0);
        printf("Recv:%s", buf);
    }
}

4、编译

gcc chat_room.c -o chat
gcc client.c -o cli -lpthread
服务器:
	./chat
客户端:
	./cli 127.0.0.1 8008
	./cli 127.0.0.1 8008
	./cli 127.0.0.1 8008

5、效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值