要求:
1、从配置文件中读取数据;
2、编写可滚动的日志文件;
3、实现非阻塞套接字,应用epoll;
4、实现心跳检测(心跳包);
5、分线程处理
client.c 客户机程序
#include "head.h"
#include "config.h"
#include "heart_client.h"
#define BUFFER_SIZE 40
int main(int argc, char *argv[])
{
int client_sockfd;
int len;
struct sockaddr_in remote_addr; // 服务器端网络地址结构体
memset(&remote_addr,0,sizeof(remote_addr)); // 数据初始化--清零
remote_addr.sin_family=AF_INET; // 设置为IP通信
char s_ip[20];
GetConfigFileStringValue("IPANDPORT", "IP", "127.0.0.1", s_ip, sizeof(s_ip), "Config.ini");
printf("IP : %s\n", s_ip);
remote_addr.sin_addr.s_addr=inet_addr(s_ip);// 服务器IP地址
uint16_t port = GetConfigFileIntValue("IPANDPORT", "PORT", 8866, "Config.ini");
printf("port : %d\n", (int)port);
if (port == -1){ // 判断获取到的年龄是否正确
printf("Get port failed!\n");
return -1;
}
remote_addr.sin_port=htons(port); // 服务器端口号
// 创建客户端套接字--IPv4协议,面向连接通信,TCP协议
client_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(client_sockfd<0){
perror("client socket creation failed");
exit(EXIT_FAILURE);
}
// 将套接字绑定到服务器的网络地址上
if((connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0){
perror("connect to server failed");
exit(EXIT_FAILURE);
}
pthread_t pth;
int err;
int *client_sockfd_1 = (int *)malloc(sizeof(int));
*client_sockfd_1 = client_sockfd;
if((err = pthread_create(&pth, NULL, send_heart, (void *)client_sockfd_1)) != 0){
fprintf(stderr, "pthread_create: %s\n", strerror(err));
exit(1);
}
sleep(5);
// 循环监听服务器请求
pd = (DATA_PACK *)malloc(sizeof(DATA_PACK));
char *buf = (char *)malloc(sizeof(*pd));
pd->data_type=8;
strcpy(pd->name, "127.0.0.1");
pd->num1= (int )GetConfigFileIntValue("TWONUMBER", "NUMBER1", 0x3f3f3f3f, "Config.ini");
pd->num2 = (int )GetConfigFileIntValue("TWONUMBER", "NUMBER2", 0x3f3f3f3f, "Config.ini");
printf("两个数为:%d %d\n",pd->num1,pd->num2);
len = sizeof(*pd);
memcpy(buf, pd, len);
send(client_sockfd,buf,len,0);
// 接收服务器端信息
len=recv(client_sockfd,pd,BUFFER_SIZE,0);
if(pd->data_type==8)
printf("receive from server %s: %d\n",pd->name,pd->num1);
else
printf("receive from server %s: 发送数字个数不正确\n",pd->name);
if(len<0)
{
perror("receive from server failed");
exit(EXIT_FAILURE);
}
free(pd);
close(client_sockfd);// 关闭套接字
return 0;
}
config.c //实现获取配置文件中的内容
#include "config.h"
// 功能描述: 获取配置文件完整路径(包含文件名)
// 输入参数: pszConfigFileName-配置文件名
// pszWholePath-配置文件完整路径(包含文件名)
void GetCompletePath(UINT8 *pszConfigFileName, UINT8 *pszWholePath){
UINT8 *pszHomePath = NULL;
UINT8 szWholePath[256] = {0};
// 先对输入参数进行异常判断
if (pszConfigFileName == NULL || pszWholePath == NULL){
printf("GetCompletePath: input parameter(s) is NULL!\n");
return;
}
pszHomePath = (UINT8 *)getenv("HOME"); // 获取当前用户所在的路径
if (pszHomePath == NULL){
printf("GetCompletePath: Can't find home path!\n");
return;
}
// 拼装配置文件路径
snprintf(szWholePath, sizeof(szWholePath)-1, "%s", pszConfigFileName);
strncpy(pszWholePath, szWholePath, strlen(szWholePath));
}
// 功能描述: 获取具体的字符串值
// 输入参数: fp-配置文件指针
// pszSectionName-段名, 如: GENERAL
// pszKeyName-配置项名, 如: EmployeeName
// iOutputLen-输出缓存长度
// 输出参数: pszOutput-输出缓存
void GetStringContentValue(FILE *fp, UINT8 *pszSectionName, UINT8 *pszKeyName, UINT8 *pszOutput, UINT32 iOutputLen){
UINT8 szSectionName[100] = {0};
UINT8 szKeyName[100] = {0};
UINT8 szContentLine[256] = {0};
UINT8 szContentLineBak[256] = {0};
UINT32 iContentLineLen = 0;
UINT32 iPositionFlag = 0;
// 先对输入参数进行异常判断
if (fp == NULL || pszSectionName == NULL || pszKeyName == NULL || pszOutput == NULL){
printf("GetStringContentValue: input parameter(s) is NULL!\n");
return;
}
sprintf(szSectionName, "[%s]", pszSectionName);
strcpy(szKeyName, pszKeyName);
while (feof(fp) == 0){
memset(szContentLine, 0x00, sizeof(szContentLine));
fgets(szContentLine, sizeof(szContentLine), fp); // 获取段名
// 判断是否是注释行(以;开头的行就是注释行)或以其他特殊字符开头的行
if (szContentLine[0] == ';' || szContentLine[0] == '\r' || szContentLine[0] == '\n' || szContentLine[0] == '\0'){
continue;
}
// 匹配段名
if (strncasecmp(szSectionName, szContentLine, strlen(szSectionName)) == 0) {
while (feof(fp) == 0){
memset(szContentLine, 0x00, sizeof(szContentLine));
memset(szContentLineBak, 0x00, sizeof(szContentLineBak));
fgets(szContentLine, sizeof(szContentLine), fp); // 获取字段值
// 判断是否是注释行(以;开头的行就是注释行)
if (szContentLine[0] == ';'){
continue;
}
memcpy(szContentLineBak, szContentLine, strlen(szContentLine));
// 匹配配置项名
if (strncasecmp(szKeyName, szContentLineBak, strlen(szKeyName)) == 0) {
iContentLineLen = strlen(szContentLine);
for (iPositionFlag = strlen(szKeyName); iPositionFlag <= iContentLineLen; iPositionFlag ++){
if (szContentLine[iPositionFlag] == ' '){
continue;
}
if (szContentLine[iPositionFlag] == '='){
break;
}
iPositionFlag = iContentLineLen + 1;
break;
}
iPositionFlag = iPositionFlag + 1; // 跳过=的位置
if (iPositionFlag > iContentLineLen){
continue;
}
memset(szContentLine, 0x00, sizeof(szContentLine));
strcpy(szContentLine, szContentLineBak + iPositionFlag);
// 去掉内容中的无关字符
for (iPositionFlag = 0; iPositionFlag < strlen(szContentLine); iPositionFlag ++){
if (szContentLine[iPositionFlag] == '\r' || szContentLine[iPositionFlag] == '\n' || szContentLine[iPositionFlag] == '\0'){
szContentLine[iPositionFlag] = '\0';
break;
}
}
// 将配置项内容拷贝到输出缓存中
strncpy(pszOutput, szContentLine, iOutputLen-1);
break;
}
else if (szContentLine[0] == '['){
break;
}
}
break;
}
}
}
// 功能描述: 从配置文件中获取字符串
// 输入参数: pszSectionName-段名, 如: GENERAL
// pszKeyName-配置项名, 如: EmployeeName
// pDefaultVal-默认值
// iOutputLen-输出缓存长度
// pszConfigFileName-配置文件名
// 输出参数: pszOutput-输出缓存
void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8 *pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8 *pszConfigFileName){
FILE *fp = NULL;
UINT8 szWholePath[256] = {0};
// 先对输入参数进行异常判断
if (pszSectionName == NULL || pszKeyName == NULL || pszOutput == NULL || pszConfigFileName == NULL){
printf("GetConfigFileStringValue: input parameter(s) is NULL!\n");
return;
}
// 获取默认值
if (pDefaultVal == NULL){
strcpy(pszOutput, "");
}
else{
strcpy(pszOutput, pDefaultVal);
}
// 打开配置文件
GetCompletePath(pszConfigFileName, szWholePath);
fp = fopen(szWholePath, "r");
if (fp == NULL){
printf("GetConfigFileStringValue: open %s failed!\n", szWholePath);
return;
}
// 调用函数用于获取具体配置项的值
GetStringContentValue(fp, pszSectionName, pszKeyName, pszOutput, iOutputLen);
// 关闭文件
fclose(fp);
fp = NULL;
}
// 功能描述: 从配置文件中获取整型变量
// 输入参数: pszSectionName-段名, 如: GENERAL
// pszKeyName-配置项名, 如: EmployeeName
// iDefaultVal-默认值
// pszConfigFileName-配置文件名
// 输出参数: 无
// 返 回 值: iGetValue-获取到的整数值 -1-获取失败
INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8 *pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName){
UINT8 szGetValue[512] = {0};
INT32 iGetValue = 0;
// 先对输入参数进行异常判断
if (pszSectionName == NULL || pszKeyName == NULL || pszConfigFileName == NULL){
printf("GetConfigFileIntValue: input parameter(s) is NULL!\n");
return -1;
}
GetConfigFileStringValue(pszSectionName, pszKeyName, NULL, szGetValue, 512-1, pszConfigFileName); // 先将获取的值存放在字符型缓存中
if (szGetValue[0] == '\0' || szGetValue[0] == ';'){ // 如果是结束符或分号, 则使用默认值
iGetValue = iDefaultVal;
}
else{
iGetValue = atoi(szGetValue);
}
return iGetValue;
}
heart_client.c //客户client发送心跳程序
#include "head.h"
#include "heart.h"
void *send_heart(void *addr){
int* client_sockfd = (int*)addr;
printf("client_socket: %d\n", *client_sockfd);
pd = (DATA_PACK *)malloc(sizeof(DATA_PACK));
strcpy(pd->name, "127.0.0.1");
while(1){
//write(client_sockfd,pd,sizeof(DATA_PACK));
send(*client_sockfd,pd,sizeof(*pd),0);
sleep(3); //定时3秒
}
free(client_sockfd);
free(pd);
return NULL;
}
WriteSysLog.c //实现书写日志
#include "head.h"
#include "WriteSysLog.h"
void WriteSysLog(char *str){
char buf[512],name[20]="./syslog.log_0",s[10];
long MAXLEN = 10*1024*1024;//10MB
time_t timep;
FILE *fp = NULL;
struct tm *p;
time(&timep);
p = localtime(&timep);
memset(buf,0,sizeof(buf));
sprintf(buf,"%d-%d-%d %d:%d:%d : ",(1900+p->tm_year),(1+p->tm_mon),p->tm_mday,p->tm_hour, p->tm_min, p->tm_sec);
//星期p->tm_wday
strcat(buf,str);
strcat(buf,"\r\n");
fp = fopen(name,"r");
if(fp==NULL){
fp = fopen(name,"w+");
}
else{
fseek(fp,0,2);
if(ftell(fp) >= MAXLEN){
fclose(fp);
xx++;
int len,j,k;
len=strlen(name);
for(j=0;j<len;j++){
if(name[j]=='_')
k=j;
}
for(j=k+1;j<len;j++){
name[j]='\0';
}
sprintf(s,"%d",xx);
strcat(name,s);
memset(s,'\0',sizeof(s));
fp = fopen(name,"w+");
//大于10MB则产生新日志文件
}
else{
fclose(fp);
fp = fopen(name,"a");
}
}
fwrite(buf,1,strlen(buf),fp);
fflush(fp);
fsync(fileno(fp));
fclose(fp);
}
addfd.c //往epoll中添加套接字
#include "head.h"
#include "addfd.h"
void addfd(int epollfd, int fd, int flag){
struct epoll_event event;
memset(&event, 0x00, sizeof(event));
event.data.fd = fd;
event.events = EPOLLIN;
if(flag){
event.events |= EPOLLET;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
et.c //实现数据的接收和发送
#include "head.h"
#include "et.h"
#include "heart.h"
extern s_t *s_head;
extern volatile g_stop;
#define MAX_LEN 1024
void *et(void *arg){
pth_etlt *pth_arg = (pth_etlt *)arg;
struct epoll_event* events = pth_arg->events;
int number = pth_arg->number;
int epollfd = pth_arg->epollfd;
int listenfd = pth_arg->listenfd;
int i;
DATA_PACK *data = (DATA_PACK *)malloc(sizeof(DATA_PACK));
int p = 0;
DATA_PACK *num=(DATA_PACK *)malloc(sizeof(DATA_PACK));
char *buf=(char *)malloc(sizeof(*num));
int len=sizeof(*num);
for(i = 0; i < number; i++){
int sockfd = events[i].data.fd;
struct sockaddr_in client_address;
if(sockfd == listenfd){
socklen_t client_addresslen = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addresslen);
addfd(epollfd, connfd, 1);
printf("client ip: %s port: %d\n", inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
s_t *p = (s_t *)malloc(sizeof(s_t)), *q;
strcpy(p->peerip, inet_ntoa(client_address.sin_addr));
strcpy(p->name, inet_ntoa(client_address.sin_addr));
p->sockfd = sockfd;
p->count = 0;
q = s_head->next;
s_head->next = p;
p->next = q;
}
else if(events[i].events & EPOLLIN){
// printf("ET once\r\n");
while(g_stop){
//memset(data, 0x00, sizeof(*data));
int ret = recv(sockfd, data, MAX_LEN, 0);
if(ret < 0){
if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
//printf("read later \r\n");
break;
}
close(sockfd);
break;
}
else if(ret == 0){
close(sockfd);
}
else{
char s[128];
heart_handler(sockfd,data);
sprintf(s,"%s 收到了数据:%d %d",data->name,data->num1,data->num2);
WriteSysLog(s);
if(data->data_type==8){
if(data->num1==0x3f3f3f3f||data->num2==0x3f3f3f3f){
sprintf(s,"%s 收到的数据个数不对",data->name);
WriteSysLog(s);
num->data_type=4;
strcpy(num->name,"127.0.0.1");
send(sockfd,buf,len,0);
break;
}
memset(s,'\0',sizeof(s));
//sprintf(s,"%s 收到了数据:%d %d",data->name,data->num1,data->num2);
//WriteSysLog(s);
strcpy(num->name,"127.0.0.1");
num->data_type=8;
num->num1=data->num1 + data->num2;
memcpy(buf,num,len);
send(sockfd,buf,len,0);
memset(s,'\0',sizeof(s));
sprintf(s,"向 %s 发送的数据为:%d",data->name,num->num1);
WriteSysLog(s);
}
}
}
}
else{
printf("做了另外的事情\r\n");
}
}
pthread_exit((void *)data);
}
heart.c //实现心跳检测
#include "head.h"
#include "heart.h"
extern s_t *s_head;
extern volatile g_stop;
void init_shead(){
s_head = (s_t *)malloc(sizeof(s_t));
}
void heart_handler(int sockfd,DATA_PACK *pd){
s_t *cur = s_head->next;
while( NULL != cur){
if(strcmp(cur->name,pd->name) == 0){
cur->count = 0;
printf("客户端IP: %s :用户 %s 连接正常\n",cur->peerip,pd->name);
}
cur = cur->next;
}
}
void *heart_check(void *p)
{
printf("心跳检测线程已开启!\n");
while(g_stop){
s_t *temp = NULL;
s_t **ppNode = &s_head->next;
while(NULL != (*ppNode) && g_stop){
if((*ppNode)->count == 5){
g_stop = 0;
printf("客户端IP: %s :用户 %s 已经掉线!!\n",(*ppNode)->peerip,(*ppNode)->name);
close((*ppNode)->sockfd);
temp = *ppNode;
*ppNode = (*ppNode)->next;
free(temp);
temp = NULL;
return;
}
else if((*ppNode)->count > 0){
printf("客户端IP: %s :用户 %s 连接异常!\n",(*ppNode)->peerip,(*ppNode)->name);
(*ppNode)->count++;
printf("count = %d\n",(*ppNode)->count);
ppNode = &((*ppNode)->next);
continue;
}
else if((*ppNode)->count == 0){
(*ppNode)->count++;
printf("count = %d\n",(*ppNode)->count);
ppNode = &((*ppNode)->next);
}
else;
}
sleep(3);
}
pthread_exit((void *)1);
}
lt.c //实现监听检测
#include "head.h"
#include "lt.h"
void lt(struct epoll_event* events, int number, int epollfd, int listenfd){
char buf[BUF_SIZE];
int i;
for(i = 0; i < number; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in client_address;
socklen_t client_addresslen = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addresslen);
if(connfd < 0){
printf("接受失败\r\n");
exit(1);
}
addfd(epollfd, connfd, 0);
}
else if(events[i].events & EPOLLIN){
printf("LT once\r\n");
memset(buf, 0x00, sizeof(buf));
int ret = recv(sockfd, buf, sizeof(buf)-1, 0);
if(ret <= 0){
printf("rec 0\r\n");
close(sockfd);
continue;
}
printf("recv data from %d buf is %s\r\n", sockfd, buf);
}
else{
printf("做了另外的一些事\r\n");
}
}
}
server.c //服务器程序
#include "head.h"
#include "lt.h"
#include "et.h"
#include "addfd.h"
#include "heart.h"
s_t *s_head = NULL;
volatile g_stop=1;
int main(int argc, char **argv){
if(argc <= 2){
printf("请重新运行并在./server 后输入ip地址(127.0.0.1)和端口号(8867)\r\n");
exit(1);
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
struct epoll_event events[MAX_SOCKET_NUMBERS];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd,listenfd, 1);
int err1;
pthread_t pth1;
init_shead();
if( (err1 = pthread_create(&pth1, NULL, heart_check, (void *)0) ) != 0){
puts("--------------------");
fprintf(stderr, "pthread_creat : %s\n", strerror(err1));
exit(1);
}
DATA_PACK *num;
while(g_stop){
int ret = epoll_wait(epollfd, events, MAX_SOCKET_NUMBERS, -1);
if(ret < 0){
printf("epoll等候失败\r\n");
exit(1);
}
int err;
pthread_t pth;
pth_etlt arg = {events, ret, epollfd, listenfd};
if( (err = pthread_create(&pth, NULL, et, (void *)&arg) ) != 0){
fprintf(stderr, "pthread_creat : %s\n", strerror(err));
exit(1);
}
pthread_join(pth, (void**)&num);
}
close(listenfd);
return 0;
}
setnonblocking.c //实现将套接字转换为非阻塞
#include "head.h"
#include "setnonblocking.h"
int setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return new_option;
}
Config.ini //配置文件,存放数据
[IPANDPORT]
;the value of IP
IP=127.0.0.1
;the value of port
PORT=8867
[TWONUMBER]
;the value of number1
NUMBER1=1052
;the value of number2
NUMBER2=568