基于TCP/IP协议的网络多人聊天室

项目背景

        基于 TCP/IP 的网络多人聊天室是一种旨在提供实时的文字交流平台,使多个用户能够在网络上进行即时聊天和互动的应用程序。它通过利用 TCP/IP 协议栈中的传输控制协议(TCP)和 Internet 协议(IP)来实现消息的传输和网络通信。

该网络多人聊天室是基于传输层TCP协议由服务端模块与客户端模块组成。服务器端负责接收和转发消息,而客户端负责与服务器建立连接并发送/接收消息。TCP协议是一种一对一连接协议,它在客户端和服务端之间建立可靠的通信连接,确保数据的可靠性和有序性。因此,客户端只能与服务端进行一对一连接通信,但可通过服务器与其他客户端完成一对多通信。

  • 服务端作为该网络聊天室的核心组件,负责接受来自客户端的连接请求,维护客户端的连接状态,并处理消息的转发。
  • 服务器端通常使用单线程或多线程的模型来处理客户端的连接和消息。每个客户端连接会被分配一个独立的线程,负责处理该客户端的消息收发。
  • 服务器端需要维护一个用户列表或用户数据库,用于管理在线用户和其相关信息,如用户名、IP 地址等。服务器端还负责处理系统通知、用户加入/退出聊天室等特殊事件的处理。

  • 客户端是用户使用的界面,允许用户输入消息并发送给服务器,同时接收其他用户发送的消息。
  • 客户端可以由线程组成,这种方式通常称为多线程编程。多线程编程是一种并发编程的方式,允许在一个程序中同时执行多个线程,每个线程都可以执行客户端要求的任务,并且多个线程可以共享一个进程的资源,可以提高系统资源利用率并节约内存空间。
  • 客户端需要与服务器建立连接,并通过套接字(Socket)与服务器进行通信。客户端通常提供用户注册、登录、选择聊天室、显示在线用户列表等功能。客户端还负责将用户输入的消息发送给服务器,并将接收到的消息显示在用户界面上。

 软件开发平台

  1. Ubuntu20.04操作系统
  2. 基于ssh网络远程连接Linux系统的VScode代码编辑器
  3. 开发语言:C语言

功能分析

服务器功能

  1. 登录界面

  2. 循环服务器。循环登录功能。
  3. 可以显示好友信息。好友名称 + 好友 ip + port
  4. 保存聊天信息 + 保存日志内容。//保存到文件中。或者是链表中。或者数据库中。
  5. 多用户登录功能。并发服务器。使用多线程技术,多进程技术。
  6. 群聊功能。转发功能。私聊功能。单独与好友聊天。
  7. 上线提醒功能。某人上线了,所有人都知道。服务器给所有人发送信息。
  8. 用户标识符功能:jack> mike: linkda= //字符串连接函数。

  9. 任务排斥,其他任务抢占现象:线程互斥锁 条件变量 信号量。进程文件锁,进程信号量。
  10. 信号功能。信号注册。signal sigaction

  11. 接收文件。//接受文件名 + 以这个名字命名一个文件 + 接受文件内容。

客户端功能

  1. 登录成功以后:欢迎信息。
  2. 信号注册功能。ctrl + c : 显示当前时间。
  3. 发送文件。
  4. 互斥锁。文件锁 信号量。
  5. 自定义功能。广告功能,QQ秀功能。群通知功能。匿名聊天功能。时间戳功能。踢人功能。敏感词屏蔽。

聊天室系统服务端、客户端模块运行流程图

  • 服务端运行流程图

  • 客户端运行流程图

客户端模块相关代码

  • 用户登录

//定义用户信息节点
typedef struct userdata{
    void *data;
    struct userdata *next;
}user_t;

//data指针 数据指向
typedef struct logdata{
    char username[16];
    char userpasswd[16];
    int user_cid;
}log_t;

log_t *login(char ip[],unsigned int port)
{
    FILE *fp = fopen("user_data.txt","a+");
    fseek(fp,0,SEEK_SET);

    log_t *logdata = (log_t *)malloc(sizeof(log_t *));
    // 1.存用户名
    printf("\tWelcome to the chat room\n\n");
    printf("username:");
    scanf("%s",logdata->username);
    fprintf(fp,"username:%s\n",logdata->username);

    // 2.存密码
    printf("password:");
    scanf("%s",logdata->userpasswd);
    fprintf(fp,"password:%s\n",logdata->userpasswd);

    // 存储用户信息 IP + Port    
    fprintf(fp,"IP:%s\n",ip);
    fprintf(fp,"Port:%d\n",port);
    
    // 关闭文件
    fclose(fp);
    
    return logdata;
}

void get_time(FILE * fp)
{
    time_t currentTime = time(NULL);
    struct tm *localTime = localtime(&currentTime);
    
    fprintf(fp,"time: %d-%02d-%02d %02d:%02d:%02d\n\n",
           localTime->tm_year + 1900,
           localTime->tm_mon + 1,
           localTime->tm_mday,
           localTime->tm_hour,
           localTime->tm_min,
           localTime->tm_sec);
}

void sigfun(int signo)
{
    // ctrl + c
    if(signo == SIGINT)
    {
        time_t currentTime = time(NULL);
        struct tm *localTime = localtime(&currentTime);
        printf("time: %d-%02d-%02d %02d:%02d:%02d\n\n",
           localTime->tm_year + 1900,
           localTime->tm_mon + 1,
           localTime->tm_mday,
           localTime->tm_hour,
           localTime->tm_min,
           localTime->tm_sec);
    }
}
  • 具体实现

  • #include "cJSON.h"
    
    int file_pass_flag = 0;
    
    void file_pass(int signo)
    {
        // ctrl + z
        if(signo == SIGTSTP)
        {
            // printf("file_pass_flag = %d\n",file_pass_flag);
            // file_pass_flag = 21;
            // printf("file_pass_flag = %d\n",file_pass_flag);
            system("sl");
        }
    }
    
    int main(int argc, char const *argv[])
    {
        if(argc < 5)
        {
            perror("usage:./client + s_IP + Port + c_IP + Port\n");
            return -1;
        }
        
        // !-------------select准备部分------------------
        // 输入文件描述符集合
        fd_set rset = {0};
        // 设定超时时间
        struct timeval tm = {0};
        // 有数据变化的文件描述符个数
        int count  = 0;
        char bufh[128] = "";
        // !---------------------------------------------
    
        // 已经登录
        char tcname[16] = "";
        // strcpy(tcname,login((char *)argv[1],atoi(argv[2])));
        
        log_t *users_data = login((char *)argv[1],atoi(argv[2]));
        if (strcmp(users_data->username,""))
        {
            printf("\nusersname:%s---欢迎\n",users_data->username);
            signal(SIGINT,sigfun);
        }   
        
        strcpy(tcname,users_data->username);
        //建立客户端套接字
        int cid = socket(AF_INET,SOCK_STREAM,0);
        printf("cid = %d\n",cid);
    
        //connect
        struct sockaddr_in caddr = {0};
        caddr.sin_family = AF_INET;
        caddr.sin_port = htons(atoi(argv[2]));
        caddr.sin_addr.s_addr = inet_addr(argv[1]);
        if(connect(cid,(struct sockaddr*)&caddr,sizeof(caddr)) < 0 )
        {
            perror("connect error\n");
            return -3;
        }
        printf("connect successful\n");
        // !看看是否成功接收到客户端登录name
        printf("---%s---\n",tcname);
        
        // !在这里初始化一个接收服务器信息的地址
        char rbuf[512] = "";
        char bufv[128] = "";
        struct sockaddr_in rAddr = {0};
        rAddr.sin_family = AF_INET;
        rAddr.sin_addr.s_addr = inet_addr(argv[3]);
        rAddr.sin_port = htons(atoi(argv[4]));
    
        // 接收条件
        cJSON *json_res = NULL;
        cJSON *json_name = NULL;
        cJSON *json_code = NULL;
        cJSON *json_info = NULL;
        cJSON *json_sign = NULL;
        cJSON *fuhao = NULL;
        cJSON *json_ip = NULL;
        cJSON *json_port = NULL;
        // !私聊对象
        cJSON *json_chat_name = NULL;
    
        // 先发送自己的客户端用户名给服务器
        send(cid,(char *)tcname,sizeof(tcname),0);
        
        char bufs[128] = "";
        int len = 0 ;
        
        //------------------json的数据封装。------------------------------//
        cJSON * json_pointer = NULL;
        //创建一个链表数据对象。
        json_pointer = cJSON_CreateObject();
        //1添加字符串类型到节点当中 姓名
        char cname[16] = "";
        
        cJSON_AddStringToObject(json_pointer,"name",tcname);
        //2 添加整型数据 密码
        char cpasswd[16] = "1221";
        // 匹配密码
        FILE *cfp  = fopen("user_data.txt","r+");
        // fscanf(cfp,"password:%s\n",cpasswd);
        
        cJSON_AddStringToObject(json_pointer,"code",cpasswd);
        //3 添加字符串类型到节点当中 信息
        //cJSON_AddStringToObject(json_pointer,"info","hello linux");
        //4 添加字符串类型到节点当中 签名
        cJSON_AddStringToObject(json_pointer,"sign","Fear of violence");
        //添加字符串类型到节点当中 
        cJSON_AddStringToObject(json_pointer,"fuhao",">");
        // 添加IP字符串到节点中
        char cIP[16] = "";
        strcpy(cIP,argv[3]);
        printf("cIP = %s\n",cIP);
        cJSON_AddStringToObject(json_pointer,"IP",cIP);
        // 添加端口号到节点中
        unsigned int cPort = htons(atoi(argv[4]));
        cJSON_AddNumberToObject(json_pointer,"Port",cPort);
        
        //数据整理
        char * str = NULL;
        char chat_name[16] = "";
    
        //循环读写
        while(1)
        {
            bzero(chat_name,sizeof(chat_name));
            bzero(bufs,sizeof(bufs));
            bzero(bufv,sizeof(bufv));
    
            // !select循环读写设置部分
            FD_SET(cid,&rset);
            FD_SET(STDIN_FILENO,&rset);
            tm.tv_sec = 2;
            count = select(cid+1,&rset,NULL,NULL,&tm);
    
            signal(SIGTSTP,file_pass);
            // 键盘有动作,说明客户端要发数据
            if (FD_ISSET(STDIN_FILENO, &rset))  
            {
                // 选择聊天对象
                read(STDIN_FILENO, chat_name, sizeof(chat_name) - 1);
                if (file_pass_flag == 0)
                {
                    // 从键盘读数据再写给对面
                    read(STDIN_FILENO, bufs, sizeof(bufs) - 1);
    
                    // 增加私聊功能
                    printf("chat:%s",chat_name);
                    cJSON_AddStringToObject(json_pointer, "chat_name", chat_name);
                    // 聊天信息部分
                    cJSON_AddStringToObject(json_pointer, "info", bufs);
    
                    // jason格式转换成字符串格式
                    str = cJSON_Print(json_pointer);
                    FILE * fp = fopen("chat_logs.txt","a+");
                    fprintf(fp,"%s-->%sinfo:%s",tcname,chat_name,bufs);
                    get_time(fp);
                    send(cid, str, strlen(str), 0);
                    // 发送过去之后,进行删除节点。
                    if (!strncmp(bufs, "quit", 4))
                    {
                        cJSON_DeleteItemFromObject(json_pointer, "info");
                        break;
                    }
                    cJSON_DeleteItemFromObject(json_pointer, "info");
                    cJSON_DeleteItemFromObject(json_pointer,"chat_name");
                    printf("提示:客户端发送信息时,先输入聊天对象,再输入聊天内容\n");
                    fclose(fp);    
                }
                if (file_pass_flag == 21)
                {
                    char filename[16] = "";
                    printf("输入一个文件名:");
                    scanf("%s",filename);
                    strncpy(filename,filename,strlen(filename)-1);
                    FILE * fp = fopen(filename,"w+");
                    printf("请输入文件内容:");
                    // 清空键盘缓存区域
                    fflush(stdin);
                    // fputs();
                }
                signal(SIGTSTP,SIG_DFL);            
            }
    
            if (FD_ISSET(cid,&rset))
            {
                printf("服务器正在发送信息~\n");
                bzero(rbuf, sizeof(rbuf));
                // 接收服务器发出数据
                len = recv(cid, rbuf, sizeof(rbuf), 0);
    
                // 解析rbuf
                json_res = cJSON_Parse(rbuf);
                // 姓名
                json_name = cJSON_GetObjectItem(json_res, "name");
                // 密码
                json_code = cJSON_GetObjectItem(json_res, "code");
                // 信息
                json_info = cJSON_GetObjectItem(json_res, "info");
                // 签名
                json_sign = cJSON_GetObjectItem(json_res, "sign");
                // 符号
                fuhao = cJSON_GetObjectItem(json_res, "fuhao");
                // IP
                json_ip = cJSON_GetObjectItem(json_res, "IP");
                // Port
                json_port = cJSON_GetObjectItem(json_res, "Port");
                // !聊天对象
                json_chat_name = cJSON_GetObjectItem(json_res, "chat_name");
    
                printf("来自:%s的信息_:%s",json_name->valuestring,json_info->valuestring);
                printf("%s %s的个性签名:%s\n",fuhao->valuestring,json_name->valuestring,json_sign->valuestring);
                printf("IP:%s\tPort:%d\n",json_ip->valuestring,json_port->valueint);    
            }
            
        }
        shutdown(cid,SHUT_RDWR);
        fclose(cfp);
    
        return 0;
    }
    

     服务端模块相关代码

  • 功能实现

#include "cJSON.h"

//静态 cid ; 
char bufs[512] = "";
char bufv[128] = "";
int len = 0 ;

// 建立链表,在服务器中建立链表,方便使用
user_t *head = NULL;

void * pthread_fun(void * arg)
{
    //cid是从主线程传过来的。   
    int pcid = *(int *)arg;

    // !-------------select准备部分------------------
    // 输入文件描述符集合
    fd_set rset = {0};
    // 设定超时时间
    struct timeval tm = {0};
    // 有数据变化的文件描述符个数
    int count  = 0;
    char bufh[128] = "";
    // !---------------------------------------------

    // 已经在主线程中创建链表了并使用头插法插入新的客户端链表
    // !------------为了通过name转发而做准备,准备一个单链表--------------
    user_t *temp = head->next;
    char sname[16] = "";
    bzero(sname,sizeof(sname));
    recv(pcid,(char *)sname,sizeof(sname),0);
    while(temp!=NULL)
    {
        if (((log_t *)(temp->data))->user_cid == pcid)
        {
            memcpy(((char *)(((log_t *)(temp->data))->username)),sname,16);
            printf("%s is online\n",(char *)(((log_t *)(temp->data))->username));
            break;
        }
        temp = temp->next;
    }
    // !-----------------------------------------------------------------

    //清空bufs
    bzero(bufs,sizeof(bufs));
    cJSON * json_res = NULL;
    cJSON * json_name = NULL;
    cJSON * json_code = NULL;
    cJSON * json_info = NULL;
    cJSON * json_sign = NULL;
    cJSON * fuhao = NULL;
    cJSON * json_ip = NULL;
    cJSON * json_port = NULL;
    // !私聊对象
    cJSON * json_chat_name = NULL;

    while(1)
    {
        // !select循环读写设置部分
        FD_SET(pcid,&rset);
        FD_SET(STDIN_FILENO,&rset);
        tm.tv_sec = 10;
        count = select(pcid+1,&rset,NULL,NULL,&tm);

        //cid 要传递过来
        bzero(bufs,sizeof(bufs));
        // !接收
        len = recv(pcid,bufs,sizeof(bufs),0);
        //解析bufs
        json_res = cJSON_Parse(bufs);
        // 姓名
        json_name = cJSON_GetObjectItem(json_res,"name");
        // 密码
        json_code = cJSON_GetObjectItem(json_res,"code");
        // 信息
        json_info = cJSON_GetObjectItem(json_res,"info");
        // 签名
        json_sign = cJSON_GetObjectItem(json_res,"sign");
        // 符号
        fuhao = cJSON_GetObjectItem(json_res,"fuhao");
        // IP
        json_ip = cJSON_GetObjectItem(json_res,"IP");
        // Port
        json_port = cJSON_GetObjectItem(json_res,"Port");
        // !聊天对象
        json_chat_name = cJSON_GetObjectItem(json_res,"chat_name");     

        temp = head->next;
        int send_num = 1;
        int send_jud = 0;
        while (temp!=NULL)
        {
            // 判断是否私发给服务器
            if (!strncmp(json_chat_name->valuestring,"server",6)&&send_num--)
            {
                printf("来自:%s的信息_:%s",json_name->valuestring,json_info->valuestring);
                printf("%s %s的个性签名:%s\n",fuhao->valuestring,json_name->valuestring,json_sign->valuestring);
                printf("IP:%s\tPort:%d\n",json_ip->valuestring,json_port->valueint);
                printf("chat_name:%s",json_chat_name->valuestring);
                send_num = 0;
                send_jud = 1;
            }
            // 通过服务器转发给其他用户 --- 比较name
            if (!strncmp(json_chat_name->valuestring,((log_t *)temp->data)->username,strlen(json_chat_name->valuestring)-1))
            {
                // 经过测试,确实存在转发,并且转发的字节数是对的
                send_jud = send(((log_t *)temp->data)->user_cid,bufs,strlen(bufs),0);
            }

            // 群发
            if (!strncmp(json_chat_name->valuestring,"all",3))
            {
                user_t *temp_all = head->next;
                while(temp_all!=NULL)
                {
                    if (strncmp(((log_t *)(temp_all->data))->username,json_name->valuestring,strlen(json_name->valuestring)))
                    {
                        send_jud = send(((log_t *)temp->data)->user_cid,bufs,strlen(bufs),0);    
                    }                   
                    temp_all = temp_all->next;
                }
                if (send_num)
                {
                    // 服务器显示的部分
                    printf("来自:%s的信息_:%s",json_name->valuestring,json_info->valuestring);
                    printf("%s %s的个性签名:%s\n",fuhao->valuestring,json_name->valuestring,json_sign->valuestring);
                    printf("IP:%s\tPort:%d\n",json_ip->valuestring,json_port->valueint); 
                    send_num = 0;   
                }                
            }
            temp = temp->next;
        }
        if(!send_jud)
        {
            printf("不存在该用户:%s",json_chat_name->valuestring);
        }
        send_jud = 0;
        if (!strncmp(json_info->valuestring,"quit",4))
        {
            printf("来自IP:%s的客户端正在退出~\n",json_ip->valuestring);
            break;
        }        
        temp = head->next;
    }
    //线程退出 - cid关闭是不是一件事?
    //shutdown(pcid,SHUT_RDWR);
    close(pcid);
    // ! pcid = -1;
    delete_vlinklist(head,pcid); 

    printf("来自IP:%s的客户端退出完毕~\n",json_ip->valuestring);
    //线程退出
    pthread_exit((void *)0);
}

//多用户登录 - 并发服务器 - 线程方法
int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        perror("usage:./server + ip + port\n");
        return -1;
    }
    head = create_linklist();

    //.建立套接字
    int sid = socket(AF_INET,SOCK_STREAM,0);
    printf("sid = %d\n",sid);

    //实际地址结构体
    struct sockaddr_in saddr = {0};
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);    
    saddr.sin_port = htons(atoi(argv[2]));
    if(bind(sid,(struct sockaddr*)&saddr,sizeof(saddr))<0)
    {
        perror("bind error\n");
        return -2;
    }
    printf("bind successful\n");

    //3.监听
    listen(sid,10);

    //4.循环连接
    char bufs[128] = "";
    int len = 0 ;
    pthread_t tid = 0 ;
    //只要初始化一次,在全局数据区
    static int cid = -1;
    while (1)
    {   
        //主线程。  链接。
        cid = accept(sid,NULL,NULL);
        if(cid != -1)
        {   
            // !这段说明与建立客户端连接成功
            log_t *log_data = (log_t*)malloc(sizeof(log_t));
            log_data->user_cid = cid;
            head = insert_hlinklist(head,log_data);

            //把线程设定成分离属性 - 线程执行完毕以后,自动释放占用的空间。
            pthread_create(&tid,NULL,pthread_fun,&cid);
            pthread_detach(tid);
            printf("有新用户登录 cid = %d\n",cid);
        }
        
    }
    // 销毁链表
    destroy_linklist(&head);

    close(sid);

    return 0;
}
  • 服务端应用数据结构
    //1.创建一个单链表
    user_t * create_linklist()
    {
    	user_t * head =(user_t *)malloc(sizeof(user_t));
    	if(head ==NULL)
    	{
    		perror("create error\n");
    		return NULL;
    	}
        head->data = NULL;
    	head->next = NULL;
        return head;	
    }
    
    //2.头插法插入结点
    user_t * insert_hlinklist(user_t * head, log_t * log_data)
    {
    	//数据判断
    	if(log_data == NULL||head==NULL)
    	{
    		perror("parameter error !\n");
    		return (user_t *)-1;
    	}
    	//申请空间
    	user_t * newnode = (user_t *)malloc(sizeof(user_t));
    	if(newnode==NULL)
    	{
    		perror("newnode create error!\n");
    		return (user_t *)-2;
    	}
    
    	newnode->data = (log_t *)malloc(sizeof(log_t));
    	memcpy(((log_t *)(newnode->data)),log_data,sizeof(log_t));    
    
    	newnode->next = head->next;
        head->next = newnode;
    
    	return head;
    }
    
    //根据部分信息输出全部信息
    typedef int (*cmpfun_t)(void *data1,void *data2);
    //1.比对函数
    int cmpname(void *data1,void *data2)
    {
    	log_t* newdata1 = (log_t*)data1;
    	log_t* newdata2 = (log_t*)data2;
    	return strcmp(newdata1->username,newdata2->username);
    }
    
    user_t * Search_linklist(user_t * head,void *value,cmpfun_t cmpfun)
    {
    	//1.参数判断
    	if(head == NULL || head->next == NULL || value == NULL || cmpfun == NULL)
    	{
    		perror("ERROR!\n");
    		return (user_t*)-1;
    	}
    	//2.
    	user_t * temp = head->next;
    	while (temp != NULL)
    	{
    		if (!cmpfun(temp->data,value))
    		{
    			return temp;
    		}
    		temp = temp->next;
    	}
    	return NULL;
    }
    
    // 按cid查找并且删除 节点
    user_t * delete_vlinklist(user_t*head,unsigned int cid)
    {
        //1.参数判断
        if (head == NULL || head->next==NULL)
        {
            /* 用perror也可以*/
            printf("head is null or linklist is illegal\n");
            return (user_t *)-1;
        }
        //2.遍历
        user_t*temp = head->next,*before = head;
        while (temp != NULL &&(((log_t *)(temp->data))->user_cid!=cid))
        {
            //before先移动
            before = temp;
            //temp再移动
            temp = temp->next;
        }
        //出界判断
        if (temp == NULL)
        {
            perror("this value is not in this linklist\n");
            return (user_t *)-1;
        }
        //3.修改指针
        before->next  = temp->next;
        free(temp);
        temp = NULL;
        return head;
    }
    
    //删除所有结点的数据并销毁链表
    int destroy_linklist(user_t **head)
    {
        if(*head == NULL)
        {
            perror("linklist is not exits\n");
            return -1;
        }
        //先取出元素 再取出成员
        user_t*temp =(*head)->next ,*before = NULL;  
        (*head) ->next = NULL;
        while( temp != NULL)
        {
            before = temp;
            temp = temp->next;
            free(before);
        }
        free(*head);
        *head = NULL;
        return 0 ;
    }

            在该项目的信息传输过程中应用了CJSON,用于解析JSON数据、构建JSON对象和数组,以及将JSON数据列化为字符串,使数据在网络传输后能够方便地被另一端解析和处理。

心得体会

  1.  这个项目让我有机会亲自实践多线程编程方法,并且我在这个过程中取得了一些实践成果。学会了如何在两个不同的主机之间进行通信,学习到了如何利用不同的信号功能来提高代码的可读性。
  2. 在网络编程方面,我学会了使用套接字(socket)进行网络连接,发送和接收数据。这让我对网络通信的原理有了更深入的理解,并且能够编写简单的网络应用程序来实现数据交换。
  3. 我学习到了如何使用信号来增强代码的可读性。通过合理地使用信号,我可以在代码中引入清晰的逻辑和结构。例如,我可以使用信号来处理异常情况或处理特定的事件等。
里面包含聊天室的客户端和服务器端的源文件和一份完整的设计报告。 一、 系统概要 本系统能实现基于VC++的网络聊天室系统。有单独的客户端、服务器端。 服务器应用程序能够接受来自客户端的广播,然后向客户端发送本机的IP与服务端口,让客户端接入到服务器进行聊天,检测用户名是否合法(重复),服务器责接收来自客户端的聊天信息,并根据用户的需求发送给指定的人或所有人,能够给出上线下线提示。客户端能够发出连接请求,能编辑发送信息,可以指定发给单人或所有人,能显示聊天人数,上线下线用户等。 二、 通信规范的制定 服务请求规范: 服务器端: (1) 创建一个UDP的套接字,接受来自客户端的广播请求,当请求报文内容为“REQUEST FOR IP ADDRESS AND SERVERPORT”时,接受请求,给客户端发送本服务器TCP聊天室的端口号。 (2) 创建一个主要的TCP协议的套接字负责客户端TCP连接 ,处理它的连接请求事件。 (3)在主要的TCP连接协议的套接字里面再创建TCP套接字保存到动态数组里,在主要的套接字接受请求后 ,就用这些套接字和客户端发送和接受数据。 客户端: (1) 当用户按“连接”按钮时,创建UDP协议套接字,给本地计算机发广播,广播内容为“REQUEST FOR IP ADDRESS AND SERVERPORT”。 (2)当收到服务器端的回应,收到服务器发来的端口号后,关闭UDP连接。根据服务器的IP地址和端口号重新创建TCP连接。 故我思考:客户端一定要知道服务器的一个端口,我假设它知道服务器UDP服务的端口,通过发广播给服务器的UDP服务套接字,然后等待该套接字发回服务器TCP聊天室服务的端口号,IP地址用ReceiveForom也苛刻得到。 通信规范 通信规范的制定主要跟老师给出的差不多,并做了一小点增加: (增加验证用户名是否与聊天室已有用户重复,在服务器给客户端的消息中,增加标志0) ① TCP/IP数据通信 --- “聊天”消息传输格式 客户机 - 服务器 (1)传输“用户名” STX+1+用户名+ETX (2) 悄悄话 STX+2+用户名+”,”+内容+ETX (3) 对所有人说 STX+3+内容+ETX 服务器- 客户机 (0)请求用户名与在线用户名重复 //改进 STX+0+用户名+EXT (1)首次传输在线用户名 STX+1+用户名+ETX (2)传输新到用户名 STX+2+用户名+ETX (3)传输离线用户名 STX+3+用户名+ETX (4)传输聊天数据 STX+4+内容+ETX (注:STX为CHR(2),ETX 为CHR(3)) 三、 主要模块的设计分析 四、 系统运行效果 (要求有屏幕截图) 五、 心得与体会
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值