網路通訊聊天程式(群聊)
阿新 • • 發佈:2018-12-31
基本功能:
客戶端傳送資料到伺服器,資料包括髮送內容和當前時間,伺服器將接收到的資料廣播到線上的每個客戶端,這樣就像大家在同一個群裡面聊天一樣.
類似騰訊QQ的群聊功能,能做到多個線上客戶端的同步顯示,不過沒有圖形介面,等後面有時間了把QT好好學一下,再做個圖形介面.爭取最後實現
點對點通訊。
客戶端程式碼:
/************************客戶端************************** * function: 客戶端連線上伺服器之後,建立一個子程序和父程序, * 父程序父子傳送資料到客戶端,子程序負責接收伺服器廣播 * 來的資料,父程序和子程序都是死迴圈執行,直到客戶端 * 主動斷開連線為止. * * 客戶端在會記錄每次傳送資料對應的時間,時間會隨著對應的 * 訊息傳給伺服器. * * author: LIUPENG * * email:
[email protected] * * date: 2015/06/05 01:00 * * ps: 程式程式碼只能用作參考,不得用作商業用途. * **************************************************/ #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <time.h> #define MAX_CONTENT_LEN (530) #define MAX_TIME_LEN (30) int main(int argc, char *argv[])//argv攜帶伺服器的IP(argv[1])和PORT(argv[2]) { if(argc != 3) { printf("client: 命令列引數格式錯誤(執行的命令+伺服器IP+伺服器埠號)!\n"); exit(-1); } int sockFd = 0;//檔案描述符 sockFd = socket(AF_INET, SOCK_STREAM, 0); if(-1 == sockFd) { printf("client: socket fail!\n"); close(sockFd); //exit(x):x!=0時表示異常退出,1返回給作業系統;exit(0)表示正常退出. exit(-1);//用到標頭檔案stdlib.h } struct sockaddr_in serAddr;//用到標頭檔案netinet/in.h bzero(&serAddr, sizeof(serAddr));//用到標頭檔案string.h serAddr.sin_family = AF_INET; serAddr.sin_port = htons(atoi(argv[2]));//用到標頭檔案netinet/in.h inet_pton(AF_INET, argv[1], &serAddr.sin_addr);//用到了arpa/inet.h if(connect(sockFd, (struct sockaddr*)&serAddr, sizeof(serAddr)) != 0)//用到標頭檔案sys/socket.h和sys/types.h { printf("client: connect fail!"); close(sockFd); exit(-1); } pid_t pid = fork(); if(pid > 0)//父程序 { time_t now;//time_t結構體 struct tm * timeNow;//tm結構體 char contentBuf[MAX_CONTENT_LEN] = {0}; char timeBuf[MAX_TIME_LEN] = {0}; char sendBuf[MAX_CONTENT_LEN+MAX_TIME_LEN] = {0}; while(1) { memset(contentBuf, 0, sizeof(contentBuf)); memset(timeBuf, 0, sizeof(timeBuf)); memset(sendBuf, 0, sizeof(sendBuf)); fgets(contentBuf, sizeof(contentBuf), stdin);//從標準輸入裝置上輸入 strcat(contentBuf, "\n"); time(&now);//獲取系統當前的時間 timeNow = localtime(&now);//將系統時間轉化為電腦本地的時間 //strcat(sendBuf, asctime(timeNow));//asctime是將時間轉成字串形式 //標準時間是從1900年1月1日開始計算的,所以要加上對應的時間. sprintf(timeBuf, "[%04d-%02d-%02d:%02d-%02d-%02d]:\n", timeNow->tm_year+1900, timeNow->tm_mon+1, timeNow->tm_mday+1, timeNow->tm_hour, timeNow->tm_min, timeNow->tm_sec); strcat(sendBuf, timeBuf); strcat(sendBuf, contentBuf); //send會阻塞到整個資料被傳輸完畢 if(-1 == send(sockFd, sendBuf, strlen(sendBuf)+1, 0))//用到標頭檔案sys/socket.h和sys/types.h { printf("client: send content fail!\n"); close(sockFd); exit(-1); } } } if(0 == pid)//子程序 { while(1) { char recvBuf[MAX_CONTENT_LEN] = {0}; //recv會阻塞到整個緩衝區滿或接受完. int bufLen = recv(sockFd, recvBuf, sizeof(recvBuf), 0);//用到標頭檔案sys/socket.h和sys/types.h if( bufLen < 0) { printf("client: recv fail!\n"); close(sockFd); exit(-1); } else if(0 == bufLen) { printf("client: server sockFd is disconnect!\n"); close(sockFd); exit(-1); } printf("%s\n\n", recvBuf); } } close(sockFd); return 0; } /* 一定要記得關閉套接字,不然會出現各種問題!!! */ #if 0 格式化輸出,%04d表示按4為寬度輸出,不足4位則在最前面補0; %4d表示按4位輸出,右對齊方式,但是不會補齊. #endif
伺服器程式碼:
/***********************伺服器*********************** * 實現的功能: * 伺服器接收各個客戶端發來的資料,將資料廣播到其他 * 跟伺服器建立了連線的客戶端中,類似騰訊QQ中的群聊. * 使用vector管理socket和連線伺服器的客戶端套接字. * *伺服器會將每個客戶端發來的資料和對應的時間記錄在程式 * 當前目錄下的日誌log.txt中. * *author: LIUPENG * * email: [email protected] * * ps: 本程式只適用於參考,不得用作商業用途. * * date:2015/06/05 01:00 * * **********************************************/ #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <unistd.h> #include "diary.h" #define MAX_RECV_LEN (560) #define MAX_LOG_LEN (570) #define LOG_PATH "./log.txt" /*C++中定義的vector*/ #include <vector>//包含此標頭檔案,則必須使用g++編譯. using namespace std; int removeValue(vector<int> &vec, int removeValue) { int index = 0; vector<int>::iterator iter = vec.begin(); printf("enter for erase!\n"); for(;iter != vec.end();iter++) { if(removeValue == *iter) { vec.erase(iter); return 1;//1 表示刪除成功 } } return 0; } void setSelect(vector<int> &vec, fd_set &fdSet) { int index = 0; for(index = 0; index < vec.size(); index++) { FD_SET(vec.at(index), &fdSet); } } void countMaxFd(vector<int> &vec, int &maxFd) { int size = vec.size(); int index = 0; maxFd = vec[0]; for(index = 0; index < vec.size(); index++) { maxFd = maxFd > vec.at(index) ? maxFd : vec.at(index); } } int main(int argc, char *argv[]) { if(argc != 2) { printf("server: 命令列引數個數錯誤(執行的命令+伺服器埠號)!\n"); exit(-1); } int sockFd = 0; int conFd = 0; vector<int> fdAll;//記錄已經建立的socket套接字和連線的套接字. sockFd = socket(AF_INET, SOCK_STREAM, 0);//用到標頭檔案sys/socket.h if(-1 == sockFd) { printf("server: socket fail!\n"); close(sockFd); exit(-1); } struct sockaddr_in serAddr;//用到netinnet/in.h struct sockaddr_in cliAddr; bzero(&serAddr, sizeof(serAddr));//用到string.h serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8080);//用到了netinet/in.h serAddr.sin_addr.s_addr = htonl(INADDR_ANY);//用到了netinnet/in.h if(-1 == bind(sockFd, (struct sockaddr*)&serAddr, sizeof(serAddr)))//用到了sys/socket.h和sys/types.h { printf("server: bind fail!\n"); close(sockFd); exit(-1);//用到stdlib.h } if(-1 == listen(sockFd, 10))//用到了sys/types.h和sys/socket.h { printf("server: listen fail!\n"); close(sockFd); exit(-1); } int index = 0; int maxFd = 0; fd_set fdSelect; FD_ZERO(&fdSelect); FD_SET(sockFd, &fdSelect); fdAll.push_back(sockFd);//sockFd會一直是fdAll的第一個元素. maxFd = fdAll[0]; while(1) { printf("maxFd = %d\n", maxFd); setSelect(fdAll, fdSelect); //select每執行一次,加入其內的套接字會被監聽,監聽結束的時候, //事件發生的套接字被置為1,未發生的被置為0.所以在每次迴圈開始前, //需要將socket和connect套接字加入fdSelect中. int ret = select(maxFd+1, &fdSelect, NULL, NULL, NULL); if(ret < 0) { printf("server: select fail!\n"); close(sockFd); exit(-1); } else if(0 == ret) { printf("server: timeout!\n"); close(sockFd); exit(-1); } //程式走到這裡,分兩種情況:1.有新的客戶端請求連線,則sockFd可讀; // 2.已經連上的客戶端在傳送資料,則對應的fdAll[index]可讀. //第一種情況 if(FD_ISSET(sockFd, &fdSelect)) { bzero(&cliAddr, sizeof(cliAddr)); socklen_t cliAddrLen = sizeof(cliAddr); //注意此函式的第三個引數型別為socklen_t. conFd = accept(sockFd, (struct sockaddr*)&cliAddr, &cliAddrLen);//用到sys/socket.h和sys/types.h if(conFd < 0) { printf("server: accept fail!\n"); close(sockFd); exit(-1); } fdAll.push_back(conFd); maxFd = maxFd > conFd ? maxFd : conFd; FD_SET(conFd, &fdSelect); } //第二種情況 char recvBuf[MAX_RECV_LEN] = {0}; for(index = 1; index < fdAll.size(); index++) { if(FD_ISSET(fdAll[index], &fdSelect)) { memset(recvBuf, 0, sizeof(recvBuf)); int recvLen = 0; recvLen = recv(fdAll[index], recvBuf, sizeof(recvBuf), 0);//用到sys/socket.h和sys/types.h if(recvLen < 0) { printf("server: recv failed!\n"); close(fdAll[index]); FD_CLR(fdAll[index], &fdSelect); if(1 != removeValue(fdAll, fdAll[index])) { printf("removeValue fail on [%s]:[%d]!\n", __FILE__, __LINE__); } exit(-1); } else if(0 == recvLen)//表示客戶端斷開了 { printf("server: the client--[%d ] disconnected!\n", fdAll[index]); close(fdAll[index]); FD_CLR(fdAll[index], &fdSelect); if(1 != removeValue(fdAll, fdAll[index])) { printf("removeValue fail on [%s]:[%d]!\n", __FILE__, __LINE__); exit(-1); } countMaxFd(fdAll, maxFd); continue;//連線的套接字斷開後,不需要執行後續的send. } printf("server:recv [%d]: %s\n\n", fdAll[index], recvBuf); char buf[MAX_LOG_LEN] = {0}; sprintf(buf, "[%d]--", fdAll[index]); strcat(buf, recvBuf); writeLog((char *)LOG_PATH, buf); char sendBuf[MAX_RECV_LEN] = {0}; int indexCycle = 0; sprintf(sendBuf, "client[%d]: ", index); strncat(sendBuf, recvBuf, strlen(recvBuf)); for(indexCycle = 1; (indexCycle < fdAll.size()); indexCycle++) { if(index != indexCycle) { if(-1 == send(fdAll[indexCycle], sendBuf, strlen(sendBuf), 0))//用到sys/socket.h和sys/types.h { printf("server: send to client--[%d] fail!\n", fdAll[indexCycle]); /*伺服器傳送失敗可能是在傳送時,客戶端剛好斷開,所以不能關閉sock套接字.*/ } } } }//end if }//end for(第二種情況) }//end while(1) close(sockFd); return 0; }
日誌記錄程式碼:
/*****************************************
* function: 記錄各個客戶端傳送的資料,但是不記錄伺服器
* 廣播出來的資料,因為這會和客戶端的資料重複.
*
* author: LIUPENG
*
* date: 2015/06/07
*
* ps: 此程式程式碼只能用於參考,不得用作商業用途.
****************************************/
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void writeLog(char *logPath, char *logContent)
{
int fd = open(logPath, O_CREAT|O_RDWR|O_APPEND, 0700);
if(-1 == fd)
{
printf("open fail on [%s]:[%d]!\n", __FILE__, __LINE__);
exit(-1);
}
if(write(fd, logContent, strlen(logContent)) < 0)
{
printf("write log fail on [%s]:[%d]!\n", __FILE__, __LINE__);
exit(-1);
}
}
伺服器端執行:./IOServer 8080;
在任意客戶端執行:./client 127.0.0.1 8080;
這樣就能通訊了,在程式當前目錄下可以檢視通訊內容(在log.txt檔案中).