非阻塞套接字和epoll

要求:

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


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章