由于使用了多线程操作,客户端进入程序后请先随便注册一次用户后再进行使用。
本程序默认第一个用户即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
#include
#include
}
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
涉及到用户权限及密码验证问题时加入如下语句:
#include
#include
#include
需要注意的是,应该在编译时链接加密算法库,即增加编译选项:
-lcrypt
涉及到文件及时间操作加入如下语句:
#include
#include
涉及到多进程操作时加入如下语句:
#include
#include
涉及到多线程操作时加入如下语句:
#include
需要注意的是,应该在编译时链接线程库,即增加编译选项:
-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=
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=
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架构的即时聊天系统,实现聊天的各种基本功能。