局域网 OICQ 程序设计
平台开发和环境简介
Linux 系统 + Gcc + makefile
功能描述
实现局域网 OICQ 程序设计,包括客户端和服务端。
客户端描述:客户端运行开始出现登陆界面。与服务端进行连接,连接后把账号信息发送给服务端,服务端验证后,把确认结果通知客户端。如果通过验证,客户端从服务端接收其他在线客户端信息并把这些客户信息显示给用户。用户可以选择客户并与之进行信息交流。即发送消息和接受消息。并把结果显示给用户。
服务端功能描述:服务端启动后,等待客户端连接。接受客户端发送过来的账号信息。进行验证。并把验证的结果返回给客户端。如果验证通过,记录客户端的信息。并把该客户端的记录的信息发送给其他的客户端,也把其他的在线用户发送给该用户,实现整个网络内的在线客户信息的同步。
项目实施
服务端利用epoll函数实现对客户端多用户连接的管理,首先将bind、listen过的socket的文件描述符加到epoll集合中,监听它的读,每当有新的客户端连接过来,就将与之通讯的文件描述符加到epoll集合中并创建一个在线户列表记录它们的信息(我用的是链表来储存这些信息)当客户端断开后就删除epoll中的文件描述符,并把在线户列表中的信息删除(删除这个节点)。每当与他们通信的描述符“准备好了”,epoll_wait函数就会解除阻塞。客户端发送信息给服务端的信息都有一个包头,服务端通过这个包头来确定客户端发送的是什么类型的信息,以便后续的操作。
客户端有注册、登录、注销的功能,登录成功后才可以发送信息和接受信息。发送信息,服务端建立一个记录信息的链表,每一个用户发送信息,服务端都会增加一个节点记录信息(发送方的ID,接受方的ID,发送的信息),
epoll
我采用的I/O复用机制,来实现对多客户的连接,用LINUX支持的epoll来实现的,因为epoll的速度比select要快,管理文件描述的数量也别select要多。
while(1)
{
nfound = epoll_wait(efd,evs,20,-1);
if(nfound < 0)
{
perror("epoll_wait");
exit(0);
}
for(int i=0;i<nfound;i++)
{
if(evs[i].data.fd == fd)//有新的客户连接
{
nfd = accept(fd,(struct sockaddr *)&caddr,&caddr_len);
if(nfd < 0)
{
perror("accept");
return -1;
}
struct sockaddr_in addr;
read(nfd,&addr,sizeof(addr));
ev.data.fd = nfd;
ev.events = EPOLLIN;
epoll_ctl(efd,EPOLL_CTL_ADD,nfd,&ev);
add_link(&head,nfd,addr);// 增加节点记录客户端信息
}
else
{
ret = my_read(evs[i].data.fd,head);//read
if (ret == -1)
{
epoll_ctl(efd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);//删除出错的客户端fd
del_node(&head,evs[i].data.fd);//删除出错的节点
}
}
}
心跳机制
客户端建立一个线程,传入与服务端TCP协议连接的描述符,每隔3秒发送一个心跳包给服务端,证明自己“活着”。
void *send_heatbeat(void *ptr)
{
pthread_detach(pthread_self());
int fd = *(int *)ptr;
PACK *ph = NULL;
ph = malloc(sizeof(PACK));
ph -> type = HEAT_FLG;
ph -> len = 0;
while(1)
{
sleep(3);
write(fd,ph,sizeof(PACK));
}
}
服务端建立一个线程,来实现对每一个客户的超时管理,如果客户端超过十秒没有发送心跳包给服务端,服务端就认为,该客户已经离线,进行离线处理。
void *admin_thread(void *ptr)
{
pthread_detach(pthread_self());
BAG pt = *(BAG *)ptr;//将在线户链表的头结点的地址和epoll的文件描述符打包传入
NODE **head = pt.pb;
NODE *pa = *head;
int efd = pt.efd;
struct epoll_event ev;
while(1)
{
pa = *head;
sleep(1);
pthread_spin_lock(&spin);//加锁防止其他线程同时对在线户列表同时写
while(pa != NULL)
{
(pa -> count)--;
if(pa -> count == 0)
{
ev.data.fd = pa->nfd;
ev.events = EPOLLIN;
epoll_ctl(efd,EPOLL_CTL_DEL,pa->nfd,&ev);//删除长时间未响应的客户端fd
thread_del_node(head,pa->nfd);//删除长时间未响应的节点
}
pa = pa->next;
}
pthread_spin_unlock(&spin);//解锁
}
}
守护进程
服务端使用守护进程,一种后台运行独立于所有终端控制之外的进程。守护进程中重定向标准输入、输出、错误输出的文件描述符到指定文件。
void deamon(void)
{
int pid;
int fd;
FILE *fp;
int res;
pid = fork();
if(pid > 0)
{
exit(0);
}
setsid();
umask(0022);
res = chdir("/home/shangqian/ROOT/project/QQ/deamon");
if(res < 0)
{
perror("chdir");
exit(-1);
}
close(STDIN_FILENO);
fd = open("/dev/null",O_WRONLY);
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
fp = fopen("./dea_conf","w");
if(fp < 0)
{
printf("wrong\n");
exit(0);
}
fprintf(fp,"%d\n",getpid());//记录守护进程的进程号
fclose(fp);
}
结果展示
客户端界面
登录成功后界面
发送信息界面
查看在线用户
查看聊天记录
总结
开发完这个项目后,我对网络通讯方面有了更深的了解,对于tcp/ip通信协议和udp通讯协议有了全面的认识,对于socket编程也更加的熟练。