文章目录
1. 实验四:网络编程
2. 客户端函数
2.1 void *receive_message(void *arg)
作用:从服务器接收消息并打印,根据消息类型进行格式化处理(如私聊、断开提示等)。
关键逻辑:
while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
buffer[read_size] = '\0';
if (strncmp(buffer, "私聊来自", 12) == 0) {
// 私聊格式化
char *colon_pos = strchr(buffer, ':');
if (colon_pos) {
strncpy(sender_id, buffer, colon_pos - buffer);
printf("%s: %s\n", sender_id, colon_pos + 1);
}
} else if (strcmp(buffer, "quit") == 0) {
printf("您已断开连接。\n");
break;
} else {
printf("%s\n", buffer);
}
}
2.2 void *send_message(void *arg)
作用:从用户输入读取消息并发送到服务器,区分公聊、私聊、退出指令。
关键逻辑:
fgets(message, BUFFER_SIZE, stdin); // 读取输入
message[len - 1] = '\0'; // 去除换行
if (strncmp(message, "/private", 8) == 0) {
send(sock, message, strlen(message), 0); // 私聊命令
} else if (strcmp(message, "quit") == 0) {
send(sock, message, strlen(message), 0); // 退出命令
break;
} else {
snprintf(full_message, sizeof(full_message), "%s: %s", username, message);
send(sock, full_message, strlen(full_message), 0); // 普通消息
}
2.3 int main(int argc, char *argv[])
作用:程序入口,连接服务器,初始化用户名,创建接收与发送线程,等待线程结束并关闭连接。
关键逻辑:
(1)创建并连接 socket:
client_socket = socket(AF_INET, SOCK_STREAM, 0);
connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
(2)输入用户名并发送:
fgets(username, sizeof(username), stdin);
username[username_len - 1] = '\0';
snprintf(username_message, sizeof(username_message), "用户名 %s", username);
send(client_socket, username_message, strlen(username_message), 0);
(3)创建两个线程:
pthread_create(&receive_thread_id, NULL, receive_message, (void *)&client_socket);
pthread_create(&send_thread_id, NULL, send_message, (void *)&client_socket);
(4)等待线程并关闭连接:
pthread_join(send_thread_id, NULL);
close(client_socket);
3. 服务端函数
3.1 remove_client
作用:从服务器的客户端数组中移除断开的客户端,并重置群主(如果是群主离线)。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].id == client_id) {
clients[i].id = -1;
clients[i].socket = -1;
break;
}
}
if (owner_id == client_id) {
owner_id = -1;
}
3.2 send_private_message
作用:发送私聊消息到指定客户端。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].id == receiver_id) {
receiver_socket = clients[i].socket;
break;
}
}
snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message);
send(receiver_socket, formatted_message, strlen(formatted_message), 0);
3.3 handle_command
作用:解析客户端发来的命令(目前只实现了 /private
)。
关键代码:
if (strncmp(message, "/private", 8) == 0) {
int receiver_id;
char *msg_start = strchr(message + 9, ' ');
if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1) {
msg_start++; // 跳过空格
send_private_message(msg_start, receiver_id, client_id);
}
}
3.4 broadcast
作用:将普通消息广播给所有客户端(不包括发送者)。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].socket != -1 && clients[i].socket != sender_socket) {
send(clients[i].socket, message, strlen(message), 0);
}
}
3.5 client_thread
作用:处理每个客户端的通信,包括初始编号、群主分配、消息收发与断开处理。
关键代码:
snprintf(id_message, sizeof(id_message), "您是客户端编号 %d", client_id);
send(conn, id_message, strlen(id_message), 0);
if (client_id == 1) {
owner_id = client_id;
send(conn, "您是群主", 10, 0);
}
while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0) {
buffer[read_size] = '\0';
if (buffer[0] == '/') {
handle_command(buffer, conn, client_id);
} else {
broadcast(buffer, conn);
}
}
3.6 server_input_thread
作用:允许服务器手动输入信息并群发公告。
关键代码:
fgets(message, BUFFER_SIZE, stdin);
message[len - 1] = '\0';
snprintf(server_message, sizeof(server_message), "服务器公告: %s", message);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].socket != -1) {
send(clients[i].socket, server_message, strlen(server_message), 0);
}
}
3.7 main
作用:服务器主入口,初始化服务器、监听客户端连接、创建新线程处理每个连接。
关键代码:
(1)创建监听 Socket:
server_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_socket, 100);
(2)接受客户端并分配线程:
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size);
clients[slot].socket = client_socket;
clients[slot].id = client_count;
pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]);
pthread_detach(tid);
4. 运行结果及分析
4.1 服务端
u202321331023@jmu-cs:~/net_exp$ ./server 30023
聊天室已启动,监听端口: 30023
服务器消息: 客户端 1 已连接,地址: 127.0.0.1:33310
客户端 1: 小林
客户端 2 已连接,地址: 127.0.0.1:60706
客户端 2: 小红
客户端 3 已连接,地址: 127.0.0.1:42474
客户端 3: 小蓝
客户端 1: 小林: Hello everyone!
客户端 2: 小红: 你好1号小林
客户端 3: 小蓝: 大家好
欢迎来到聊天室各位
广播: 服务器公告: 欢迎来到聊天室各位
服务器消息: 私聊: 客户端 3 到 客户端 2: 你好,这是私聊信息
4.2 客户端
4.2.1 客户端1:小林
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小林
您是客户端编号 1 您是群主
Hello everyone!
小红: 你好1号小林
小蓝: 大家好
服务器公告: 欢迎来到聊天室各位
4.2.1 客户端2:小红
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小红
您是客户端编号 2
小林: Hello everyone!
小蓝: 大家好
服务器公告: 欢迎来到聊天室各位
私聊来自 3: 你好,这是私聊信息
4.2.1 客户端3:小蓝
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小蓝
您是客户端编号 3
小林: Hello everyone!
小红: 你好1号小林
大家好
服务器公告: 欢迎来到聊天室各位
/private 2 你好,这是私聊信息
4.3 结果分析
4.3.1 连接与初始化阶段
(1)服务端输出:
聊天室已启动,监听端口: 30023
客户端 1 已连接,地址: 127.0.0.1:33310
客户端 2 已连接,地址: 127.0.0.1:60706
客户端 3 已连接,地址: 127.0.0.1:42474
(2)客户端输出:
您是客户端编号 1 您是群主
您是客户端编号 2
您是客户端编号 3
(3)分析:
- 服务端监听端口 30023 成功。
- 客户端连接顺序决定了编号:小林是客户端 1,因此被设为群主。
- 每个客户端都成功收到了自己的编号,
client_thread
中的初始化逻辑工作正常。 - 群主识别逻辑也生效,
owner_id = client_id;
触发成功。
4.3.2 广播消息
(1)发送与接收:
小林: Hello everyone!
小红: 你好1号小林
小蓝: 大家好
服务端记录:
客户端 1: 小林: Hello everyone! 客户端 2: 小红: 你好1号小林 客户端 3: 小蓝: 大家好
客户端显示:
- 每位用户都能看到其他人发的内容(说明广播功能正常)。
- 消息格式也被正常拼接为
用户名: 消息
,符合客户端send_message
函数逻辑。
4.3.3 服务器广播
(1)服务端手动输入:
服务器消息: 欢迎来到聊天室各位
广播: 服务器公告: 欢迎来到聊天室各位
(2)客户端响应:
服务器公告: 欢迎来到聊天室各位
(3)分析:
- 服务端控制台广播成功发送,并能被所有客户端接收。
server_input_thread
中的格式拼接和循环send()
均执行正确。
4.3.4 私聊功能
(1)客户端3 小蓝输入:
/private 2 你好,这是私聊信息
(2)服务端日志:
服务器消息: 私聊: 客户端 3 到 客户端 2: 你好,这是私聊信息
(3)客户端2 小红收到:
私聊来自 3: 你好,这是私聊信息
(4)分析:
- 私聊命令
/private <ID> <消息>
被成功解析。 - 服务端
handle_command
识别/private
并调用send_private_message
。 - 接收端能正确格式化并显示“私聊来自 X: 消息”。
5. 挑战性任务(实现简易版QQ功能,即客户端与客户端之间聊天,服务器只是作为中转)
5.1 服务器能够接收来自多个不同客户端的连接
5.1.1 关键代码片段:接收多个客户端连接并创建线程
while ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size)))
{
client_count++;
pthread_mutex_lock(&clients_mutex);
// 寻找空闲槽位
int slot = -1;
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket == -1)
{
slot = i;
break;
}
}
if (slot != -1)
{
// 保存客户端信息
clients[slot].socket = client_socket;
clients[slot].id = client_count;
printf("客户端 %d 已连接,地址: %s:%d\n",
client_count,
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 创建线程处理客户端
if (pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]) < 0)
{
perror("创建线程失败");
close(client_socket);
clients[slot].socket = -1;
clients[slot].id = -1;
}
else
{
pthread_detach(tid); // 自动释放资源
}
}
else
{
printf("客户端连接已达上限,拒绝连接\n");
close(client_socket);
}
pthread_mutex_unlock(&clients_mutex);
}
5.1.2 功能说明:
服务器通过 accept()
接收多个客户端连接,并为每个连接创建一个独立线程 client_thread()
来并发处理,从而实现了多客户端同时接入与通信。
代码位置 | 功能 |
---|---|
accept(...) |
等待并接收新的客户端连接请求 |
clients[slot] |
将新连接保存到客户端数组中 |
pthread_create(...) |
为每个客户端连接创建独立线程,调用 client_thread 处理收发数据 |
pthread_detach(...) |
设置线程为分离状态,避免内存泄漏 |
MAX_CLIENTS 限制 |
控制服务器最多可同时处理的连接数 |
5.2 私聊功能
5.2.1 命令识别入口:handle_command()
函数
服务器检测和处理 /private
私聊命令
void handle_command(char *message, int conn, int client_id)
{
if (strncmp(message, "/private", 8) == 0) // 识别私聊命令
{
int receiver_id;
char *msg_start = strchr(message + 9, ' '); // 查找消息正文起始
if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1)
{
msg_start++; // 跳过空格
send_private_message(msg_start, receiver_id, client_id); // 调用私聊函数
}
}
}
5.2.2 私聊核心处理函数:send_private_message()
发送私聊消息给指定客户端的核心实现:
void send_private_message(char *message, int receiver_id, int sender_id)
{
pthread_mutex_lock(&clients_mutex);
int receiver_socket = -1;
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].id == receiver_id) // 查找目标ID
{
receiver_socket = clients[i].socket;
break;
}
}
if (receiver_socket != -1)
{
char formatted_message[BUFFER_SIZE];
snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message); // 构造私聊消息格式
send(receiver_socket, formatted_message, strlen(formatted_message), 0); // 发送私聊消息
printf("私聊: 客户端 %d 到 客户端 %d: %s\n", sender_id, receiver_id, message); // 服务器日志
}
pthread_mutex_unlock(&clients_mutex);
}
5.2.3 命令调用位置:在 client_thread()
中调用 handle_command()
确保客户端发送的 /private
命令被处理:
while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0)
{
buffer[read_size] = '\0';
if (buffer[0] == '/')
{
handle_command(buffer, conn, client_id); // 识别是否为私聊命令
}
else
{
broadcast(buffer, conn); // 普通消息广播
}
}
5.3 服务器广播功能
5.3.1 广播函数定义:broadcast()
c复制编辑void broadcast(char *message, int sender_socket)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket != -1 && clients[i].socket != sender_socket)
{
if (send(clients[i].socket, message, strlen(message), 0) < 0)
{
perror("广播消息失败");
close(clients[i].socket);
remove_client(clients[i].socket, clients[i].id);
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
5.3.2 具体说明
代码位置 | 功能 |
---|---|
for 循环遍历所有客户端 |
找到所有在线客户端 |
clients[i].socket != sender_socket |
避免给自己重复发送消息 |
send() |
向其他客户端广播消息 |
错误处理 | 如果发送失败,关闭连接并清除客户端信息 |
6. 实验过程中遇到的问题及解决方法
6.1 终端中文编码错误
**原因:**服务器端的编码是独立的,即使在笔记本上处理好编码格式,复制到服务器上会依旧按照服务器的编码格式
解决方法: 在服务器上直接设置对应的编码保存格式,设置成UTF-8(默认是ISO-8859)
7. 附完整源代码
7.1 客户端源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#define BUFFER_SIZE 1024
int client_socket;
char username[50];
pthread_t receive_thread_id, send_thread_id;
// 接收消息的线程函数
void *receive_message(void *arg)
{
int sock = *((int *)arg);
char buffer[BUFFER_SIZE];
int read_size;
while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0)
{
buffer[read_size] = '\0';
if (strncmp(buffer, "私聊来自", 12) == 0)
{
// 格式化私聊信息
char *colon_pos = strchr(buffer, ':');
if (colon_pos)
{
char sender_id[BUFFER_SIZE];
int sender_id_length = colon_pos - buffer;
strncpy(sender_id, buffer, sender_id_length);
sender_id[sender_id_length] = '\0';
printf("%s: %s\n", sender_id, colon_pos + 1);
}
else
{
printf("%s\n", buffer);
}
}
else if (strcmp(buffer, "quit") == 0)
{
printf("您已断开连接。\n");
break;
}
else
{
printf("%s\n", buffer);
}
}
if (read_size == 0)
{
printf("连接已断开。\n");
}
else if (read_size == -1)
{
perror("接收失败");
}
pthread_exit(NULL);
}
// 发送消息的线程函数
void *send_message(void *arg)
{
int sock = *((int *)arg);
char message[BUFFER_SIZE];
char full_message[BUFFER_SIZE + 100];
while (1)
{
if (fgets(message, BUFFER_SIZE, stdin) == NULL)
{
break;
}
// 移除换行符
size_t len = strlen(message);
if (len > 0 && message[len - 1] == '\n')
{
message[len - 1] = '\0';
}
if (strncmp(message, "/private", 8) == 0)
{
// 直接发送私聊命令
if (send(sock, message, strlen(message), 0) < 0)
{
printf("发送消息失败,可能已断开连接\n");
break;
}
}
else if (strcmp(message, "quit") == 0)
{
if (send(sock, message, strlen(message), 0) < 0)
{
printf("发送消息失败,可能已断开连接\n");
}
break;
}
else
{
// 拼接用户名和消息
snprintf(full_message, sizeof(full_message), "%s: %s", username, message);
if (send(sock, full_message, strlen(full_message), 0) < 0)
{
printf("发送消息失败,可能已断开连接\n");
break;
}
}
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
struct sockaddr_in server_addr;
// 检查命令行参数
if (argc != 3)
{
printf("用法: ./client <服务器IP> <端口号>\n");
printf("示例: ./client 127.0.0.1 30023\n");
return 1;
}
char *ip_address = argv[1];
int port;
// 检查端口是否为数字
if (sscanf(argv[2], "%d", &port) != 1)
{
printf("错误: 端口号必须是整数\n");
return 1;
}
// 创建套接字
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1)
{
perror("无法创建套接字");
return 1;
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip_address);
server_addr.sin_port = htons(port);
// 连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("连接失败");
printf("无法连接到服务器 %s:%d\n", ip_address, port);
return 1;
}
printf("已连接到服务器 %s:%d\n", ip_address, port);
// 输入用户名
printf("请输入您的用户名: ");
fgets(username, sizeof(username), stdin);
// 移除换行符
size_t username_len = strlen(username);
if (username_len > 0 && username[username_len - 1] == '\n')
{
username[username_len - 1] = '\0';
}
// 发送用户名
char username_message[BUFFER_SIZE];
snprintf(username_message, sizeof(username_message), "用户名 %s", username);
if (send(client_socket, username_message, strlen(username_message), 0) < 0)
{
perror("发送用户名失败");
return 1;
}
// 创建接收消息线程
if (pthread_create(&receive_thread_id, NULL, receive_message, (void *)&client_socket) != 0)
{
perror("创建接收线程失败");
return 1;
}
// 创建发送消息线程
if (pthread_create(&send_thread_id, NULL, send_message, (void *)&client_socket) != 0)
{
perror("创建发送线程失败");
return 1;
}
// 等待发送线程结束
pthread_join(send_thread_id, NULL);
// 关闭连接
close(client_socket);
printf("连接已关闭\n");
return 0;
}
7.2 服务端源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 100
// 客户端结构体
typedef struct
{
int id;
int socket;
} Client;
// 全局变量
int client_count = 0;
Client clients[MAX_CLIENTS];
int owner_id = -1;
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;
// 删除客户端
void remove_client(int socket, int client_id)
{
pthread_mutex_lock(&clients_mutex);
// 从客户端列表中删除
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].id == client_id)
{
clients[i].id = -1;
clients[i].socket = -1;
break;
}
}
// 如果是群主离开,重置群主
if (owner_id == client_id)
{
owner_id = -1;
}
pthread_mutex_unlock(&clients_mutex);
}
// 向指定客户端发送私聊信息
void send_private_message(char *message, int receiver_id, int sender_id)
{
pthread_mutex_lock(&clients_mutex);
int receiver_socket = -1;
// 查找接收者的socket
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].id == receiver_id)
{
receiver_socket = clients[i].socket;
break;
}
}
if (receiver_socket != -1)
{
char formatted_message[BUFFER_SIZE];
snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message);
if (send(receiver_socket, formatted_message, strlen(formatted_message), 0) < 0)
{
perror("发送私聊消息失败");
// 考虑是否需要删除连接失败的客户端
}
else
{
printf("私聊: 客户端 %d 到 客户端 %d: %s\n", sender_id, receiver_id, message);
}
}
pthread_mutex_unlock(&clients_mutex);
}
// 处理命令
void handle_command(char *message, int conn, int client_id)
{
// 私聊命令
if (strncmp(message, "/private", 8) == 0)
{
int receiver_id;
char *msg_start = strchr(message + 9, ' ');
if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1)
{
// 跳过空格
msg_start++;
send_private_message(msg_start, receiver_id, client_id);
}
}
}
// 广播消息
void broadcast(char *message, int sender_socket)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket != -1 && clients[i].socket != sender_socket)
{
if (send(clients[i].socket, message, strlen(message), 0) < 0)
{
perror("广播消息失败");
// 考虑是否需要删除连接失败的客户端
close(clients[i].socket);
remove_client(clients[i].socket, clients[i].id);
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
// 客户端线程函数
void *client_thread(void *arg)
{
Client *client = (Client *)arg;
int conn = client->socket;
int client_id = client->id;
char buffer[BUFFER_SIZE];
int read_size;
// 发送客户端编号
char id_message[50];
snprintf(id_message, sizeof(id_message), "您是客户端编号 %d", client_id);
send(conn, id_message, strlen(id_message), 0);
// 如果是第一个连接的客户端,设置为群主
if (client_id == 1)
{
owner_id = client_id;
send(conn, "您是群主", 10, 0);
}
// 消息处理循环
while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0)
{
buffer[read_size] = '\0';
if (buffer[0] == '/')
{
handle_command(buffer, conn, client_id);
}
else
{
printf("客户端 %d: %s\n", client_id, buffer);
broadcast(buffer, conn);
}
}
if (read_size == 0)
{
printf("客户端 %d 已断开连接\n", client_id);
close(conn);
remove_client(conn, client_id);
}
else if (read_size == -1)
{
perror("接收失败");
}
pthread_exit(NULL);
}
// 服务器输入线程函数
void *server_input_thread(void *arg)
{
char message[BUFFER_SIZE];
char server_message[BUFFER_SIZE + 50];
while (1)
{
printf("服务器消息: ");
if (fgets(message, BUFFER_SIZE, stdin) == NULL)
{
break;
}
// 移除换行符
size_t len = strlen(message);
if (len > 0 && message[len - 1] == '\n')
{
message[len - 1] = '\0';
len--;
}
if (len > 0)
{
snprintf(server_message, sizeof(server_message), "服务器公告: %s", message);
printf("广播: %s\n", server_message);
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket != -1)
{
if (send(clients[i].socket, server_message, strlen(server_message), 0) < 0)
{
perror("发送服务器消息失败");
close(clients[i].socket);
clients[i].socket = -1;
clients[i].id = -1;
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_size = sizeof(struct sockaddr_in);
pthread_t tid;
// 初始化客户端数组
for (int i = 0; i < MAX_CLIENTS; i++)
{
clients[i].id = -1;
clients[i].socket = -1;
}
// 检查命令行参数
if (argc != 2)
{
printf("用法: ./server <端口号>\n");
printf("示例: ./server 30023\n");
return 1;
}
int port;
// 检查端口是否为数字
if (sscanf(argv[1], "%d", &port) != 1)
{
printf("错误: 端口号必须是整数\n");
return 1;
}
// 创建服务器套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
perror("无法创建套接字");
return 1;
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(port);
// 绑定套接字
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("绑定失败");
return 1;
}
// 监听连接
if (listen(server_socket, 100) < 0)
{
perror("监听失败");
return 1;
}
printf("聊天室已启动,监听端口: %d\n", port);
// 创建服务器输入线程
pthread_t server_thread;
if (pthread_create(&server_thread, NULL, server_input_thread, NULL) < 0)
{
perror("创建服务器输入线程失败");
return 1;
}
// 接受连接循环
while ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size)))
{
client_count++;
pthread_mutex_lock(&clients_mutex);
// 寻找空闲的客户端槽位
int slot = -1;
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (clients[i].socket == -1)
{
slot = i;
break;
}
}
if (slot != -1)
{
clients[slot].socket = client_socket;
clients[slot].id = client_count;
printf("客户端 %d 已连接,地址: %s:%d\n",
client_count,
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 创建新的客户端线程
if (pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]) < 0)
{
perror("创建线程失败");
close(client_socket);
clients[slot].socket = -1;
clients[slot].id = -1;
}
else
{
// 分离线程,让它自己结束
pthread_detach(tid);
}
}
else
{
printf("客户端连接已达上限,拒绝连接\n");
close(client_socket);
}
pthread_mutex_unlock(&clients_mutex);
}
if (client_socket < 0)
{
perror("接受连接失败");
return 1;
}
close(server_socket);
return 0;
}