多线程服务器代码:实现实时群聊功能
#include 'myhead.h'
struct thread_pool *pool = NULL; // 线程池结构体指针 pclient_connfd cli = NULL; // 已连接套接字数组结构体指针 pclient_info head = NULL; // 客户端信息链表结构体指针 int totaluser; // 总在线用户数
// 任务函数 void *my_task(void *arg) { pthread_detach(pthread_self()); // 设置线程结束后自动回收资源
// printf("123\n");
char buf[100]; // 接收缓冲区
int ret = 0; // 是否接收到数据标志位
char *divide = ":"; // 分隔符,分开收到数据的名字和内容
// printf("000\n");
pclient_info m = (pclient_info)arg;
// printf("m->connfd:%d\n",m->connfd);
// printf("m->client_name:%s\n",m->client_name);
// printf("111\n");
while (1)
{
// 连接成功,开始接收数据
bzero(buf, sizeof(buf));
ret = recv(m->connfd, buf, sizeof(buf), 0); // 大于0说明接收到了数据
// if(ret==-1)//5s内无数据
// {
// printf("已超时\n");
// }
// printf("ret:%d\n",ret);
if (ret > 0) // 5s内有数据
{
printf("%s\n", buf); // 打印收到的内容
// 将收到的信息转发给所有客户端
for (pclient_info h = head->next; h != NULL; h = h->next) // 将已经存入的用户名发送给用户端,来确保不会同名
{
send(h->connfd, buf, strlen(buf), 0);
}
// char *p=strtok(buf,divide);//以冒号分割,得到前面的名字
//p=strtok(NULL,buf);//获取冒号后的发送内容
char *p = strtok(buf, divide);
/* 继续获取其他的子字符串 */
while (p != NULL)
{
p = strtok(NULL, divide);
if (p != NULL && strncmp(p, "quit", strlen(p)) == 0) // 客户端输入quit说明该客户端想要下线
{
if (strlen(p) == 4)
{
printf("%s已下线\n", m->client_name);
printf("(当前在线用户数:%d)\n", --totaluser);
close(m->connfd);
delete_clientnode(head, m->client_IP); // 从链表中删除下线的客户端
}
}
}
// printf( "%s\n", p );
// printf("123\n");
// if(strncmp(p,"quit",strlen(p))==0)//客户端输入quit说明该客户端想要下线
// {
// if(strlen(p)==4)
// {
// printf("%s已下线\n",m->client_name);
// printf("(当前在线用户数:%d)\n", --totaluser);
// delete_clientnode(head,m->client_IP);//从链表中删除下线的客户端
// close(m->connfd);
// }
// }
// printf("222\n");
}
}
}
int main(int argc, char *argv[]) { // 初始化线程池 pool = malloc(sizeof(thread_pool)); init_pool(pool, 0); // 初始化线程池
// 初始化已连接套接字数组
cli = init_connfdaddr();
// 初始化客户端结构体链表
head = init_clientlist();
// 创建套接字
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
printf("socket_fd:%d\n", socket_fd);
// 绑定
struct sockaddr_in server_addr;
socklen_t len = sizeof(server_addr); // 结构体长度
bzero(&server_addr, len);
server_addr.sin_family = AF_INET; // 地址族
server_addr.sin_port = htons(atoi(argv[1])); // 端口号
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // IP地址
bind(socket_fd, (struct sockaddr *)&server_addr, len);
// 设置监听套接字
listen(socket_fd, 60); // 可同时监听60个客户端
// 添加非阻塞属性(给socket_fd)
int state;
state = fcntl(socket_fd, F_GETFL);
state |= O_NONBLOCK; // 在原来的基础上再添加一个非阻塞属性
fcntl(socket_fd, F_SETFL, state);
struct sockaddr_in client_addr; // 客户端结构体
bzero(&client_addr, len);
int tmp; // 是否成功添加到数组中标志位
char buf_name[50]; // 客户端用户名
// 接收数据
while (1)
{
bzero(buf_name, sizeof(buf_name));
// 等待客户端连接
int connfd = accept(socket_fd, (struct sockaddr *)&client_addr, &len); // 一直阻塞直到有客户端连入
// printf("connfd:%d\n",connfd);
if (connfd > 0) // 表示有客户端连入
{
// //设置非阻塞属性
// state=fcntl(connfd,F_GETFL);
// state|=O_NONBLOCK;
// fcntl(connfd,F_SETFL,state);
//设置套接字属性为超时接收(给connfd)
// struct timeval v;
// v.tv_sec=10;//设置每次5秒接收时间
// v.tv_usec=0;
// setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&v,sizeof(v));//设置套接字超时接收属性
if (totaluser >= 60) // 为-1表示数组满了,加入失败
{
printf("可连入服务器端的客户端数量已满,无法连接\n");
close(connfd);
continue;
}
recv(connfd, buf_name, sizeof(buf_name), 0); // 接收客户端的用户名
// for(plient_info h=head->next;h!=NULL;h=h->next)//将已经存入的用户名发送给用户端,来确保不会同名
// {
// send(connfd,h->client_name,strlen(h->client_name),0);
// recv(connfd,buf_name,sizeof(buf_name),0);//接收客户端的用户名
// }
printf("connfd:%d\n", connfd);
printf("新连入的用户名为:%s\n", buf_name);
printf("新连入的IP为:%s\n", inet_ntoa(client_addr.sin_addr));
printf("(当前在线用户数:%d)\n", ++totaluser);
if (find_nameclientnode(head, buf_name) == NULL && find_IPclientnode(head, inet_ntoa(client_addr.sin_addr)) == NULL) // 等于NULL说明链表中没有存入该客户端(按名字)
{
new_clientnode(head, buf_name, inet_ntoa(client_addr.sin_addr), connfd); // 将该客户端的名字与IP号插入到链表中
print_clientlist(head);
}
pclient_info p = find_IPclientnode(head, inet_ntoa(client_addr.sin_addr));
// 新客户端连入,开辟一条新线程
add_thread(pool, 1);
add_task(pool, my_task, (void *)p);
}
}
// 关闭套接字
close(socket_fd);
return 0;
}
客户端代码: #include 'myhead.h'
int main(int argc, char *argv[]) {
// 创建套接字
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
printf("sockfd = %d\n", socket_fd);
// 连接服务器端
struct sockaddr_in server_addr;
socklen_t len = sizeof(server_addr);
bzero(&server_addr, len);
server_addr.sin_family = AF_INET; // 地址族
server_addr.sin_port = htons(atoi(argv[2])); // 服务器端端口号
inet_pton(AF_INET, argv[1], &server_addr.sin_addr); // 服务器端ip地址
connect(socket_fd, (struct sockaddr *)&server_addr, len); // 套接字连接客户端
// 输入客户端用户名
char bufname[50]; // 客户端名字
bzero(bufname, sizeof(bufname));
printf("请输入用户名:");
scanf("%s", bufname);
send(socket_fd, bufname, strlen(bufname), 0);
// char bufback[50];
// while(strncmp(bufname,bufback,strlen(bufname)!=0))
// {
// bzero(bufback,sizeof(bufback));
// recv(socket_fd,bufback,sizeof(bufback),0);//接收客户端的用户名
// if(strncmp(bufname,bufback,strlen(bufname)==0))
// {
// printf("用户名已存在,请重新输入\n");
// printf("请输入用户名:");
// scanf("%s",bufname);
// send(socket_fd,bufname,strlen(bufname),0);
// }
// }
char buf[50]; // 发送缓冲区
char buf1[100]; // 拼接字符串后存入的数组
char bufall[100]; // 群聊接收缓冲区
int ret1 = 0; // 客户端是否接收成功标志位
while (1)
{
bzero(buf, sizeof(buf));
bzero(buf1, sizeof(buf1));
bzero(bufall, sizeof(bufall));
printf("请输入要送发的内容:");
scanf("%s", buf);
sprintf(buf1, "%s:%s", bufname, buf);
// sprintf(buf1,"张三:%s",buf);
send(socket_fd, buf1, strlen(buf1), 0);
ret1 = recv(socket_fd, bufall, sizeof(bufall), 0);
printf("ret1:%d\n", ret1);
if (ret1 > 0)
{
printf("%s\n", bufall);
}
printf("strlenbufall:%ld\n", strlen(bufall));
if (strncmp(buf, "quit", strlen(buf)) == 0)
{
if (strlen(buf) == 4)
{
break;
}
}
}
// 关闭套接字
close(socket_fd);
return 0;
}
为什么上述代码中目的就是让所有客户端都收到服务器端发送的消息,但客户端收不到服务器端发送给自己的消息内容:在服务器端代码中,发送消息给所有客户端的逻辑是正确的。服务器端接收到消息后,会遍历所有客户端,将消息发送给每个客户端。
在客户端代码中,接收服务器端发送的消息的逻辑有问题。在while循环中,使用recv函数接收服务器端发送的消息时,需要注意以下几点:
-
recv函数的第三个参数是接收缓冲区的大小,需要保证接收缓冲区足够大以容纳服务器端发送的消息。
-
recv函数的返回值是实际接收到的字节数,需要将接收到的消息存储在接收缓冲区中。
-
recv函数是阻塞函数,如果服务器端没有发送消息,客户端会一直阻塞在接收函数,导致无法继续执行后面的代码。可以考虑使用非阻塞模式或者多线程来处理接收消息的逻辑。
请检查客户端代码中的recv函数的使用,确保接收到服务器端发送的消息,并正确处理。
原文地址: https://www.cveoy.top/t/topic/jqCF 著作权归作者所有。请勿转载和采集!