主要知识点:socket套接字、链表用户管理、线程创建管理、IO复用、select监听套接字、文件读写等。
https://download.csdn.net/download/mrhjlong/10337335
/*************************************************************************
> File Name: client.c
> Author: mrhjlong
> Mail: [email protected]
> Created Time: 2016年08月01日 星期一 14时40分39秒
************************************************************************/
#include "userlist.h"
void *func_ttl(void *arg)
{
int fd = (int)arg;
while(1)
{
cli_TTL(fd);
sleep(20);
}
return NULL;
}
int main(int argc, char *argv[])
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
err_sys("socket error");
struct sockaddr_in des_addr;
des_addr.sin_family = AF_INET;
des_addr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &des_addr.sin_addr);
//连接客户端
int ret = connect(fd, (struct sockaddr *)&des_addr, sizeof(des_addr));
if(ret == -1)
err_sys("connect error");
//登陆和注册登陆
ret = cli_REG_LOG(fd);
if(ret == -1) //放弃登陆或注册,直接退出
{
close(fd);
return 0;
}
//创建线程发送保活信息
pthread_t pid;
ret = pthread_create(&pid, NULL, func_ttl, (void *)fd);
if(ret != 0)
err_sys("pthread create error");
MSG msgdata;
fd_set read_set;
int n;
int flag = 0;
char recv_buf[BUFSIZE] = {0};
char fname[50] = {0};
while(1)
{
FD_ZERO(&read_set);
if(flag == 0)
{
printf("Input a command: s-send | f-file | l-list | q-quit...\n");
FD_SET(0, &read_set);
}
FD_SET(fd, &read_set);
select(fd + 1, &read_set, NULL, NULL, NULL);
if(FD_ISSET(0, &read_set)) //输入响应
{
bzero(&msgdata, sizeof(msgdata));
fgets(msgdata.cmd, 50, stdin);
if(strcmp(msgdata.cmd, "q\n") == 0) //退出
{
pthread_cancel(pid); //关闭线程
flag = 1;
shutdown(fd, SHUT_WR);
FD_CLR(0, &read_set);
continue;
}
else if(strcmp(msgdata.cmd, "l\n") == 0) //发送命令,列出当前在线用户
{
cli_LIST(fd);
continue;
}
else if(strcmp(msgdata.cmd, "s\n") == 0)
{
cli_SEND(fd, &msgdata);
}
else if(strcmp(msgdata.cmd, "f\n") == 0) //发送FILE命令
{
bzero(fname, 50);
ret = cli_FILE(fd, &msgdata, fname);
if(ret == 0)
flag = 1;
continue;
}
else
{
printf("#################WARNING#################\n");
printf("Input error! Please try again!\n");
printf("#########################################\n");
continue;
}
}
if(FD_ISSET(fd, &read_set)) //接收信息
{
bzero(recv_buf, BUFSIZE);
bzero(&msgdata, sizeof(msgdata));
n = recv(fd, recv_buf, BUFSIZE, 0);
if(n == 0) //关闭
{
printf("closed!\n");
break;
}
read_XML(recv_buf, &msgdata);
if(strcmp(msgdata.cmd, "LISTD") == 0)
{
printf("**************Online users:**************\n");
printf("%s\n", msgdata.text);
printf("*****************************************\n");
continue;
}
else if(strcmp(msgdata.cmd, "RECV") == 0)
{
printf("*****************RECV...*****************\n");
printf("FROM:%s\n", msgdata.name);
printf("MSG:%s\n", msgdata.text);
printf("*****************************************\n");
}
else if(strcmp(msgdata.cmd, "NOUSR") == 0)
{
printf("#################WARNING#################\n");
printf("User:%s is offline! Please try later.\n", msgdata.text);
printf("#########################################\n");
flag = 0;
}
else if(strcmp(msgdata.cmd, "LEAVE") == 0)
{
pthread_cancel(pid); //关闭线程
printf("cmd closed!\n");
break;
}
else if(strcmp(msgdata.cmd, "FCNT") == 0) //建立传输文件连接,发送
{
cli_FCNT(&msgdata, fname);
flag = 0;
}
else if(strcmp(msgdata.cmd, "FLSN") == 0) //建立传输文件监听, 接收信息
{
cli_FLSN(fd, &msgdata);
}
}
}
close(fd);
return 0;
}
/*************************************************************************
> File Name: server.c
> Author: mrhjlong
> Mail: [email protected]
> Created Time: 2016年08月01日 星期一 14时13分10秒
************************************************************************/
#include "userlist.h"
int main(void)
{
struct list_head list; //新建在线用户链表
INIT_LIST_HEAD(&list); //初始化链表头
struct list_head usrList; //新建已注册用户链表
INIT_LIST_HEAD(&usrList);
FILE *fp = fopen("regUser.txt", "r");
if(fp == NULL)
err_sys("open regUser.txt error!\n");
//获取已注册用户链表
get_user_list(fp, &usrList);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
err_sys("socket error");
int optval = 1;
int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
if(ret == -1)
err_sys("setsockopt error!");
//服务器地址端口设置
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(9999);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定服务器
ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if(ret != 0)
err_sys("bind error");
//监听
ret = listen(sockfd, 10);
if(ret != 0)
err_sys("listen error");
printf("listening...\n");
fd_set read_set;
MSG msgdata;
char recv_buf[BUFSIZE] = {0};
int n;
struData_t *p = NULL;
struct list_head *pos = NULL;
struct timeval timeout;
LD ldata;
while(1)
{
FD_ZERO(&read_set);
FD_SET(sockfd, &read_set);
FD_SET(0, &read_set);
//遍历链表,添加套接字到select
list_for_each(pos, &list)
{
p = list_entry(pos, struData_t, list);
FD_SET(p->sockfd, &read_set);
}
//设置select
timeout.tv_sec = 3; //阻塞3秒
timeout.tv_usec = 0;
if(pos->prev == &list)
ret = select(sockfd + 1, &read_set, NULL, NULL, &timeout);
else
{
p = list_entry(pos->prev, struData_t, list);
ret = select(p->sockfd + 1, &read_set, NULL, NULL, &timeout);
}
//超时返回,检测客户保活信息
if(ret == 0)
{
chk_ttl(&list);
continue;
}
//服务端退出
if(FD_ISSET(0, &read_set))
{
char cmd_quit[50] = {0};
fgets(cmd_quit, 50, stdin);
if(strcmp(cmd_quit, "q\n") == 0) //输入q退出
{
ser_quit(&usrList, &list);
printf("Closing server!\n");
break;
}
else if(strcmp(cmd_quit, "s\n") == 0) //输入s显示聊天记录
{
FILE *fp = fopen("Chatlog.txt", "r");
char buffer[100] = {0};
if(fp == NULL)
printf("open Chatlog.txt error!\n");
printf("*****************Chat record:******************\n");
fgets(buffer, 100, fp);
while(strlen(buffer) >= 8)
{
printf("%s", buffer);
bzero(buffer, 100);
fgets(buffer, 100, fp);
}
printf("***********************************************\n");
}
else if(strcmp(cmd_quit, "l\n") == 0) //输入l获取已注册用户信息
{
FILE *fp = fopen("regUser.txt", "r");
if(fp == NULL)
err_sys("open regUser.txt error!\n");
//获取已注册用户链表
get_user_list(fp, &usrList);
}
else
printf("Input error! Please try again!\n");
continue;
}
//监听套接字响应
if(FD_ISSET(sockfd, &read_set))
{
ldata.sockfd = sockfd;
ldata.usrList = &usrList;
ldata.list = &list;
//创建处理注册和登陆操作的线程
pthread_t pid;
int n = pthread_create(&pid, NULL, listen_reg_log, (void *)&ldata);
if(n != 0)
err_sys("pthread create error");
n = pthread_detach(pid);
if(n != 0)
err_sys("pthread detach error");
usleep(200); //等待线程accept,否则会重复创建线程
ret--;
}
//客户端套接字响应,接收到信息
if(ret > 0)
{
//遍历链表,找到响应的套接字
list_for_each(pos, &list)
{
p = list_entry(pos, struData_t, list);
if(FD_ISSET(p->sockfd, &read_set))
break;
}
bzero(recv_buf, BUFSIZE);
n = recv(p->sockfd, recv_buf, BUFSIZE, 0);
//客户端主动关闭,服务端释放内存
if(n == 0)
{
printf("User:%s is leaving...\n", p->client_name);
close(p->sockfd);
list_del(pos);
free(p);
continue;
}
bzero(&msgdata, sizeof(msgdata));
read_XML(recv_buf, &msgdata); //解析XML数据
if(strcmp(msgdata.cmd, "LIST") == 0) //LIST命令处理
{
ser_LIST(p->sockfd, &list, &msgdata);
continue;
}
else if(strcmp(msgdata.cmd, "TTL") == 0) //TTL命令处理
{
time(&(p->ttl)); //重置保活时间
continue;
}
else if(strcmp(msgdata.cmd, "SEND") == 0) //SEND命令处理
{
ser_SEND(p->sockfd, p->client_name, &list, &msgdata);
continue;
}
else if(strcmp(msgdata.cmd, "FILE") == 0)
{
ser_FILE(p->sockfd, p->client_name, &list, &msgdata);
}
}
}
close(sockfd);
return 0;
}