使用线程实现QQ聊天
server端代码:
#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
#include <pthread.h>
#include<vector>
#define SERVER_PORT 9527 //port
#define BACKLOG 10 //同时允许多少个客户端跟server建立连接
#define GROUP_CHAT 0 //群聊
#define PRIVATE_CHAT 1 //私聊
#define OFF_LINE 2 //下线
#define ON_LINE 3 //上线
using namespace std;
typedef struct //定义一个聊天结构体
{
int user_id; //用户id
int type;
char name[16]; //用户名
char message[200]; //消息内容
char to_the_other_one[20]; //要私聊的用户
}STU;
typedef struct
{
int accept_fd; //保存客户端的accept_fd
char user_name[16]; //姓名
char message[200]; //消息内容
}CLIENT_ACCEPT_FD;
vector<CLIENT_ACCEPT_FD > client_info;//容器中存放客户端信息
//线程处理函数
void* thread_fun(void* accept_fd)
{
int r_size;
size_t w_size;
int tmp_fd = *((int*)accept_fd);// accept_fd 传过来,用这个accept_fd 实现群发功能
STU user0;//声明用户1
CLIENT_ACCEPT_FD Client;//保存着accept_fd的聊天结构体
//对这两个结构体初始化
bzero(&user0, sizeof(user0));
bzero(&Client, sizeof(Client));
Client.accept_fd = tmp_fd;
client_info.push_back(Client);//把客户端信息加入容器中
//cout << "容器中的大小是" << client_info.size() << endl;
while (1)
{
//读客户端发过来的数据
r_size = read(tmp_fd, &user0, sizeof(user0)); //r_size >0 有读到数据 <0 错误 == 0 有客户端退出
if (r_size > 0)
{
//cout << "姓名:" << user0.name << endl;
//cout << "消息体:" << user0.message << endl;
strcpy(Client.user_name, user0.name);
strcpy(Client.message, user0.message);
cout << "姓名:" << Client.user_name << "消息" << Client.message << endl;
//cout << "服务器收到的类型是" << user0.type << endl;
if (user0.type == GROUP_CHAT) //是群聊
{
//遍历容器 回发消息给每个客户端
for (int i = 0; i < client_info.size(); i++)
{
w_size = write(client_info[i].accept_fd, &user0, sizeof(user0));
}
}
if (user0.type == PRIVATE_CHAT) //是私聊
{
for (int i = 0; i < client_info.size(); i++)
{
if (client_info[i].user_name == user0.to_the_other_one)
{
w_size = write(client_info[i].accept_fd, &user0, sizeof(user0));
break;
}
}
}
}
else if (r_size == 0)//客户端下线
{
vector<CLIENT_ACCEPT_FD>::iterator it;
cout << "没下线前的" << client_info.size() << endl;
for (it = client_info.begin(); it != client_info.end(); it++)
{
//找到下线用户的accept_fd 把他从容器中移除
if (it->accept_fd == tmp_fd)
{
user0.type = OFF_LINE;
client_info.erase(it);
break;
}
}
cout << "下线后的" << client_info.size() << endl;
for (int i = 0; i < client_info.size(); i++)
{
w_size = write(client_info[i].accept_fd, &user0, sizeof(user0));
}
return NULL;
}
else if (r_size < 0)
{
perror("read error");
return NULL;
}
}
}
int main()
{
int ret = 0;
int accept_fd;
pthread_t thread_id;
// creat socket
int socket_fd;//套接字描述符
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("socket error");
return -1;
}
// 设置端口复用
int opt = 1;//开启端口复用
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//bind
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0)
{
perror("bind error");
return -1;
}
//listen
ret = listen(socket_fd, BACKLOG);
while (1)
{
cout << "wait connect ..." << endl;
accept_fd = accept(socket_fd, NULL, NULL);
cout << "connected !!!" << endl;
if (accept_fd == -1)
{
perror("accept error");
return -1;
}
ret = pthread_create(&thread_id, NULL, thread_fun, &accept_fd);//创建线程
if (ret != 0)
{
perror("pthread_create error");
return -1;
}
}
return 0;
}
client 端代码:
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#define SERVER_PORT 9527
#define SERVER_IP "192.168.75.128"
#define GROUP_CHAT 0 //群聊
#define PRIVATE_CHAT 1 //私聊
#define OFF_LINE 2 //下线
#define ON_LINE 3 //上线
using namespace std;
typedef struct //定义一个聊天结构体
{
int user_id; //用户id
int type;
char name[16]; //用户名
char message[200]; //消息内容
char to_the_other_one[20]; //要私聊的用户
}STU;
int main()
{
//客户端代码
/*
思路:socket 读用专门的进程来实现。从客户端获取聊天内容用另一个进程去做
*/
pid_t pid;
int r_size = 0;
//socket
int client_fd;
int ret = 0;
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1)
{
perror("socket error");
return -1;
}
//connect
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_IP);
ret = connect(client_fd, (const struct sockaddr*) &addr,sizeof(addr));
//聊天结构体初始化
STU chat1;
char name[16] = { 0 };
char other_name[16] = { 0 };
char buffer[100] = { 0 };
int m_type;
bzero(&chat1,sizeof(chat1));
cout << "请输入你的姓名:" << endl;
cin >> name;
cout << "请输入聊天类型 0:群聊 1:私聊 " << endl;
cin >> m_type;
cout << "类型是" << m_type << endl;
if (m_type == 1)
{
cout << "请输入你要私聊的用户名" << endl;
cin >>other_name;
}
pid = fork();
if (pid > 0)//父进程
{
while (fgets(buffer, sizeof(buffer), stdin) != NULL)
{
strcpy(chat1.to_the_other_one, other_name);//对方名字
strcpy(chat1.name, name);
strcpy(chat1.message, buffer);//通过buffer 把消息传递到结构体的message中
chat1.type = m_type;//类型传递
write(client_fd, &chat1, sizeof(chat1));
memset(&chat1, 0, sizeof(chat1));
memset(buffer, 0, sizeof(buffer));
}
}
else if (pid == 0)//子进程
{
//读取数据
STU chat2;
bzero(&chat2, sizeof(chat2));
int tmp = 0;
while (r_size = (read(client_fd, &chat2, sizeof(chat2))) > 0)
{
if (strcmp(chat2.name, name) != 0)
{
cout << chat2.name << ": " << chat2.message << endl;
}
if (chat2.type == OFF_LINE)//下线
{
cout << chat2.name << "下线了" << endl;
}
}
}
return 0;
}
运行效果: