信息安全实践第九次作业并发服务器III——多路IO模型
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
四川大学计算机学院、软件学院
实验报告
学号:姓名:专业:__软件工程__ 班级:第 9 周
课程名称信息安全产品开发实践实验课时 4
实验项目并发服务器III——多路I/O模
型
实验时间2013.11.9
实验目的
利用select函数实现在Linux环境下实现一个聊天室程序实验环境虚拟机Ubuntu-VMware Workstation
实验内容(算法、程序、步骤和方法)
这次的实验还是分服务器和客户端,客户端的代码基本用demo中的就行了,只需把通过输入’exit’退出改成通过输
入’/quit’退出就行了。
服务器有关多路I/O的核心代码已经在demo中完成了,理论上只需要往demo 程序中添加聊天室的各个功能就好了。
但是个人刚开始对demo服务器代码还是有疑问,所以把demo 程序的一部分代码修改过,主要改动如下:
1、在扫描所有文件描述符的for循环中,demo程序是从sockfd(广播时+1)服务器套接字(文件描述符)开始往后扫描的,我把它改成了从0(或3)开始扫描(当然,这样做的话需要注意在广播的时候要跳过sockfd)。
2、在demo程序中接收新用户是
clientinfo[newfd].client_id = newfd,而clientinfo的长度为BACKLOG 10,先不说文件描述符0、1、2分别代表着标准输入、标准输出、标准错误输出,实际能用的只有7个,而且也不能保证新建的套接字(文件描述符)数值不超过9,所以我把demo改成当接收新用户时用一个for循环在clientinfo中寻找空闲结点(空闲结点里面的client_id为-1),结点下标和文件描述符本身并没关系
(当然也可以把BACKLOG定义为FD_SETSIZE)。
不过有关这两点改动在后来的试验中发现时毫无意义的,因为文件描述符是从3开始往上递增建立,就是说sockfd作为第一个建立的文件描述符数值为3(所有套接字中最小),后面建立的套接字会按加1递增。
接下来的就是一些细节问题了,如:
1、接收username的时候要注意把最后的换行符’\n’改成’\0’。
2、使用strcmp判断对方的命令的时候不能在第二个参数中直接放一个字符串常量(如strcmp(data_buf, "/help")),要另外声明一个字符指针变量。
3、...
以下是代码:
/* linux-socket-select-异步聊天室
talk_server.c
writed by hanzhongqiu 13/04/2009 Using select() for I/O multiplexing */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
/* port we're listening on */
#define SERVERPORT 1573
/*maximum cnnnect number*/
#define BACKLOG 10
/*maximum data buffer*/
#define BUFSIZE 2048
struct client_info
{
int client_id;
struct sockaddr_in client_address; char usrname[256];//客户的名字int first;//用于只是用户是否刚刚登陆};
int main()
{
/*master file descriptor list*/
fd_set master_fds;
/*temple file descriptor list for select()*/ fd_set read_fds; /*server address*/
struct sockaddr_in server_addr;
/*client address*/
struct sockaddr_in client_addr;
/*maximum file descriptor number*/
int max_fd;
/*listening socket file descriptor*/
int sockfd;
/*newly accept()ed socket file descreptor*/ int newfd; /*buffer for saving client data*/
char data_buf[BUFSIZE];
char send_buf[BUFSIZE];
/*number of client data*/
int nbytes;
/*for set socket option*/
int opt;
opt = SO_REUSEADDR;
/*lenth of address*/
int addr_len;
/*for accept() to use*/
int size;
size = sizeof(struct sockaddr);
/*temple varient*/
int tmp_i, tmp_j;
struct client_info clientinfo[BACKLOG];
int temp;
/*clear the master and temple file descriptor*/
FD_ZERO(&master_fds);
FD_ZERO(&read_fds);
memset(&data_buf, 0, BUFSIZE);
memset(&send_buf, 0, BUFSIZE);
/*create socket*/
if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
perror("create socket() error:");
exit(1);
}
/*set the socket*/
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
{
perror("setsockopt() error:");
exit(1);
}
/*bind first config the socket then binding*/
memset(&server_addr, 0, size);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVERPORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero(&(server_addr.sin_zero), 8);
if (-1 == bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))
{
perror("bind() socket error:");
exit(1);
}
/*listen */
if (-1 == listen(sockfd, BACKLOG))
{
perror("listen() error:");
exit(1);
}
/*add sockfd to master set*/
FD_SET(sockfd, &master_fds);
/*keep track the max file descriptor*/
max_fd = sockfd;
printf("server is ok!\n");
for (temp = 0;temp < BACKLOG;temp++)
{
clientinfo[temp].client_id = -1;
clientinfo[temp].first = -1; //刚登陆——还没输入用户名}
/*loop*/
while (1)
{
read_fds = master_fds;
if (-1== select(max_fd + 1, &read_fds, NULL, NULL, NULL)) //select 监视
{
perror("select() error!\n");
exit(1);
}
/*looking for data to read*/
for (tmp_i = 0; tmp_i <= max_fd; tmp_i++) //轮询所有描述符
{
/*got connect*/
if (FD_ISSET(tmp_i, &read_fds))
{ //判断是服务器还是客户端套接字
if (tmp_i == sockfd) //服务器套接字监听到新的客户端
{
newfd = accept(sockfd, (struct sockaddr*)&client_addr, &size); //新套接字
for (temp = 0;temp < BACKLOG;temp++) //在clientinfo 中找一个空闲的位置
{
if (clientinfo[temp].client_id == -1)
{
clientinfo[temp].client_id = newfd;
clientinfo[temp].client_address.sin_addr =
client_addr.sin_addr;
clientinfo[temp].first = 0; //刚登陆——还没输入用户名break;
}
}
if (-1 == newfd)
{
perror("accept() error:");
exit(1);
}
else
{
FD_SET(newfd, &master_fds); //设置连接集合
if (newfd > max_fd)
{
max_fd = newfd;
}
printf("Get the new connect from %s\n",
(char*)inet_ntoa(client_addr.sin_addr));
}
}
else //接收到客户端发过来的信息
{/*get data from the client*/
for (temp = 0;temp < BACKLOG;temp++) //查找客户端套接字对应的clientinfo下标
if (tmp_i==clientinfo[temp].client_id)
break;
char *cmd = "/quit";
nbytes = read(tmp_i, data_buf,
sizeof(data_buf));
if (0 > nbytes)
{
perror("recv() error:");
exit(1);
}
else if(0 == strcmp(data_buf, cmd)) //客户端结束连接
{
printf("client: %s -- %s exit!\n",
(char*)inet_ntoa(clientinfo[temp].client_address.sin_addr),clientinfo[te mp]. usrname);
strcat(send_buf,
(char*)inet_ntoa(clientinfo[temp].client_address.sin_addr));
strcat(send_buf, "--");
strcat(send_buf,
clientinfo[temp].usrname);
strcat(send_buf, " was exit!");
FD_CLR(tmp_i, &master_fds); //清除描述符
memset(clientinfo[temp].usrname, 0, 256); //清空结构体——以留给下一个用户
memset(&clientinfo[temp].client_address, 0, size);
clientinfo[temp].client_id = -1;
clientinfo[temp].first = 0;
close(tmp_i);
for (tmp_j = 0; tmp_j <= max_fd; tmp_j++) //广播有客户端退出消息{
if (FD_ISSET(tmp_j, &master_fds) && tmp_j!=sockfd)
{
if (-1 == write(tmp_j,
send_buf, sizeof(send_buf)))
{
perror("send data error:");
}
}
}// end for
}
else if (clientinfo[temp].first==0) //刚登陆——准备输入客户名
{
char *p;
p = strchr(data_buf,'\n');
*p = '\0'; //消除用户名后面的换行符
printf("the new user %s : %s \n",
(char*)inet_ntoa(clientinfo[tmp_i].client_address.sin_addr), data_buf);
strcat(clientinfo[temp].usrname,
data_buf); //设置新用户名
clientinfo[temp].first = 1; //已非刚登陆
}
else //已有用户名的用户发送信息
{
char *cmd1 = "/help";
char *cmd2 = "/who";
char *cmd3 = "/send";
if (strcmp(data_buf, cmd1) == 0)
{ //显示帮助信息
strcat(send_buf, "broadcast at usual\n");
strcat(send_buf,
"/help:helping information\n");
strcat(send_buf, "/quit:quit the chat\n");
strcat(send_buf, "/who:get the online clients\n");
strcat(send_buf, "/send username message: send message to username\n");
if (-1 == write(tmp_i,
send_buf, sizeof(send_buf)))
{
perror("send data error:");
}
}
else if (strcmp(data_buf, cmd2) == 0)
{ //显示在线用户
for (temp = 0;temp < BACKLOG;temp++)
{
if
(clientinfo[temp].client_id != -1)
{
strcat(send_buf,
(char*)inet_ntoa(clientinfo[temp].client_address.sin_addr));
strcat(send_buf, "--");
strcat(send_buf, clientinfo[temp].usrname);
strcat(send_buf, "\n");
}
}// end for
if (-1 == write(tmp_i, send_buf, sizeof(send_buf)))
{
perror("send data error:");
}
}
else
{
char *delim=" ";
char data_buf2[BUFSIZE];
strcat(data_buf2,
data_buf);//如果是广播的话data_buf还是会被strtok切分
if(strcmp(strtok(data_buf, delim), cmd3) == 0)
{ //向指定用户发送点到点消息
strcat(send_buf, "private chat from ");
strcat(send_buf,
(char*)inet_ntoa(clientinfo[temp].client_address.sin_addr)); strcat(send_buf, "--");
strcat(send_buf, clientinfo[temp].usrname);
strcat(send_buf, ": \n");
char *name = strtok(NULL, delim);
strcat(send_buf,
strtok(NULL, delim));//如果直接用下面的循环的话会在最前面多一个空格char *message;
while (message =
strtok(NULL, delim))//防止对话内容有空格
{
strcat(send_buf, " ");
strcat(send_buf, message);
}
for (temp = 0;temp < BACKLOG;temp++) //寻找被指定的用户
{
if (strcmp(name, clientinfo[temp].usrname) == 0)
{
if (-1 ==
write(clientinfo[temp].client_id, send_buf, sizeof(send_buf))) { perror("send data error:");
}
break;
}
}
if (temp == BACKLOG)
{ //没有找到用户
名
memset(&send_buf, 0, BUFSIZE);
strcat(send_buf, "the username not exist\n");
if (-1 ==
write(tmp_i, send_buf, sizeof(send_buf)))
{
perror("send data error:");
}
}
}
else
{ //广播
strcat(send_buf,
(char*)inet_ntoa(clientinfo[temp].client_address.sin_addr)); strcat(send_buf, "--");
strcat(send_buf,
clientinfo[temp].usrname);
strcat(send_buf, " said: \n");
strcat(send_buf,
data_buf2);
for (tmp_j = 0; tmp_j <= max_fd; tmp_j++)
{
if (FD_ISSET(tmp_j, &master_fds) && tmp_j!=sockfd) {
if (-1 == write(tmp_j, send_buf, sizeof(send_buf))) { perror("send data error:");
}
}
}// end for
}
memset(&data_buf2, 0, BUFSIZE);//这个不能忘了
}
}//end else 已有用户名的用户发送信息
}//end else 结束客户端发送
}//end if 结束判断是服务器还是客户端套接字
}//end for 结束一次轮询
memset(&data_buf, 0, BUFSIZE);
memset(&send_buf, 0, BUFSIZE);
//FD_ZERO(&master_fds);
//FD_SET(sockfd, &master_fds);
}//end while //结束一次监听
return 0; }
(接上)实验内容(算法、程序、步骤和方法)以下是程序实现效果图:服务器: 客户端Martin:
使用/help、接收Lucy的私聊、接收Lucy的广播、
客户端Amy:
使用/who、接收Lucy的广播、
客户端Lucy:
使用/send对Martin发送私聊、使用/send对不存在的Fancy发起私聊、发送广播、
一旦有客户端退出:
服务器显示:
其它客户端收到广播:
数据记录
和计算
通过
结论
(结果)
小结
有关多路I/O的核心代码已在demo程序中实现了,所以在做实验时反而觉得难点在于对字符串的处理而不是多路I/O本身。
感觉和多进程、多线程相比这样用一个fd_set同一管理更加方便。
指导老师
评议
成绩评定:指导教师签名:。