当前位置:文档之家› Linux期末大作业

Linux期末大作业

简单的IRC聊天程序—— Linux课程期末实验第一章概述【实验目的】综合应用Linux系统下的网络编程技术,使用C语言,结合软件工程思想,设计并实现一个简单的IRC聊天程序,具有服务器端和客户端,可以是终端字符界面,支持用户管理,用户名/密码注册和登录,支持版面聊天,用户可以选择进入某个版面,版面发言大家可以看到,同时支持点对点私聊,可以选择某个用户进行私聊。

从而熟悉在Linux系统所提供的网络通信接口及该系统下的编程思想,深入对计算机系统的理解,并切实提高软件设计开发的能力。

【实验原理】一个简单的聊天室,其功能是当这个聊天室中的任何一个用户输入一段字符后,室内的其他用户都可以看到这句话。

据此,聊天程序分为客户端和服务器端。

客户端对应每一个参加聊天的用户,完成从终端上输入采集并传递到服务器端和从服务器端接收信息输出显示的功能。

总体介绍该结构如下。

首先是初始化服务器,使服务器进入监听状态:sockfd = socket(AF_INET, SOCK_STREAM, 0);// 首先建立一个socket,族为AF_INET,类型为SOCK_STREAM。

// AF_INET = ARPA Internet protocols,即使用TCP/IP协议族。

// SOCK_STREAM类型提供了顺序的,可靠的,基于字节流的全双工连接。

// 由于该协议族中只有一个协议,因此第三个参数为0。

bind(sockfd, (struct sockaddr *)&servaddr,sizeof(serv_addr));// 再将这个socket与某个地址进行绑定。

// serv_addr 包括sin_family=AF_INET 协议族同socket。

// sin_addr.s_addr = htonl(INADDR_ANY)服务器所接收的所有其他地址// 请求建立的连接。

// sin_port = htons(SERV_TCP_PORT) 服务器所监听的端口。

listen(socket, MAX_CLIENT);// 地址绑定后,服务器进入监听状态。

// MAX_CLIENT是可以同时建立连接的客户总数。

服务器进入监听状态后,等待客户建立连接。

若客户需要连接,也需要先进行网络部分的初始化工作:sockfd = socket(AF_INET, SOCK_STREAM, 0);// 与服务器端相同。

connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))// 客户使用connect建立一个连接。

// serv_addr中的变量分别设置为:// sin_family = AF_INET 协议族同socket// sin_addr.s_addr = inet_addr(SERV_HOST_ADDR)// 地址为server所在的计算机地址,本程序中为127.0.0.1// sin_port = htons(SERV_TCP_PORT)端口为服务器监听的端口。

当客户建立新连接的时候,服务器使用accept来接收该连接:accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);// 在函数返回时,cli_addr中保留的是该连接对方的信息// 包括IP地址和对方使用的端口。

// accept返回一个新的文件描述符。

在服务器进入监听状态以后,使用select方法实现,该方法中的所有描述符都是阻塞的。

使用select判断一组文件描述符中时候有一个可读(写),如果没有就阻塞。

直到有一个的时候被唤醒。

客户端实现如下:由于要处理两个文件描述符,因此需要判断是否有可读写的文件描述符需要加入两项:FD_ZERO(sockset);// 将sockset清空。

FD_SET(sockfd, sockset);// 把sockfd加入到sockset集合中。

FD_SET(0, sockset);// 把标准输入(0)加入到sockset集合中。

然后客户处理如下:while(不想退出) {select(sockfd+1, &sockset, NULL, NULL, NULL);// 此时该函数使进程阻塞,直到标准输入或者sockfd中有一个可读为止// 第一个参数是0和sockfd中的最大值加1// 第二个参数是读集合,也就是sockset// 第三、四个参数是写集,在本程序中都为空// 第五个参数为超时时间,即在指定的时间仍然没有可读,则报错并返回。

// 当该参数被设置为NULL时,超时时间为无限长。

// 当select因为可读返回时,sockset中包含的只是// 可读的那些文件描述符。

if (FD_ISSET(sockfd, &sockset)){// FD_ISSET这个宏判断sockfd是否属于可读的文件描述符/** 从sockfd中读入,输出到标准输出上去。

*/}if (FD_ISSET(0, &sockset)) {/** 从标准读入中读入,输出到sockfd上去。

*/}* 重新设置sockset。

(即:将sockset清空,并将sockfd和0加入)*/}下面是服务器的情况:设置sockset如下:FD_ZREO(sockset);FD_SET(sockfd, sockset);for (所有有效连接) {FD_SET(userfd[i], sockset);}max = 最爱文件描述符+1;服务器处理如下:while(1) {select(maxfd, &sockset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &sockset)) {// 有新连接/** 建立新连接,并将该描述符加入到sockset中*/}for (所有有效连接) {if (FD_ISSET(userfd[i], sockset)) {// 该连接中有字符可读/** 从该连接中读取字符,并发送给其他有效连接*/}}/** 重新设置sockset;}读写文件函数如下:read(userfd[i], line, MAX_LINE);// userfd[i]是指第i个用户连接的文件描述符// line是指读出的字符存放位置// MAX_LINE是一次最多读出的字符数// 返回值是实际读出的字符数write(userfd[i], line, strlen(line));// userfd[i]是指第i个用户连接的文件描述符// line是指要发送的字符串// strlen(line)是要发送的字符串长度// 返回值是实际发送的字符数【实验环境】1. 操作系统环境:Ubuntu-10.04 Linux2. 集成开发环境:Eclipse第二章分析【用例图–服务器端】【用例图–客户端】第三章设计【服务器端】1. 初始化网络接口int init_serv(int port){int SERV_TCP_PORT;int sockfd;struct sockaddr_in serv_addr;SERV_TCP_PORT = port;if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) {print ‘初始化失败’;return 0;}bzero((char *) &serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_TCP_PORT);if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){print ‘无法绑定网络地址,初始化失败’;return 0;}return sockfd; // seccess}2. 从文件中读入用户信息int get_users(User users[]){FILE *usersPtr; // 声明文件指针int i = 0; // 声明循环变量if ((usersPtr=fopen("users", "r")) == NULL)// 如果打开文件流失败{输出错误信息}else{while(!feof(usersPtr)){将文件中的数据逐次读入变量users中;i++;}fclose(usersPtr); // 关闭文件流return i+1; // 返回用户个数}}3. 处理登录事务if (strncmp(line, "%login#", 6) == 0){从line中读出username信息和passwd信息;遍历users(即从文件中读出的所有用户信息),核对用户名密码是否正确;if (正确){获取所有版块信息并组成字符串;向该客户端发送版块信息;}else{向用户发送登录失败的消息;}}4. 处理注册事务if (strncmp(line, "%regis", 6) == 0){获取注册用户名;遍历users(即从文件中读出的所有用户信息),判断该用户名是否已经被注册,若未被注册,则添加;if (没有被注册过){用户个数++;获取所有版块信息并组成字符串;向该客户端发送版块信息;}else{向用户发送注册失败的消息; }}5. 处理私聊事务if (strncmp(line, "%priva#", 6) == 0){flag = 1;从line中读出username信息和passwd信息;for (j=0; j<user_size && flag==1; j++){if (该用户名存在 && 该用户在线){将line组装成{private}[users[j].username];flag = 0;}}if (flag == 1){向users[i]发送:{private}[system]user not exists or not on line\n;}}6. 处理群聊事务if (strncmp(line, "%publi#", 6) == 0){从line中读出需要发送的群聊信息;将line组装成{public}[users[i].username];for (j=0; j<user_size; j++){if (在该版块的所有在线用户){发送聊天信息;}}}【客户端】1. 初始化网络接口int init_cli()// --- return sockfd if seccessed, else return 0{int sockfd;int SERV_TCP_PORT;struct sockaddr_in serv_addr;SERV_TCP_PORT = 5000;bzero((char *)&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(SERV_TCP_PORT);if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){print ‘初始化失败’;return 0;}if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){print ‘无法绑定网络地址,初始化失败’;return 0;}return sockfd;}2. 登录int login(char * str, char * username, char * passwd, int sockfd, fd_set sockset, Module modules[]){int result = -1;char ch = 'Y';char message = 'A';int message_size = 0;while (ch=='Y' || ch=='y'){ch = 'N';将str组装成%login#username#passwd的形式;message = get_check_message(str, sockfd, sockset,modules); // 将str发送至服务器端进行验证if (message == 'A'){print ‘无法获取服务器端信息,失败!’;result = -1;}else if (message == '0'){print ‘登录成功!’;result = 0;}else{print ‘用户名或密码错误’;print ‘是否重试?’;scanf("%c", &ch);while (ch!='N' && ch!='n' && ch!='Y' && ch!='y'){print ‘输入有误’;print ‘是否重试?’;scanf("%c", &ch);}}}return result;}3. 注册int regist(char * str, char * username, char * passwd, int sockfd, fd_set sockset, Module modules[]){int result = -1;char ch = 'N';char message = 'A';int message_size = 0;while (ch=='Y' || ch=='y'){将str组装成%login#username#passwd的形式;message = get_check_message(str, sockfd, sockset,modules); // 将str发送至服务器端进行验证if (message == 'A'){print ‘无法获取服务器端信息,失败!’;result = -1;}else if (message == '0'){print ‘注册成功!’;;result = 0;}else{print ‘用户名已存在’;print ‘是否重试?’;、scanf("%c", &ch);while (ch!='N' && ch!='n' && ch!='Y' && ch!='y'){print ‘输入有误’;print ‘是否重试?’;scanf("%c", &ch);}}}return result;}4. 聊天int chat(int status, char * str, int sockfd, fd_set * sockset, char * username){int result = 1;int i, j;char str1[MAX_LINE];while (1){select(sockfd+1, sockset, NULL, NULL, NULL);// 使用select判断一组文件描述符中时候有一个可读(写),// 如果没有就阻塞。

相关主题