由于使用了多线程操作,客户端进入程序后请先随便注册一次用户后再进行使用。

本程序默认第一个用户即ID为1的用户为超级管理员。

由于线程阻塞,最后的踢人操作有阻塞,需要在被踢出在线链表后手动下线。

看了老师给的颜色控制命令之后,再也不想print白色的字了。。。


聊天室项目编写时遇到的问题:

首先一定要备份,完成一步备份一下!

1.最开始卡住是那天的strncpy,查错查了一下午加一个晚上,最后鹏哥帮着一个个print才发现是这个函数的问题。能用strcpy的地方一定不要用strncpy!
2.然后就是返回值来决定菜单的显示,这个问题困扰了一天半,然后把程序拷贝到redhat上就可以跑了???回到Ubuntu一个个print,线程跑的比较慢,所以read的值能不能传过来,要看主进程跑的比线程慢还是快了,最后一个sleep解决了一切卡了一天半的问题。没事就要sleep!
3.然后链表卡住了,这块知识掌握的的不够牢固,最后一天在宿舍从起床到吃完饭一直在看链表,在梅总的指导下总算是对链表有了个清晰的认识,链表卡了一天!
4.传参的细节问题,*星号,  取地址符一定要会用。传参到底是形参还是实参一定要搞懂!
5.最后虽然完成了程序但是由于框架是用if else搭出来的,显得比switch case框架笨重很多,而且最后的管理员踢人功能也没法完美的实现跳出菜单。做东西前,一定要先有规划,规划好框架再写,要么最后改都改不了!
 

一、基本内容和要求

基本内容:

本课程设计主要实现一个Linux下的局域网聊天工具的设计。该设计主要分为两部分,客户端部分和主机部分。运行服务器端程序可以和任意运行了客户端程序的主机进行通信,通信内容能够通过终端显示出来。两个部分都使用C语言,利用vim编辑器,通过Berkeley套接口编程实现相关功能。

基本要求:

设计一个可以运行在Linux平台下C/S架构的即时聊天系统,实现聊天的各种基本功能。

二、项目配置

A.  功能:能够正确注册,登录,退出;

          能够查询、添加、删除好友;

          能够查看好友资料信息和状态信息;

          能够实现个人资料信息维护、修改、更新个人状态信息;

          能能在显示好友列表时显示好友状态;

          能够实现正常地发送接收消息

          能够查看聊天记录.

          能够实现管理员功能;

          能够实现文件传输;

B.  性能: 准确即时发送数据到指定用户;

          能承载一定用户数量压力的服务器;

C.  输出:注册信息,存储到数据库中;

          个人信息表,存到相应的个人用户下;

          个人状态表,存储个人ID、是否在线等;

          好友列表,查看好友的信息、状态、ID等;

D.  输入:输入ID登录,查找ID、添加好友ID;

          修改个人信息、个人状态;

          发送聊天信息,查看聊天记录;

E.  安全方面:IP与ID一一对应,ID与密码匹配登录

F.  支持系统:Linux;

 

三、Linux-C相关知识

 

3.1 socket编程头文件和常用函数

【IP地址转换函数】

点分十进制   整数  inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。算是比较新的函数了。

{

   #include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>      // arpa/inet.h:提供IP地址转换函数

}

sys/types.h:数据类型定义

sys/socket.h:提供socket函数及数据结构

netinet/in.h:定义数据结构sockaddr_in

arpa/inet.h:提供IP地址转换函数

netdb.h:提供设置及获取域名的函数

sys/ioctl.h:提供对I/O控制的函数

sys/poll.h:提供socket等待测试机制的函数

 

其他在网络程序中常见的头文件

unistd.h:提供通用的文件、目录、程序及进程操作的函数

errno.h:提供错误号errno的定义,用于错误处理

fcntl.h:提供对文件控制的函数

time.h:提供有关时间的函数

crypt.h:提供使用DES加密算法的加密函数

pwd.h:提供对/etc/passwd文件访问的函数

shadow.h:提供对/etc/shadow文件访问的函数

pthread.h:提供多线程操作的函数

signal.h:提供对信号操作的函数

sys/wait.h、sys/ipc.h、sys/shm.h:提供进程等待、进程间通讯(IPC)及共享内存的函数

建议:在编写网络程序时,可以直接使用下面头文件代码

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netdb.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

#include <errno.h>

#include <malloc.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <sys/ioctl.h>

#include <stdarg.h>

#include <fcntl.h>

#include <fcntl.h>

涉及到用户权限及密码验证问题时加入如下语句:

#include <shadow.h>

#include <crypt.h>

#include <pwd.h>

需要注意的是,应该在编译时链接加密算法库,即增加编译选项:

-lcrypt

涉及到文件及时间操作加入如下语句:

#include <sys/time.h> #include <utime.h> #include <time.h>

#include <sys/stat.h> #include <sys/file.h>

涉及到多进程操作时加入如下语句:

#include <sys/wait.h> #include <sys/ipc.h>

#include <sys/shm.h> #include <signal.h>

涉及到多线程操作时加入如下语句:

#include <pthread.h> #include <sys/poll.h>

需要注意的是,应该在编译时链接线程库,即增加编译选项:

-lthread

绑定端口

int bind(int sockfd, struct sockaddr *sa, int addrlen);

连接网络(TCP)

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

监听端口(TCP)

int listen(int sockfd, int queue_length);

响应连接请求(TCP)

int accept(int sockfd, struct sockaddr *addr, int *addrlen);

关闭

int close(int sockfd);

int shutdown(int sockfd, int how);

0—-禁接收

1—禁发送

2—禁收发

轮询

int select(int numfds, fd_set *readfds, fd_set * writefds,  fd_set * exceptfds, struct timeval* timeout);

注意windows和unix中,函数返回后fd_set内容发生了改变,下次使用必须重新赋值。

接收和发送:

TCP: int send(int s, const void* buf, int len, int flags);

int recv(….);

UDP: int sendto(int s, const void* buf, int len, int flags, const struct sockaddr* to, int tolen);

int recvfrom(…);

基于消息的方式:

int sendmsg(int s, const struct msghdr * msg, int flags);

int recvmsg(…);

 

3.2 Linux文件编程

1.Linux系统调用

操作系统负责管理和分配所有的计算机资源,为了更好的服务于应用程序,操作系统提供了一组特殊接口------系统调用。

通过这组接口用户程序可以使用操作系统内核提供的各种功能,主要包括:进程控制、进程通信、文件系统管理、内存管理、I/O管理、网络管理、套接字等

问题:为什么不允许程序直接访问计算机资源???

2.用户编程接口API

用户编程接口API俗称库函数,为了提高用户的编程效率,开发环境会把实现特定功能的函数封装成函数库供用户调用。

系统调用/用户编程接口API

一个是系统级别,一个是用户级别

系统调用提供的数量和功能有限,函数库非常丰富

系统调用属于操作系统内核功能,移植性受限,用户编程接口API与内核无关,只要计算机上安装了相应函数库,就可以调用,移植性好。

3.POSIX规范

Portable Operation System Interface

可移植的操作系统接口规范,由IEEE制定,目的是为了提高UNIX环境下的应用程序可移植性。

Linux类UNIX操作系统

4.Linux文件I/O

Linux下一切皆文件:

6类:普通文件、目录文件、符号链接文件、管道文件、套接字文件、设备文件

l文件描述符:

对于内核而言,所有打开文件都由文件描述符表示。

文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。

当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。

5.文件标准I/O

ANSI C定义的用于I/O操作的一系列API函数

文件指针

FILE:每个被打开的文件都在内存中开辟一个区域,用来存放文件的有关信息。这些信息保存在一个结构体类型的变量中,该结构体类型是由系统定义的,称为FILE(流)。

标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用FILE 来描述。

流的含义:

定义:所有的I/O操作本质上都是从文件中输入或输出字节流,所以称为流。

分类:文本流/二进制流。

文本流

定义:在流中处理的数据是以字符出现。在文本流中,’\n’被转换成回车                 符CR和换行符LF的ASCII码0DH和0AH。而当输出时,0DH和0AH                   被转换成’\n’

数字2001在文本流中的表示方法为  ‘2’  ‘0’  ‘0’  ‘1’                                                   ASCII:    50  48  48  49

ASCII:   32  30   30  31

ASCII    0010 0000  0001 1110  0001 1110  0001 1111

二进制流

定义:流中处理的是二进制序列。若流中有字符,则用一个字节的二进制                 ASCII码表示;若是数字,则用对应的二进制数表示。对’\n’不进行                 变换

数字2001在二进制流中的表示方法为 00000111  11010001。

缓冲:

目的:尽量减少使用read/write的调用

定义:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区在一起送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区逐个的将数据送到程序的数据区。

全缓冲

当填满I/O缓存后才进行实际I/O操作,或者满足一定条件后,系统通过调用malloc来获得所需要的缓冲区域,默认值全缓冲。

刷新(fflush):标准I/O的写操作。

当缓冲区满了,或者满足一定的条件后,就会执行刷新操作。

行缓冲

当在输入和输出中遇到新行符(‘\n’)时,进行I/O操作。

当流遇到一个终端时,典型的行缓存。

无缓冲

标准错误流stderr无缓冲。

很多的人机交互界面要求不可全缓冲。

标准I/O预定义3个流,他们可以自动地为进程所使用

标准输入

0

STDIN_FILENO

stdin

标准输出

1

STDOUT_FILENO

stdout

标准错误输出

2

STDERR_FILENO

stderr

 

 

6.文件I/O与标准I/O的区别

l文件I/O又称为低级磁盘I/O,遵循POSIX标准,在支持POSIX标准的系统上都能运行。

l标准I/O称为高级磁盘I/O,遵循ANSI C标准,只要系统中有标准C函数库,就可以运行。

l文件I/O执行读写操作时,会执行系统调用,直接读写磁盘文件。缺点:频繁执行系统调用增加了系统开销,因为频繁在用户态与内核态进行切换。

l标准I/O是在文件I/O的基础上封装了缓冲机制,所有文件操作实际上是在缓冲区里进行的,必要时才实际读写磁盘文件。缺点:如果程序异常退出,缓冲区里的内容有可能没有写入磁盘文件,信息丢失。

l文件I/O可以操作任何类型的文件

l标准I/O只能读写普通文件

7.文件I/O操作

l不带缓冲

l通过文件描述符来访问文件

文件I/O常用函数

open()/creat() close() read() write() lseek()

8.标准I/O操作

fopen() fclose()

fgetc()/fputc()一次读或写一个字符。

fgets()和fputs()一次读或写一行。

fread()和fwrite()函数支持直接I/O

fseek() ftell() rewind() Fflush()

 

3.3 线性表的顺序存储结构

1.线性表的顺序存储结构

typedef int datatype;

typedef struct Sqlist {

datatype List[MAX];

int length;

}Sqlist;

Sqlist L;

注意:初始化length的赋值含义;插入、删除操作的位置判断、溢出判断;插入或者删除后数据元素向后或者向前移动范围判断;

2.线性表的链式存储结构

typedef int ElemType;

typedef struct Node

 {

     ElemType data;

     struct Node *next;

 }Node;

typedef struct Node * LinkList;

LinkList L;

函数原型:Initiate(LinkList *L)

函数调用:Initiate(&L);

*L=(LinkList)malloc(sizeof(Node));

注意:头节点概念(类型也是Node,不保存数据);

      头指针概念(头节点的next指针的值,保存的第一个数据节点的地址);

      插入、删除时的判断条件,注意判断条件的差异(插入p!=NULL   /   删除p->next!+NULL)

 插入时,p指向要插入的位置i的前一个节点(1=<i<=Listlength()+1)

 p = *L; j = 1;   

  while (p!=NULL && j < i)     /* 寻找第i个结点 */

     {

           p = p->next;

           ++j;

     }    

 if (p==NULL || j > i)

        return ERROR;   /* 第i个元素不存在 */

                

 s = (LinkList)malloc(sizeof(Node));  /* 生成新结点 */  

        s->data = e;        

        s->next = p->next;      /* 将p的后继结点赋值给s的后继  */

        p->next = s;          /* 将s赋值给p的后继 */

 

 删除时,p指向要删除的位置i的前一个节点(1=<i<=Listlength())

 p = *L; j = 1;

   while (p->next!=NULL && j < i)  /* 遍历寻找第i个元素 */

        {

        p = p->next;

        ++j;

        }      

   if ((p->next)==NULL || j > i)

            return ERROR;           /* 第i个元素不存在 */   

        q = p->next;

        p->next = q->next;  /* 将q的后继赋值给p的后继 */

        free(q);

 

 

3.4多线程知识

线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux。

进程:进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。

也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用libpthread.a。

四、源程序注解

见程序:https://download.csdn.net/download/qq_39540224/10548861

五、结果分析

运行环境ubuntu12.04

详见:Linux环境下——C语言聊天室项目界面展示

https://blog.csdn.net/qq_39540224/article/details/81098507

六、结论

本课程设计主要实现一个Linux下的局域网聊天工具的设计。该设计主要分为两部分,客户端部分和主机部分。运行服务器端程序可以和任意运行了客户端程序的主机进行通信,通信内容能够通过终端显示出来。两个部分都使用C语言,利用vim编辑器,通过Berkeley套接口编程实现相关功能。完成了一个可以运行在Linux平台下C/S架构的即时聊天系统,实现聊天的各种基本功能。

 

 

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐