一、实验名称
动手打造自己的 IM
二、实验目的
1本次实验旨在锻炼大家的Socket编程能力,以日常生活中广泛使用的IM软件为背景,培养大家对于网络编程的兴趣。
2、通过本次实验,培养linux环境下网络编程能力,使得我们对网络应用层的网络应用软件有了一个深入的理解,这对网络课程的学习起到了很好的实践作用。
三、实验内容及步骤
实验内容:本作业要求同学们完成一个简陋的IM的客户端和服务器端,实现一些最基本和最常用的IM功能:
客户端功能:
1、4个以!开头命令,!login登录命令!list列出在线用户命令!file传输文件命令!logoff注销命令。
2、回显功能:当其他用户登录或者登出时,客户端会收到服务器的提示信息,当向另一个客户端发送文件之后,也会收到该客户端的提示信息。
3、聊天功能:两个客户端可以进行聊天:
以[username]: 开头的行为其他用户向你发送的聊天信息。
以>>[username]: 开头的行为你向其他用户发送的聊天信息。
服务器功能:
Server端基本架构:多进程并发服务器实现,为每个在线用户分配一个进程用以和在线用户保持通信,并在子进程退出(某用户下线)时,回收子进程资源。
ü服务器端固定端口:8081
ü用户登录成功,反馈提示信息
ü维护在线用户信息:包括IP地址和端口号,用户名字,用户密码等
ü向在线用户发出其他用户上/下线提示信息
实验步骤:
1、熟悉Linux下C语言的编程环境和编译方法,学会用gedit来编写并修改代码及命令行工具。
2、学习有关socket方面的有关知识,尝试着去编写服务器端和客户端的实现连接的代码,最重要的是学会使用socket()、listen()、bind()、connect()、accept()等函数的使用方法,在此基础上实现客户端和服务器端的连接。
3、在实现客户端和服务器端连接的基础上编写处理!login、!logoff、!list等简单的命令(无需客户端与客户端交互),这个过程最重要的是学会send()、recv()函数的使用,传递套接字时要注意接收和发送的对应。
4、在实现了单个客户端与服务器的用简单命令交互的基础上,学习线程的有关知识在服务器端创建多线程,实现服务器可以和多个客户端交互的功能。此过程最重要的是学会线程创建和使用,最重要的是pthread_create(4个参数)的使用,其中关键的地方是线程函数的建立及其参数的传递,参数是socket,这样才能在线程中实现与客户端的交互。
5、在实现了多个客户端与服务器进行交互的基础上,就有可能实现客户端与客户端的交互,编程时一个很关键的地方就是所使用的数据结构,没有一个存储logined客户端的数据结构,就很难实现客户端与客户端的交互。因此需要建立一个结构体来存储客户端的socket、port(可选)、username、flag(状态)。
两种方案:
1)从服务器获得另一个客户端的端口号和IP后,让客户端与客户端建立连接,实现直接交互,这就是P2P结构,编程时我试着使用了这种方法,但是连接总是出问题。
2)通过服务器实现客户端之间的交互,简单来说就是客户端把信息通过socket发送到服务器,服务器再把信息通过不同的socket发送到客户端,这是建立在每个客户端都有自己的socket的基础上的。
我选择了第二种方案来实现客户端的交互。
6、编程进行到这里,我突然意识到了如果客户端没有监听服务器的代码,无法接收和识别服务器的信息是客户端要发送的信息、还是服务其本身要发送的信息,思考良久,无奈之下只能大篇幅的修改代码,在客户端使用双线程,一个用于监听键盘的输入,一个用于监听服务器是否发送信息;这样当键盘输入时一个线程接收并负责向服务器发送信息,当收到服务器发送的信息时,另一个线程接收并把信息输出到终端上。
7、在以上的基础上,就可以实现客户端的通话及文件的发送,最后就是调试程序并整理输出的格式。
四、实验分析
1,server.c编译及运行结果:
图1处于监听状态的服务器
2,开启另外终端运行客户端程序:
第一个用户:Me,运行客户端程序之后,使用命令!login登录,成功登录后会收到服务器的发送的信息,结果如下:
图2 一个client登陆成功后的服务器
图3第一个客户端Me登陆成功
另起两个终端第二个客户端You和第三个客户端Him登录后结果如下:
图4多个客户端登陆后的服务器
3,指令!list的使用:
图5 Me客户端上执行!list命令
4,!logoff命令:当用户Me执行!logoff后,客户端显示服务器发送的信息,此时使用!list指令,结果如下:
图6客户端Him执行!logoff命令
图7客户端Him执行!logoff命令后Me执行!list的结果
5,聊天功能演示:
图8 Me客户端和You客户端进行通话
图9You客户端和Me客户端通话
6,指令!file的使用,文件传输功能演示:
图10You客户端向Me发送lilufeng.pdf文件显示成功
图11 Me客户端接收文件成功信息
7、几种错误处理,结果如下:
图12用户名错误登录失败
图13发送文件时用户不在线、发送失败
图14Me想和You对话,You没有登录,连接失败
五、实验结论
难点:
1、本实验是基于Linux环境下,使用C语言编写而成,这两者都需要实验前进行深入的探索和学习,对我而言,加大了编程和实现的难度。
2、对socket和thread方面的知识可以说一无所知,编程的过程中要不断地学习才能向前迈一小步。
感受:
1、这次实验的过程中,我深知自己的知识的匮乏,实验的过程可以说完全是一个探索的过程,对用到的函数我必须要在Dev等工具下先进性一定的应用和学习才能真正掌握其用法和内涵,实践的过程中学到了很多的知识,而且在编程的后面阶段感觉对这些知识已经能够熟练地应用,最有说服力的就是那些函数的掌握,可见,软件这个行业只有在实践中才能真正的学到本领。
2、编程的前后,我的代码被改了很多遍,因为开始编程的时候没有一个整体的构思,只能走一步算一步,每走一步都是那么的艰难,线程的加入和数据结构的改变都使原来的代码结构发生天翻地覆的变化,到最后在客户端加入双线程时整体的代码又改了一遍,所以说这次试验让我感觉非常的累,而且过程中感觉网络编程是那么的复杂,现在想想这些东西,还是很简单的。由此,我明白了开发软件前为什么要进行详细的设计,看着是浪费时间,其实那是在节省时间和精力,而且是开发出一个软件的基础。
3、通过这次试验让我学会了关于网络的socket和P2P等很多知识,这些知识在短期或者说今生难于忘记,当然还有操作系统方面的thread和process方面的知识。
4、同时我也感受到了IT人士的辛劳,其实我一直都在感受到,只不过这次更加强烈罢了,不过辛劳后,看到自己的成果和感到内心的充实,觉得付出还是值得的。
客户端程序源码:
//[浠g爜]
tcpclient.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define
SERVER_PORT 8081 //
define the defualt connect port
id
#define
BUFFER_SIZE 255 //buf size
int
clifd,length = 0;//socket size
char
buf[BUFFER_SIZE]; //the buffer of socket
pthread_t
thread;//thread discription
char
command[100],uName[20],pwd[20];
void
*listenServer(int);//listen and receive message from the server
int
cli_socket_connect_server(int);//create socket and connect the
server
int
main(int argc)
{
clifd =
cli_socket_connect_server(SERVER_PORT);//return the socket
while(1)
{ char
frontcmd[20],ch1,ch2;
int countcmd
=0,countFcmd=0;
memset(frontcmd,' ',sizeof(frontcmd));
memset(command,' ',sizeof(command));
memset(buf,' ',sizeof(buf));
while(ch1 =
getchar()) //input the command
{
if(ch1 == ' ')
{
while(ch2 =
getchar())
{
if(ch2 == 'n')
break;
else
command[countcmd++]
= ch2;
}
break;
}
else if(ch1 ==
'n')
break;
else
frontcmd[countFcmd++] = ch1;
}//input
over:frontcmd is the really command
if(frontcmd[0] ==
'>')//talk function process
{
strcpy(buf,">>");
send(clifd,buf,BUFFER_SIZE,0);
char *talkto;
talkto =
strtok(frontcmd,">:");
strcpy(buf,talkto);//talk to whom
strcat(buf,":");//:
is used to strtok in the server
strcat(buf,command);//what you send
send(clifd,buf,BUFFER_SIZE,0);
}
if(strcmp(frontcmd,"!logoff") == 0)//logoff command process
{
strcpy(buf,"!logoff");
send(clifd,buf,BUFFER_SIZE, 0);
strcpy(buf,uName);
send(clifd,buf,BUFFER_SIZE, 0 );
close(clifd);
pthread_exit(NULL);
break;
}//end of logoff
if
if(strcmp(frontcmd,"!list") == 0)//list command process
{
strcpy(buf,"!list");
send(clifd,buf,BUFFER_SIZE, 0 );
}//end of list
if
if(strcmp(frontcmd,"!login") == 0)//login command process
{
strcpy(buf,"!login");
send(clifd,buf,BUFFER_SIZE, 0 );
printf("User
name:t");
scanf("%s",uName);
printf("Password:t");
scanf("%s",pwd);
strcpy(buf,uName);
send(clifd,buf,BUFFER_SIZE, 0 );
length =
recv(clifd,buf,BUFFER_SIZE,0);
if
(length < 0)
{
printf(" error comes
when recieve data from server! send filen");
exit( 1 );
}
printf("From
server:nt%sn",buf);
if(pthread_create(&thread, NULL, (void
*)listenServer, (void *)clifd) == 0) //after login success create
the listenServer thread
{
printf("listen
thread create success!n");
}
else
{
printf("listen
thread create failed!n");
exit(1);
}
}//end of login
if
if(strcmp(frontcmd,"!file") == 0)
{
strcpy(buf,frontcmd);//send front command(!file) to the server
send(clifd,buf,BUFFER_SIZE,0);
char
*file,*toUser;
file =
strtok(command," =>");//file
toUser =
strtok(NULL," =>");//destination user
printf("filename:t%sntoUser:t%s",file,toUser);
strcpy(buf,toUser);//send the file to whom
send(clifd,buf,BUFFER_SIZE,0);
}//end of file
if
}//end of while
return 0;
}
void
*listenServer(int file_socket)
{
while(recv(file_socket,buf,BUFFER_SIZE,0)>0)
{
char *flagcmd =
NULL,*printInfo = NULL;
flagcmd =
strtok(buf,"|");
printInfo =
strtok(NULL,"|");
if(strcmp(flagcmd,"!list") == 0)//list the user that logined
now
printf("from
server:n%s",printInfo);
else
if(strcmp(flagcmd,"!logoff") == 0)//logoff
printf("from
server:nt%s",printInfo);
else
if(strcmp(flagcmd,"!file") == 0)//send the file
{
if(strcmp(printInfo,"0") == 0)//destination user does not login
printf("From
server:ntuser does not login,file failed!n");
else
//destination user logined
printf("nConnect
success!file success!n");
}
else
if(strcmp(flagcmd,"!msg") == 0)//receive the file message
{
printf("From
sever:nt%s",printInfo);
}
else
if(strcmp(flagcmd,"!talk") == 0)//talk message
{
if(strcmp(printInfo,"0") == 0)
printf("The user
does not login!n");
else
printf("%sn",printInfo);
}
else
printf("Please
debug!");
}
}
int
cli_socket_connect_server(int serverPort)
{
struct
sockaddr_in cliaddr;
socklen_t socklen
= sizeof(cliaddr);
char
sever[20];//store the IP
int
client_socket;
if((client_socket
= socket(AF_INET,SOCK_STREAM, 0
)) < 0
)//create socket of the client
{
printf(" create
socket error!n ");
exit( 1 );
}
bzero(
& cliaddr,sizeof(cliaddr));
cliaddr.sin_family
= AF_INET;
inet_aton(sever,
& cliaddr.sin_addr);
cliaddr.sin_port
= htons(serverPort);//port of
the sever
cliaddr.sin_addr.s_addr =
htons(INADDR_ANY);//the same IP with the
sever.Connect auto!
if(connect(client_socket,(struct sockaddr * )
& cliaddr, socklen)
< 0 )//connect
the server
{
printf(" can't
connect!n ");
exit( 1 );
}
return
client_socket; //return the socket
}
服务器端源码:
//tcpserver.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define
SERVER_PORT 8081 //
define the defualt connect port
id
#define
LENGTH_OF_LISTEN_QUEUE 10 //
length of listen queue in server
#define
MAX_THREAD 10
#define
BUFFER_SIZE 255
#define
SUCCESS_MESSAGE "Login
Success!"
#define
FAIL_MESSAGE "Login Failed!"
#define
TOTAL_USER_NUM 6
struct
clientInformation
{
int flag;
//record
the struct state: 0 is empty
char loginUsr[20];
//the users that have logined
int socket;
//store the logined uses'
socket
};
struct
clientInformation clientInfo[TOTAL_USER_NUM];//logined users'
array
int
servfd,sever_socket; //the socket
int len=0;
//record the length of the socket
int numOfThread = 0;
//record the number of
threads
char users[6][10] =
{"Me","You","Him","Her","user","tmd"};//users that can login
char
buf[BUFFER_SIZE]; //the capacity of the
socket
pthread_t
thread[MAX_THREAD]; //thread discription
struct
sockaddr_in servaddr,cliaddr;
void*
docommand(int);//thread function
int main(int
argc)
{
int num;
for(num = 0; num
< TOTAL_USER_NUM; num++)
{
clientInfo[num].flag
= 0;
clientInfo[num].socket = 0;
}
if ((servfd =
socket(AF_INET,SOCK_STREAM, 0 ))
< 0 )//create
socket in the server
{
printf(" create socket
error!n ");
exit( 1
);
}
bzero( &
servaddr,sizeof(servaddr));
servaddr.sin_family =
AF_INET;
servaddr.sin_port =
htons(SERVER_PORT);
servaddr.sin_addr.s_addr =
htons(INADDR_ANY);
if(bind(servfd,(struct sockaddr
* ) & servaddr,sizeof(servaddr)) < 0
)//bind
{
printf(" bind to
port %d failure!n ",SERVER_PORT);
exit( 1
);
}
if(listen(servfd,LENGTH_OF_LISTEN_QUEUE)
< 0
)//listen
{
printf("
call listen failure!n ");
exit( 1
);
}
while(numOfThread
< MAX_THREAD)
{
socklen_t length
= sizeof(cliaddr);
sever_socket =
accept(servfd,(struct sockaddr
* ) & cliaddr, & length);//accept
the login
if
(sever_socket <
0 )
{
printf("
error comes when call accept!n ");
break;
}
else
{
//void* arg[] =
{&sever_socket};
if(pthread_create(&thread[numOfThread++], NULL,
(void *)docommand, (void *)sever_socket) != 0)
printf("绾跨▼%d鍒涘缓澶辫触!n",numOfThread); //create
the new thread to the login client
else
printf("绾跨▼%d琚垱寤簄",numOfThread);
}
}
close(servfd);
return 0
;
}
void *docommand(int
sever_socket)
{
while
( 1 )
{// server loop will nerver exit unless any body kill the process
memset(buf,' ',sizeof(buf));
len
= recv(sever_socket, buf,
BUFFER_SIZE, 0); //recv the
command
if
(len < 0)
{
printf(" error comes when
recieve data from client ! " );
exit( 1 );
}
else
{
if(strcmp(buf,">>") == 0) //talk
process
{
len
= recv(sever_socket, buf,
BUFFER_SIZE, 0);//recv the user name that the file will be sent
to
if
(len < 0)
{
printf("error comes when
recieve data from client ! " );
exit( 1 );
}
char
temp[100],*talkto;
strcpy(temp,buf);//temp store what will be sent
talkto =
strtok(buf,":");
int j;
for(j = 0;j
< TOTAL_USER_NUM;j++)
{
if(clientInfo[j].flag == 1)
{
if(strcmp(clientInfo[j].loginUsr,talkto) == 0)
{
strcpy(buf,"!talk|");
strcat(buf,temp);//move temp(the information) to buf
send(clientInfo[j].socket,buf,BUFFER_SIZE,0);//send the talking
message to whom what you talk
break;
}
}
}
if(j ==
TOTAL_USER_NUM)
{
strcpy(buf,"!talk");
strcat(buf,"|0");//"0" == user does not login
send(sever_socket,buf,BUFFER_SIZE, 0 ); //send message(user does
not login)
}
}
else
if(strcmp(buf,"!file") == 0) //file process
{
len
= recv(sever_socket, buf,
BUFFER_SIZE, 0);//recv the user name that the file will be sent
to
if
(len < 0)
{
printf("error comes when
recieve data from client ! " );
exit( 1 );
}
int i;
for(i = 0;i
< TOTAL_USER_NUM;i++)
{
if(clientInfo[i].flag == 1)
{
if(strcmp(clientInfo[i].loginUsr,buf) == 0)
{
strcpy(buf,"!file|");
strcat(buf,"1");//"1" == user logined
send(sever_socket,buf,BUFFER_SIZE, 0 ); //send to the client who
send file(receiver)
strcpy(buf,"!msg|nreceived the file!n");
send(clientInfo[i].socket,buf,BUFFER_SIZE,0);//tell the client to
recv the file
break;
}
}
}
if(i ==
TOTAL_USER_NUM)
{
strcpy(buf,"!file");
strcat(buf,"|0");//"0" == user does not login
send(sever_socket,buf,BUFFER_SIZE, 0 ); //send message(user does
not login)
}
}//end of if
else
if(strcmp(buf,"!list") == 0) //list the logined users process
{
strcat(buf,"|");
int k;
for(k = 0; k
< TOTAL_USER_NUM; k++)
{
if(clientInfo[k].flag == 1)
{
strcat(buf,clientInfo[k].loginUsr);
strcat(buf,"n");
}
}
send(sever_socket,buf,BUFFER_SIZE,0); //send all logined users to
client
}//end of else if
1
else
if(strcmp(buf,"!logoff") == 0) // logoff process
{
while(1)
{
strcat(buf,"|logoff
success!n");
send(sever_socket,buf,BUFFER_SIZE,0); //send logoff success
message
memset(buf,' ',sizeof(buf));
len
= recv(sever_socket, buf,
BUFFER_SIZE, 0); //recv the login users name
int k;
for(k = 0; k
< TOTAL_USER_NUM; k++)//move it out from the
clientinformation struct
{
if(strcmp(buf,clientInfo[k].loginUsr) == 0)
{
strcpy(clientInfo[k].loginUsr," ");
clientInfo[k].flag =
0;
clientInfo[k].socket
= 0;
break;
}
}
close(sever_socket);
numOfThread--;
pthread_exit(NULL);
break;
}
}//end of else if
2
else
if(strcmp(buf,"!login") == 0)//login process
{
len
= recv(sever_socket, buf,
BUFFER_SIZE, 0); //recv the login user's name
if
(len < 0)
{
printf("error comes when
recieve data from client ! login" );
exit( 1 );
}
printf("from
client:%stIP:%stPort:%dn ",buf,
inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
int i;
for(i = 0;i
< TOTAL_USER_NUM;i++)//judge whether have this
user
{
if(strcmp(buf,users[i]) == 0)
{
for(i = 0;i
< TOTAL_USER_NUM;i++)
{
if(clientInfo[i].flag == 0)
{
strcpy(clientInfo[i].loginUsr,buf);
clientInfo[i].socket
= sever_socket;
clientInfo[i].flag =
1;
break;
}
}
strcpy(buf,SUCCESS_MESSAGE);
send(sever_socket,buf,BUFFER_SIZE, 0 ); //send login success
message
break;
}
}
if(i ==
TOTAL_USER_NUM)
{
printf("No user
called %s! Login failed!n",buf);
memset(buf,' ',sizeof(buf));
strcpy(buf,FAIL_MESSAGE);
send(sever_socket,buf,BUFFER_SIZE, 0 ); //send login failed
message
}
}//end of else if
3
} //end of else
}// end of while
}
又不懂得地方,欢迎留言询问,我必尽力解答!