简介:简单的聊天室,实现注册,群发消息,私聊消息的功能;
这是一个简单的多个客户端和服务器端的通信;
大体的是客户端创建连接服务器端的套接字,连接到服务器;
连接上之后就是调用功能函数,功能函数中写个switch(),对功能进行选择;
这些功能的实现基本都是客户端消息写给服务器端,在从服务器端读消息(一个客户端怎么在发消息的同时又读消息呢,所以进行读写分离,在一个客户端注册完成之后就开辟一个线程);
**消息的发送不管是什么都一个结构体全部发送过去;
**客户端代码:**
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 9999
char myName[20];
struct Msg
{
char msg[1024]; // 消息内容
char toname[20];
char fromname[20];
int cmd; // 消息类型
};
// 界面
void interface()
{
printf ("1、注册\n");
printf ("2、聊天\n");
printf ("3、私聊\n");
}
void * readMsg(void *v)
{
int socketfd = (int)v;
struct Msg msg;
while (1)
{
read (socketfd, &msg, sizeof(msg));
switch (msg.cmd)
{
case 2: // 群聊
printf ("收到一条消息: %s\n", msg.msg);
break;
case 3: // 私聊
printf ("%s 给你发一条消息:%s\n", msg.fromname, msg.msg);
break;
}
}
}
void reg(int socketfd)
{
struct Msg msg;
msg.cmd = 1;
printf ("请输入用户名:\n");
fgets(myName, 20, stdin);
strcpy (msg.msg, myName);
write (socketfd, &msg, sizeof(msg));
read (socketfd, &msg, sizeof(msg));
if (msg.cmd == 1001)
{
printf ("注册成功\n");
}
else if (msg.cmd == -1)
{
printf ("注册失败\n");
}
sleep(1);
pthread_t id;
pthread_create(&id, NULL, readMsg, (void *)socketfd);
pthread_detach(id); // 线程分离
}
void chat(int socketfd)
{
struct Msg msg;
msg.cmd = 2;
printf ("请输入要发送的内容: \n");
fgets(msg.msg, 1024, stdin);
write (socketfd, &msg, sizeof(msg));
}
void chat2(int socketfd)
{
struct Msg msg;
msg.cmd = 3;
printf ("请输入要发送的对象名称: \n");
fgets(msg.toname, 20, stdin);
printf ("请输入要发送的内容: \n");
fgets(msg.msg, 1024, stdin);
strcpy (msg.fromname, myName);
write (socketfd, &msg, sizeof(msg));
}
// 客户端向服务器发送数据
void ask_server(int socketfd)
{
char ch[2];
while (1)
{
interface();
fgets(ch, 2, stdin);
while(getchar()!= '\n');
switch(ch[0])
{
case '1': // 注册
reg(socketfd);
break;
case '2': // 聊天
chat(socketfd);
break;
case '3': // 私聊
chat2(socketfd);
break;
}
system("clear");
}
}
int main()
{
// 创建与服务器通信的套接字
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror ("socket");
return -1;
}
// 连接服务器
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // 设置地址族
addr.sin_port = htons(PORT); // 设置本地端口
inet_aton("127.0.0.1",&(addr.sin_addr));
// 连接服务器,如果成功,返回0,如果失败,返回-1
// 成功的情况下,可以通过socketfd与服务器进行通信
int ret = connect(socketfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("connect");
return -1;
}
printf ("成功连上服务器\n");
ask_server(socketfd);
// 关闭套接字
close(socketfd);
return 0;
}
服务器端代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 9999
struct User
{
char name[20]; // 用户名
int socket; // 和用户通信的socket
};
// 协议
struct Msg
{
char msg[1024]; // 消息内容
char toname[20];
char fromname[20];
int cmd; // 消息类型
};
struct User user[20];
// 初始化套接字,返回监听套接字
int init_socket()
{
//1、创建socket
int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1)
{
perror ("socket");
return -1;
}
// 2、命名套接字,绑定本地的ip地址和端口
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; // 设置地址族
addr.sin_port = htons(PORT); // 设置本地端口
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用本地的任意IP地址
int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("bind");
return -1;
}
// 3、监听本地套接字
ret = listen(listen_socket, 5);
if (ret == -1)
{
perror ("listen");
return -1;
}
printf ("等待客户端连接.......\n");
return listen_socket;
}
// 处理客户端连接,返回与连接上的客户端通信的套接字
int MyAccept(int listen_socket)
{
// 4、接收连接
// 监听套接字不能用来与客户端进行通信,它的职责是监听客户端的连接
// accpet 处理客户端的连接,如果成功接收,会返回一个新的套接字,用来与客户端进行通信
// accept的第三个参数 是一个传入传出参数
struct sockaddr_in client_addr; // 用来保存客户端的ip和端口信息
int len = sizeof(client_addr);
int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &len);
if (client_socket == -1)
{
perror ("accept");
}
printf ("成功接收一个客户端: %s\n", inet_ntoa(client_addr.sin_addr));
return client_socket;
}
void reg(int client_socket, struct Msg *msg)
{
printf ("%s 进行注册\n", msg->msg); //客户端的整个结构体都发过来了,在服务器端的功能 函数读取之后也是读取的整个结构体(客户端,服务器端的结构体格式要一模一样);
// 将用户进行保存
int i = 0;
for (i = 0; i < 20; i++)
{
if (user[i].socket == 0) //收到客户端注册要求,就保存发送消息的客户端的套接字;
{
strcpy (user[i].name , msg->msg);
user[i].socket = client_socket;
break;
}
}
if (i == 20)
{
msg->cmd = -1;
}
else
{
msg->cmd = 1001; //注意,注册的时候你服务器端得要返回一个消息给客户端来告诉他你的注册成功了;
}
write (client_socket, msg, sizeof(struct Msg));
}
// 群发
void chat(int client_socket, struct Msg *msg)
{
printf ("%s 发一次群消息\n", msg->msg);
// 筛选可以收消息的用户;
int i = 0;
for (i = 0; i < 20; i++)
{
if (user[i].socket != 0)
{
write (user[i].socket, msg, sizeof(struct Msg));
}
}
}
// 私聊
void chat2(int client_socket, struct Msg *msg)
{
printf ("%s 要 给 %s 发一条消息\n", msg->fromname, msg->toname);
// 查找用户
int i = 0;
for (i = 0; i < 20; i++)
{
if (user[i].socket != 0 && strcmp(user[i].name, msg->toname)==0)
{
write (user[i].socket, msg, sizeof(struct Msg));
break;
}
}
}
// 把 负责处理客户端通信的函数改成线程的工作函数
void* hanld_client(void* v)
{
int client_socket = (int)v;
struct Msg msg;
while(1)
{
// 从客户端读一个结构体数据
int ret = read(client_socket, &msg, sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}
// 代表客户端退出
if (ret == 0)
{
printf ("客户端退出\n");
break;
}
switch (msg.cmd)
{
case 1 : // 客户但进行注册
reg(client_socket, &msg);
break;
case 2 : // 客户端进行聊天
chat(client_socket, &msg);
break;
case 3 : // 客户端进行私聊
chat2(client_socket, &msg);
break;
}
}
close (client_socket);
}
int main()
{
// 初始化套接字
int listen_socket = init_socket();
while (1)
{
// 获取与客户端连接的套接字
int client_socket = MyAccept(listen_socket);
// 创建一个线程去处理客户端的请求,主线程依然负责监听
pthread_t id;
pthread_create(&id, NULL, hanld_client, (void *)client_socket);
pthread_detach(id); // 线程分离
}
close (listen_socket);
return 0;
}