基本功能:
客戶端發送數據到服務器,數據包括髮送內容和當前時間,服務器將接收到的數據廣播到在線的每個客戶端,這樣就像大家在同一個羣裏面聊天一樣.
類似騰訊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文件中).