服务器端代码主要实现以下功能:
- 创建并绑定一个套接字,监听来自客户端的连接请求。
- 获取来自客户端的消息,并将该消息广播给所有已连接的客户端。
- 利用互斥量保护共享资源,防止多个线程同时修改此资源导致数据竞争问题。
- 创建一个独立的线程处理每个已连接的客户端,以便在服务器并发处理多个客户端请求时能够提高效率。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_CLIENTS 100
#define MAX_MSG_LEN 1024
#define PORT 5555 //1024-49151
#define IP "192.168.8.113" //本机ip地址 用ifconfig查看
struct message {
char username[50];
char content[MAX_MSG_LEN];
};
int clients[MAX_CLIENTS];
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;
void send_message(struct message msg, int self)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i] && i != self) {
send(clients[i], &msg, sizeof(msg), 0);
}
}
pthread_mutex_unlock(&clients_mutex);
}
void *handle_client(void *arg)
{
int client_fd = (int)(intptr_t)arg;
struct message msg;
recv(client_fd, &msg, sizeof(msg), 0);
printf("%s 加入聊天室\n", msg.username);
while (1)
{
if (recv(client_fd, &msg, sizeof(msg), 0) <= 0)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i] == client_fd)
{
printf("%s 离开聊天室\n", msg.username);
clients[i] = 0;
break;
}
}
pthread_mutex_unlock(&clients_mutex);
break;
} else {
printf("%s: %s\n", msg.username, msg.content);
send_message(msg, client_fd);
}
}
close(client_fd);
}
int main(int argc, char *argv[])
{
int server_fd, client_fd, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
memset(clients, 0, MAX_CLIENTS * sizeof(int));
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(IP);
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0)
{
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("服务器开启端口号: %d\n", PORT);
printf("等待客户端连接...\n");
while (1) {
if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0)
{
perror("accept failed");
exit(EXIT_FAILURE);
}
int i;
for (i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i] == 0)
{
clients[i] = client_fd;
break;
}
}
if (i == MAX_CLIENTS) {
printf("客户端连接失败\n");
close(client_fd);
continue;
}
pthread_t new_thread;
if (pthread_create(&new_thread, NULL, handle_client, (void *)(intptr_t)client_fd) != 0)
{
printf("线程创建失败\n");
continue;
}
}
return 0;
}
客户端端代码主要实现以下功能:
- 创建并连接到服务器套接字。
- 接收来自其他客户端的消息并将其输出到终端。
- 向服务器发送该客户端所发送的消息,以便与其他客户端进行交流。
- 创建一个独立的线程接收来自服务器的消息并将其输出到终端,以避免阻塞 UI 线程。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_USERNAME_LEN 50
#define MAX_MSG_LEN 1024
#define PORT 5555 //1024-49151
#define IP "192.168.8.113" //本机ip地址 用ifconfig查看
struct message
{
char username[MAX_USERNAME_LEN];
char content[MAX_MSG_LEN];
};
void *recv_message(void *arg)
{
int sockfd = *(int *)arg;
struct message msg;
while (1)
{
ssize_t len = recv(sockfd, &msg, sizeof(msg), 0);
if (len <= 0)
{
fprintf(stderr, "错误: 服务器断开连接 \n");
break;
} else if (len != sizeof(msg)) {
fprintf(stderr, "错误: 接收消息长度不符 \n");
continue;
} else {
printf("%s: %s\n", msg.username, msg.content);
}
}
close(sockfd); // 关闭套接字
exit(0);
}
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in serv_addr;
char username[MAX_USERNAME_LEN];
char password[MAX_USERNAME_LEN];
struct message msg;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
fprintf(stderr, "错误: 套接字创建失败 \n");
exit(1);
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, IP, &serv_addr.sin_addr) <= 0)
{
fprintf(stderr, "错误: 无效地址 \n");
exit(1);
}
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
fprintf(stderr, "错误: 连接服务器失败 \n");
exit(1);
}
printf("请输入账户: ");
fgets(username, MAX_USERNAME_LEN, stdin);
username[strlen(username) - 1] = '\0';
printf("请输入密码: ");
fgets(password, MAX_USERNAME_LEN, stdin);
password[strlen(password) - 1] = '\0';
// 对用户名和密码进行验证
strcpy(msg.username, username);
if (send(sockfd, &msg, sizeof(msg), 0) == -1)
{
fprintf(stderr, "错误: 发送消息失败 \n");
close(sockfd); // 关闭套接字
exit(1);
}
pthread_t thread_id;
pthread_create(&thread_id, NULL, recv_message, (void *)&sockfd);
while (1)
{
printf("发送消息: ");
fgets(msg.content, MAX_MSG_LEN, stdin);
msg.content[strlen(msg.content) - 1] = '\0';
if (send(sockfd, &msg, sizeof(msg), 0) == -1)
{
fprintf(stderr, "错误: 发送消息失败 \n");
break;
}
}
pthread_cancel(thread_id);
close(sockfd);
return 0;
}
代码运行结果示例: