1、服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#define PORT 8080 //服务器监听的端口号
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 100
typedef struct{
int socket;
struct sockaddr_in address;
int addr_len;
int index;//客户端在 clients 数组中的索引
}client_t;
client_t *clients[MAX_CLIENTS];
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;//一个互斥锁,用于保护对 clients 数组的访问,确保线程安全
//将消息广播给所有其他客户端
void broadcast_message(char *message, int sender_index) {
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; ++i) {
if (clients[i] && i != sender_index) {
if (send(clients[i]->socket, message, strlen(message), 0) < 0) {
perror("send");
continue;
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
void *handle_client(void *arg) {
char buffer[BUFFER_SIZE];
int leave_flag = 0;//一个标志变量,用于指示是否需要退出循环。初始值为 0,表示不退出。
client_t *cli = (client_t *)arg;
while (1) {
//recv(int sockfd, void *buf, size_t len, int flags);flags=0表示正常接收数据
int receive = recv(cli->socket, buffer, BUFFER_SIZE, 0);//表示实际接收到的字节数
if (receive > 0) {
buffer[receive] = '\0';//在接收到的消息后面添加字符串结束符 \0,确保消息是一个有效的 C 字符串
broadcast_message(buffer, cli->index);
} else if (receive == 0 || strcmp(buffer, "exit") == 0) {//客户端正常关闭了连接(TCP 的 FIN 包),如果客户端发送了 "exit" 消息
leave_flag = 1;
} else {//表示接收失败
perror("recv");
leave_flag = 1;
}
if (leave_flag) {
break;
}
}
close(cli->socket);//关闭当前客户端的套接字,释放资源
pthread_mutex_lock(&clients_mutex);
clients[cli->index] = NULL;//将当前客户端从 clients 数组中移除
pthread_mutex_unlock(&clients_mutex);
free(cli);
pthread_exit(NULL);//退出当前线程
}
int main() {
/*
server_socket:服务器端套接字描述符,用于监听客户端的连接请求。
new_socket:新连接的客户端套接字描述符。
*/
int server_socket, new_socket;
/*
struct sockaddr_in 是一个结构体
server_addr:用于存储服务器的地址信息。
client_addr:用于存储连接的客户端的地址信息。
*/
struct sockaddr_in server_addr, client_addr;
//socklen_t 是 POSIX 标准定义的数据类型,用于表示套接字地址的长度
socklen_t addr_len = sizeof(client_addr);
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
server_addr.sin_family = AF_INET;
/*
sin_addr 是 struct sockaddr_in 结构体中的一个成员
sin_addr 是一个结构体,而 s_addr 是该结构体中的实际数据字段。
在设置或获取 IP 地址时,需要通过 s_addr 来访问或修改 IP 地址的值。
*/
server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定到所有可用的网络接口
server_addr.sin_port = htons(PORT);//使用 htons 函数将端口号转换为网络字节序
/*
使用 bind 函数将套接字绑定到指定的地址和端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
指向套接字地址结构体的指针。通常是一个 struct sockaddr_in 的地址,但需要通过强制类型转换为 struct sockaddr *
*/
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
/*
使用 listen 函数将套接字设置为监听状态,最大等待队列长度为 10
EXIT_FAILURE 是一个宏,通常定义为 1
*/
if (listen(server_socket, 10) < 0) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
/*
新连接的客户端套接字描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值是一个新的套接字描述符,表示与客户端建立的连接
*/
new_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
if (new_socket < 0) {
perror("accept");
continue;
}
//使用 pthread_mutex_lock 锁定 clients 数组,确保线程安全
pthread_mutex_lock(&clients_mutex);
int i;
for (i = 0; i < MAX_CLIENTS; ++i) {
if (!clients[i]) {
client_t *cli = (client_t *)malloc(sizeof(client_t));
cli->socket = new_socket;
cli->address = client_addr;
cli->addr_len = addr_len;
cli->index = i;
clients[i] = cli;
//使用 pthread_create 创建一个线程来处理客户端连接,调用 handle_client 函数
pthread_t tid;
/*
&tid:指向线程标识符的指针,用于存储新创建的线程的标识符。
NULL:使用默认的线程属性。
handle_client:线程函数的指针,表示新线程将执行的函数。
(void *)cli:传递给 handle_client 函数的参数,cli 是一个指向 client_t 结构体的指针,表示当前客户端的信息。
*/
pthread_create(&tid, NULL, handle_client, (void *)cli);
/*将线程分离,使其在结束时自动释放资源*/
pthread_detach(tid);
break;
}
}
if (i == MAX_CLIENTS) {
printf("Max clients reached. Rejected: ");
printf("%d.%d.%d.%d\n",
client_addr.sin_addr.s_addr & 0xff,
(client_addr.sin_addr.s_addr & 0xff00) >> 8,
(client_addr.sin_addr.s_addr & 0xff0000) >> 16,
(client_addr.sin_addr.s_addr & 0xff000000) >> 24);
close(new_socket);
}
pthread_mutex_unlock(&clients_mutex);
}
return 0;
}
2、客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
void *receive_messages(void *arg) {
int server_socket = *(int *)arg;
char buffer[BUFFER_SIZE];
while (1) {
int receive = recv(server_socket, buffer, BUFFER_SIZE, 0);
if (receive > 0) {
buffer[receive] = '\0';
printf("Server: %s", buffer); // 显示接收到的消息
} else if (receive == 0) {
printf("Server disconnected\n");
break;
} else {
perror("recv");
break;
}
}
close(server_socket);
pthread_exit(NULL);
}
int main() {
int server_socket;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 创建套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 初始化服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接到服务器
if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Connected to server\n");
// 创建接收消息的线程
pthread_t receive_thread;
pthread_create(&receive_thread, NULL, receive_messages, &server_socket);
// 发送消息
while (1) {
fgets(buffer, BUFFER_SIZE, stdin);
if (strcmp(buffer, "exit\n") == 0) {
send(server_socket, buffer, strlen(buffer), 0);
break;
}
send(server_socket, buffer, strlen(buffer), 0);
}
// 等待接收线程结束
pthread_join(receive_thread, NULL);
// 关闭套接字
close(server_socket);
return 0;
}