用select多路io复用实现简单聊天程序

1、服务端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 2233  //端口号
#define MAX_CLI 32 //系统能容纳的客户数


/*记录客户信息*/
struct client_info{
    char msg[1024];
    char c_name[64];//客户名字
    char s_name[64];//私聊客户名字
    int fd;//新连接描述符
    struct sockaddr_in client;//其IP地址
};
/*记录客户发来信息*/
struct client_recvinfo{
    char msg[1024];
    char c_name[64];//客户名字
    char s_name[64];//私聊客户名字
};
struct client_info bak_cli[MAX_CLI];//备份用户信息
/*将发送过来的信息保存*/
struct client_info  copy_recv(struct client_info bak,const struct client_recvinfo new){
    strncpy(bak.msg,new.msg, strlen(new.msg));
    strncpy(bak.c_name,new.c_name, strlen(new.c_name));
    strncpy(bak.s_name,new.s_name, strlen(new.s_name));
    return bak;
}

/*打包要发送的信息*/
void buf_send(char* buf,const struct client_recvinfo new){

    if(strcmp(new.s_name,"all")==0){
        strcat(buf,"\033[31m");
        strcat(buf,new.c_name);
        strcat(buf,"\033[0m");
        strcat(buf,"(群发)");
        strcat(buf,": ");
        strcat(buf,new.msg);
        //printf("\n==pu==\n");
    }
    else{
        strcat(buf,"\033[31m");
        strcat(buf,new.c_name);
        strcat(buf,"\033[0m");
        strcat(buf,"(私发)");
        strcat(buf,": ");
        strcat(buf,new.msg);
        //printf("\n==pr==\n");
    }
}

//向所有在线用户发送在线的用户列表
void send_oname(){
    char on_names[1024];
    long ret;
    //记录在线的用户列表
    memset(&on_names,0, sizeof(on_names));
    strcat(on_names,"当前在线用户: ");
    for(int i =0;i<MAX_CLI;i++){
        if(bak_cli[i].fd!=0){
            strcat(on_names,"[");
            strcat(on_names,bak_cli[i].c_name);
            strcat(on_names,"] ");
        }
    }
    //printf("=%s=\n",on_names);

    //向所有在线用户发送
    for(int i = 0;i<MAX_CLI;i++){
        if(bak_cli[i].fd!=0) {
            ret = send(bak_cli[i].fd, &on_names, strlen(on_names), 0);
            if (ret < 0) {
                perror("send name");
            }
        }
    }
}

/*初始化服务器*/
int init_tcp_server(unsigned short port){
    int listen_fd;
    int ret;
    int opt;
    struct sockaddr_in self;

    //监听描述符
    listen_fd = socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd < 0){
        perror("socket");
        return -1;
    }

    // 配置监听描述符地址复用属性
    opt = 1;
    ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
    if (ret < 0) {
        perror("set socket opt");
        return -1;
    }

    // 填充服务器开放接口和端口号信息
    memset(&self,0, sizeof(self));
    self.sin_family = AF_INET;
    self.sin_port = htons(port);
    self.sin_addr.s_addr = htonl(INADDR_ANY);

    //将self与listen_fd绑定
    ret = bind(listen_fd,(struct sockaddr *)&self, sizeof(self));
    if(ret<0){
        perror("bind");
        return  -1;
    }

    // 默认socket是双向,配置成监听模式
    listen(listen_fd, 5);

    return listen_fd;
}
/*与客户端建立连接,并保存登陆信息*/
struct client_info  listen_hander(int fd){
    long ret;
    struct  client_info new;
    struct client_recvinfo name;

    memset(&new,0, sizeof(new));
   unsigned int len = sizeof(new.client);
    //监听描述符
    new.fd= accept(fd,(struct sockaddr *) &(new.client),&len);
    if(new.fd<0){
        perror("accept");
    }

    //接收新的用户名
    ret = recv(new.fd,&name, sizeof(name),0);
    if(ret<0){
        perror("recv name");
    }
    //检测是否同名
    for(int i=0;i<MAX_CLI;i++) {
        if (strcmp(bak_cli[i].c_name, name.c_name) == 0) {
            ret = send(new.fd, "repetitive name", 15, 0);
            if (ret < 0) {
                perror("send repetitive name");
            }
            ret = -1;//用于下个循环判断是否重名
            close(new.fd);
            memset(&new, 0, sizeof(new));
            memset(&name, 0, sizeof(name));
            break;
        }
    }
    //保存新连接用户信息
    for(int i=0;i<MAX_CLI;i++) {
        if(ret < 0){
            break;
        }
        else if(bak_cli[i].fd==0){
            bak_cli[i] = copy_recv(new,name);

            //每上线一个用户就更新在线用户列表
            send_oname();

            printf("\033[32m%s\033[0m  %s:%d \033[32m online\033[0m\n",name.c_name,
                   inet_ntoa(new.client.sin_addr), ntohs(new.client.sin_port));
            break;
        }
    }

    return  new;
}

/*接收客户端数据并处理*/
int data_hander(int new_fd){

    struct client_recvinfo new;
    long ret;
    char buf[2048];
    int w_name = 1; //判断私发用户是否存在

    memset(&new,0, sizeof(new));
    ret = recv(new_fd, &new, sizeof(new), 0);
    if (ret < 0) {
        perror("recv");
        return -1;
    } else if (ret == 0) {
        //清理退出的用户
        for(int i =0;i<MAX_CLI;i++){
            if(bak_cli[i].fd==new_fd) {

                printf("\033[32m%s\033[0m  %s:%d \033[32m offline\033[0m\n",
                       bak_cli[i].c_name,
                       inet_ntoa(bak_cli[i].client.sin_addr),
                       ntohs(bak_cli[i].client.sin_port));

                memset(&bak_cli[i], 0, sizeof(bak_cli[i]));
                //每下线一个用户就更新在线用户列表
                send_oname();
            }

        }


        return 0;
    }
    else {
        // 打印消息

        //找到当前用户的备份信息
        for (int j = 0; j < MAX_CLI; j++) {
            if (bak_cli[j].fd == new_fd) {
                bak_cli[j] = copy_recv(bak_cli[j], new);
            }
        }
        //分析接收的消息并处理
        for (int i = 31; i >= 0; i--) {
            //群发
            if (strcmp(new.s_name,"all")==0) {
                if (new_fd != bak_cli[i].fd&&bak_cli[i].fd != 0) {
                    memset(&buf,0, sizeof(buf));
                    buf_send(buf, new);
                    ret = send(bak_cli[i].fd, buf, strlen(buf), 0);
                    if (ret < 0) {
                        perror("public send");
                    }
                    printf(" (群)%s send %s to %s\n",
                           new.c_name, new.msg, bak_cli[i].c_name);
                    w_name = 0;
                }
            }
                //私发
            else if (strcmp(new.s_name,
                            bak_cli[i].c_name) == 0) {
                memset(&buf,0, sizeof(buf));
                buf_send(buf, new);
                ret = send(bak_cli[i].fd, buf, strlen(buf), 0);
                if (ret < 0) {
                    perror("private send");
                }
                printf(" (私)%s send %s to %s\n",
                       new.c_name, new.msg, bak_cli[i].c_name);
                w_name = 0;
                break;
            }
        }

        if(w_name){
            ret = send(new_fd,"404",3, 0);
            if (ret < 0) {
                perror("send 404");
            }
            printf("%s send %s to %s failed!\n",
                   new.c_name, new.msg, new.s_name);
        }
        //回应收到了消息
//         ret = send(new_fd,"server: msg recv",16,0);
//         if(ret < 0){
//             perror("answer");
//         }
    }

    return (int)ret;
}

void main_loop(int listen_fd){
    fd_set set,bak_set;
    int max_fds;
    int ret;
    struct  client_info new;

    // 把监听描述符、标准输入描述符添加到集合
    FD_ZERO(&set);
    FD_SET(listen_fd,&set);
    FD_SET(0,&set);
    max_fds = listen_fd;


    while(1){
        bak_set = set;
        ret = select(max_fds+1,&bak_set,NULL,NULL,NULL);
        if(ret < 0){
            perror("select");
            break;
        }

        for(int i = 0;i<=max_fds;i++){
            if(FD_ISSET(i,&bak_set)){
                if(i == 0){
                    // 标准输入可读 fgets
                    continue;
                }
                else if(i == listen_fd){
                    // 监听描述符可读  accept
                    memset(&new,0, sizeof(new));
                    new = listen_hander(i);
                    if(new.fd<0||new.fd==0){
                        fprintf(stderr, "listen handler error!\n");
                        continue;
                    }
                    FD_SET(new.fd, &set);
                    max_fds = new.fd > max_fds ? new.fd : max_fds;
                }
                else{
                    // 新的连接描述符可读  recv
                    ret = data_hander(i);
                    if (ret <= 0) {
                        // 收尾处理
                        close(i);
                        FD_CLR(i, &set);
                    }
                }
            }
        }
    }
}

int main(){
    int listen_fd;
    listen_fd = init_tcp_server(PORT);
    if (listen_fd < 0) {
        fprintf(stderr, "init tcp server failed!\n");
        return -1;
    }
    printf("listening...\n");

    main_loop(listen_fd);
    close(listen_fd);
    return 0;
}

2、客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>

static  short PORT = 2233;
static  char *HOST = "192.168.200.134";

/*打包要发送的信息*/
struct send_info{
    char msg[1024];
    char c_name[64];//客户名字
    char s_name[64];//私聊客户名字

};

static struct send_info msg;
static  char cname[64];
/*初始化*/
int inti_tcp_client(const char *host,unsigned short  port){
    int scoket_server;
    int ret;
    struct sockaddr_in dest;

    scoket_server = socket(AF_INET, SOCK_STREAM, 0);
    if(scoket_server < 0){
        perror("socket");
        return -1;
    }

    memset(&dest,0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    dest.sin_addr.s_addr = inet_addr(host);

    ret = connect(scoket_server, (struct sockaddr *)&dest, sizeof(dest));
    if(ret < 0){
        perror("connect");
        return -1;
    }
    return scoket_server;
}

/*记录用户名称,并接收服务器在线的用户*/
int set_name(int scoket_server) {
    char buf[1024];
    long ret;

    // 保存登陆名并发给服务器
    memset(&msg,0, sizeof(msg));
    strcat(msg.c_name,cname);


    ret = send(scoket_server,&msg, sizeof(msg),0);
    if(ret<0){
        perror("send");
        return -1;
    }
    //打印连接信息
    memset(&buf,0, sizeof(buf));
    ret = recv(scoket_server, &buf, sizeof(buf)-1, 0);
    //buf[strlen(buf)] = 0;
    if (ret > 0) {
        if(strncmp(buf,"repetitive name\n",15)==0){
            printf("名称重复!\n");
            exit(0);
        }
        else {
            printf("成功连接到主机:  %d!\t", PORT);
            printf("%s\n", buf); //打印在线的用户
        }

    }
    return 0;
}

/*接收服务器的消息*/
void recv_hander(int client_fd){
    char buf[1024];
    long ret;
    memset(buf,0, sizeof(buf));

    while (1){
        ret = recv(client_fd, &buf, sizeof(buf)-1, 0);
        if (ret < 0) {
            perror("recv hander");
            break;
        }
        else if(ret == 0){
            printf("服务器崩了!!!!\n");
            break;
        }
        if(strncmp(buf,"404",3)==0){
            printf("\033[31m服务器\033[0m: 找不到该用户!\n");
        }
        else{
            printf("%s\n", buf);
        }
        memset(buf,0, sizeof(buf));
    }
}

/*打包消息并发给服务器*/
void send_hander(int client_fd){
    int type;
    long ret;

    while(1){
        usleep(1000);
        printf("请输入聊天方式(1 群聊,0私聊):\n");
        scanf("%d",&type);
        fgets(msg.msg, sizeof(msg.msg), stdin);//清除stdin的内容
        if(type){
            printf("请输入聊天信息:\n");
            memset(&msg,0, sizeof(msg));
            fgets(msg.msg, sizeof(msg.msg), stdin);//要发送的信息
            msg.msg[strlen(msg.msg)-1] = 0;
            strcat(msg.s_name,"all");//要发送的对象
            strcat(msg.c_name,cname);//用户姓名
        }
        else{
            printf("请输入接收方姓名:\n");
            memset(&msg,0, sizeof(msg));
            fgets(msg.s_name, sizeof(msg.s_name), stdin);
            msg.s_name[strlen(msg.s_name)-1] = 0;
            printf("请输入聊天信息:\n");
            fgets(msg.msg, sizeof(msg.msg), stdin);
            msg.msg[strlen(msg.msg)-1] = 0;
            strcat(msg.c_name,cname);

        }
        ret = send(client_fd,&msg, sizeof(msg),0);
        if(ret<0){
            perror("send");
            break;
        }
    }
}

int main(){

    int client_socket;
    //记录登陆名
    printf("请输入你的名字:\t");
    fgets(cname, sizeof(cname), stdin);
    cname[strlen(cname)-1]=0;

    client_socket = inti_tcp_client(HOST,PORT);
    if (client_socket < 0) {
        fprintf(stderr, "init tcp client failed!\n");
        return -1;
    }
    set_name(client_socket);

    pid_t pid;
    pid = fork();

    if (pid < 0) {
        perror("fork");
    } else if (pid == 0) {
        recv_hander(client_socket);
        close(client_socket);
        exit(0);
    } else {
        send_hander(client_socket);
        close(client_socket);
        kill(pid,9); //关闭接收子进程,准备退出
        wait(NULL);
    }
    return  0;
}

3、测试截图

在这里插入图片描述

之前做的,没有优化,写的有点混乱,但基本实现了群聊和私聊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值