网络通信聊天程序(群聊)



基本功能:

客户端发送数据到服务器,数据包括发送内容和当前时间,服务器将接收到的数据广播到在线的每个客户端,这样就像大家在同一个群里面聊天一样.

类似腾讯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文件中).




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