网络聊天室
服务器代码
#include <iohead.h>
#define SER_PORT 8888 // 服务器端口号
#define SER_IP "192.168.78.128" // 服务器IP地址
#define MAX_CLIENTS 1024 // 最大客户端数量
#define USERNAME_SIZE 20 // 用户名最大长度
// 客户端信息结构体
typedef struct
{
int socket; // 套接字描述符
char username[USERNAME_SIZE]; // 用户名
struct sockaddr_in addr; // 客户端地址信息
int logged_in; // 登录状态标志
} ClientInfo;
ClientInfo clients[MAX_CLIENTS]; // 客户端信息数组
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; // 线程安全锁
// 广播消息给其他用户
void broadcast(const char* msg, int exclude_socket)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket != -1 && clients[i].logged_in && clients[i].socket != exclude_socket)
{
send(clients[i].socket, msg, strlen(msg), 0);
}
}
pthread_mutex_unlock(&clients_mutex);
}
int main(int argc, const char* argv[])
{
// 1. 创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket成功 sfd = %d\n", sfd);
// 设置套接字选项:允许端口号快速重用
int reuse = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
// 2. 绑定地址和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
if (bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
// 3. 监听连接
if (listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
// 4. 初始化客户端信息数组
for (int i = 0; i < MAX_CLIENTS; i++)
{
clients[i].socket = -1;
clients[i].logged_in = 0;
memset(clients[i].username, 0, USERNAME_SIZE);
}
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(sfd, &readfds);
FD_SET(0, &readfds);
int maxfd = sfd;
int new_fd;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while (1)
{
tempfds = readfds;
int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
if (res == -1)
{
perror("select error");
continue;
}
// 处理新客户端连接
if (FD_ISSET(sfd, &tempfds))
{
new_fd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if (new_fd == -1)
{
perror("accept error");
continue;
}
// 将新客户端添加到集合
FD_SET(new_fd, &readfds);
if (new_fd > maxfd) maxfd = new_fd;
// 初始化客户端信息
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket == -1)
{
clients[i].socket = new_fd;
clients[i].addr = cin;
clients[i].logged_in = 0; // 等待用户名
break;
}
}
pthread_mutex_unlock(&clients_mutex);
printf("客户端[%s:%d]连接, fd=%d\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), new_fd);
}
// 处理标准输入
if (FD_ISSET(0, &tempfds))
{
char wbuf[128];
scanf("%s", wbuf);
printf("服务器消息: %s\n", wbuf);
// 广播服务器消息
char server_msg[256];
snprintf(server_msg, sizeof(server_msg), "[服务器]: %s", wbuf);
broadcast(server_msg, -1);
}
// 处理客户端消息
for (int i = 0; i <= maxfd; i++)
{
if (FD_ISSET(i, &tempfds) && i != sfd && i != 0)
{
char rbuf[256];
int res = recv(i, rbuf, sizeof(rbuf) - 1, 0);
// 查找客户端信息
ClientInfo *current_client = NULL;
pthread_mutex_lock(&clients_mutex);
for (int j = 0; j < MAX_CLIENTS; j++)
{
if (clients[j].socket == i)
{
current_client = &clients[j];
break;
}
}
pthread_mutex_unlock(&clients_mutex);
if (!current_client)
{
close(i);
FD_CLR(i, &readfds);
continue;
}
if (res <= 0)
{
// 客户端断开
if (current_client->logged_in)
{
char leave_msg[256];
snprintf(leave_msg, sizeof(leave_msg), "【系统】%s 已下线\n", current_client->username);
broadcast(leave_msg, i); // 广播下线消息
}
pthread_mutex_lock(&clients_mutex);
printf("客户端 %s:%d (fd=%d) 断开连接\n",
inet_ntoa(current_client->addr.sin_addr),
ntohs(current_client->addr.sin_port), i);
close(i);
FD_CLR(i, &readfds);
// 清除客户端信息
current_client->socket = -1;
current_client->logged_in = 0;
memset(current_client->username, 0, USERNAME_SIZE);
// 更新maxfd
if (i == maxfd)
{
for (int j = maxfd; j >= 0; j--)
{
if (FD_ISSET(j, &readfds))
{
maxfd = j;
break;
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
else
{
// 正常消息
rbuf[res] = '\0'; // 确保字符串终止
if (!current_client->logged_in)
{
// 第一次连接,接收用户名
strncpy(current_client->username, rbuf, USERNAME_SIZE - 1);
current_client->username[USERNAME_SIZE - 1] = '\0';
current_client->logged_in = 1;
printf("用户 %s 登录 (fd=%d)\n", current_client->username, i);
// 发送欢迎消息给新用户
char welcome_msg[256];
snprintf(welcome_msg, sizeof(welcome_msg), "【系统】欢迎 %s! 您已成功登录\n", current_client->username);
send(i, welcome_msg, strlen(welcome_msg), 0);
// 广播上线消息给所有用户(包括自己)
char notice[256];
snprintf(notice, sizeof(notice), "【系统】%s 已上线\n", current_client->username);
broadcast(notice, -1);
}
else
{
// 格式化消息:用户名: 消息内容
char formatted_msg[512];
snprintf(formatted_msg, sizeof(formatted_msg), "%s: %s", current_client->username, rbuf);
printf("%s\n", formatted_msg);
// 广播给其他用户(不包括发送者)
broadcast(formatted_msg, i);
}
}
}
}
}
close(sfd);
return 0;
}
客户端代码
#include <iohead.h>
#define SER_PORT 8888 // 服务器端口号
#define SER_IP "192.168.78.128" // 服务器IP地址
#define BUFFER_SIZE 1024
int cfd;
char username[20]; // 存储用户名
// 接收服务器消息的线程函数
void *recv_thread(void *arg)
{
char rbuf[BUFFER_SIZE];
while (1)
{
memset(rbuf, 0, BUFFER_SIZE);
int res = recv(cfd, rbuf, BUFFER_SIZE, 0);
if (res <= 0)
{
printf("\n【系统】与服务器断开连接\n");
exit(1);
}
printf("%s", rbuf);
}
return NULL;
}
int main(int argc, const char*argv[])
{
// 1. 创建套接字
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == cfd)
{
perror("socket error");
return -1;
}
printf("socket success cfd = %d\n", cfd);
// 2. 连接服务器
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
if (connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("connect error");
return -1;
}
printf("连接服务器成功\n");
// 3. 输入用户名并发送给服务器
printf("请输入用户名: ");
fgets(username, sizeof(username), stdin);
username[strlen(username) - 1] = '\0';
send(cfd, username, strlen(username), 0);
// 4. 创建接收消息线程
pthread_t tid;
pthread_create(&tid, NULL, recv_thread, NULL);
pthread_detach(tid);
// 5. 主循环:发送消息
char wbuf[BUFFER_SIZE];
while (1)
{
fgets(wbuf, sizeof(wbuf), stdin);
wbuf[strlen(wbuf) - 1] = '\0'; // 去除换行符
if (strcmp(wbuf, "quit") == 0)
{
printf("系统已退出\n");
break;
}
send(cfd, wbuf, strlen(wbuf), 0);
}
// 6. 关闭套接字
close(cfd);
return 0;
}
运行结果