BBS客户端服务器的编写

根据网络编程中的内容,我们本篇文章将讲解一个bbs通信的项目,首先让我们了解一下什么是bbs.

一、bbs介绍

BBS,即Bulletin Board System的缩写,中文译为“电子公告板系统”或“网络论坛”。它是一个在网络上进行信息交流和讨论的平台。早期的BBS主要用于公布股市价格等信息,只能在苹果计算机上运行。随着个人计算机的普及,BBS逐渐转移到个人计算机上,功能也得到了扩展,用户可以在BBS上发布文章、收发电子邮件、交流聊天、发布广告等。BBS的发展历程可以追溯到20世纪70年代,最早的BBS系统出现在美国芝加哥。在中国,第一个BBS站出现在1991年。如今,BBS仍然是一种常见的网络交流方式,被广泛应用于教学、推广、地方交流和一般性讨论等领域。

二、bbs客户端

2.1、客户端流程图

简单来看

  1. 使用socket创建通讯句柄
  2. 使用connect连接到主机
  3. 使用select进行键盘和网络的多路选择
    • 如果有键盘数据,则读入键盘数据并发送到网络
    • 如果有网络数据,则接收网络数据并上屏显示
  4. 判断是否接收到退出符,如果是,则使用close断开网络连接并结束程序

 2.2实现代码

首先进行初始化,使用套接字与服务器进行连接

#include <sys/types.h>			
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>int init_socket(char *ip)
{//1. 创建一个套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);//2 定义一个 sockaddr_in 结构体,并初始化struct  sockaddr_in  serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(2000); //  h:host  n:network s : shortinet_aton(ip, &serveraddr.sin_addr);//3. connect 服务器connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));return  sockfd;
}

init_socket函数的作用是初始化一个套接字,并使用该套接字连接到指定的服务器。

函数接受一个参数ip,表示服务器的IP地址。函数内部首先使用socket系统调用创建了一个套接字,并指定了地址族(AF_INET表示IPv4)和套接字类型(SOCK_STREAM表示面向连接的TCP协议)。然后,函数定义了一个sockaddr_in结构体变量serveraddr,用于存储服务器的地址信息。

接下来,函数对serveraddr结构体进行了初始化。其中,sin_family字段被设置为AF_INET,表示使用IPv4地址族。sin_port字段被设置为htons(2000),表示服务器的端口号是2000。htons函数用于将主机字节序的16位整数转换为网络字节序。sin_addr字段被设置为使用inet_aton函数将传入的IP地址字符串转换为网络字节序的二进制形式。

最后,函数使用connect系统调用尝试连接到服务器。如果连接成功,则返回套接字描述符sockfd;如果连接失败,则返回错误代码。

 

 主函数

int main(int argc, char *argv[])
{char buf[1024]={0};int result,fd;fd_set rdset;int sockfd = init_socket(argv[1]);while(1){FD_ZERO(&rdset);FD_SET(sockfd, &rdset);FD_SET(0, &rdset);if(select(sockfd+1, &rdset, NULL, NULL, NULL)<0){perror("select:");return -1;}if(FD_ISSET(sockfd,&rdset))//网络有数据可读{memset(buf,0,sizeof(buf));result = recv(sockfd, buf, sizeof(buf)-1, 0);if(result < 0){perror("recv:");return -1;}else if(result == 0){printf("服务器断开连接\n");break;}else{buf[result] = 0; //放 \0 作为buf的结束标志printf("%s\n",buf);}}if(FD_ISSET(0, &rdset)) //键盘有数据可读{memset(buf,0,sizeof(buf));if(fgets(buf, sizeof(buf), stdin)){write(sockfd,buf,strlen(buf));}}}
}

这段代码是C语言编写的一个网络通信程序的主函数。它实现了一个简单的客户端,可以与服务器进行数据的发送和接收。

函数的开头定义了一些变量:

  • char buf[1024]:用于存储接收到的数据或要发送的数据的缓冲区。
  • int result:用于存储接收或发送操作的结果。
  • fd_set rdset:用于存储要监视的文件描述符的集合。

然后,程序调用init_socket函数来初始化套接字,并将服务器的IP地址作为参数传递给它。init_socket函数返回一个套接字描述符sockfd,用于与服务器进行通信。

接下来是一个无限循环,用于不断监视套接字和标准输入是否有数据可读。循环的开始,程序使用FD_ZERO函数清空文件描述符集合rdset,然后使用FD_SET函数将套接字描述符sockfd和标准输入的文件描述符0添加到集合中。

然后,程序调用select函数来监视文件描述符集合。如果select函数返回值小于0,表示发生了错误,程序会调用perror函数打印错误信息并返回-1。

如果select函数返回值大于0,表示有文件描述符可读。程序首先检查套接字描述符是否可读,即FD_ISSET(sockfd, &rdset)是否为真。如果是真,表示网络上有数据可读,程序会使用recv函数接收数据,并将接收到的数据存储在缓冲区buf中。如果接收操作成功,程序会将接收到的数据打印到屏幕上。如果接收到的数据长度为0,表示服务器断开了连接,程序会打印提示信息并退出循环。

如果套接字描述符不可读,程序会检查标准输入是否可读,即FD_ISSET(0, &rdset)是否为真。如果是真,表示键盘上有数据可读,程序会使用fgets函数读取一行数据,并将读取到的数据存储在缓冲区buf中。然后,程序会使用write函数将数据发送给服务器。

总的来说,这个主函数实现了一个简单的网络通信客户端,可以与服务器进行数据的发送和接收。它使用select函数来监视套接字和标准输入,并根据不同的情况进行相应的操作。

三、bbs服务器

3.1 服务器流程

  1. 使用socket创建监听句柄
  2. 使用bind绑定端口
  3. 使用listen监听端口
  4. 使用accept等待客户接入,并使用pthread_create创建线程与客户交互
  5. 在线程中,使用write发送选择菜单,并使用read等待客户选择
    • 如果选择有效,则调用addClient添加新客户到链表中
    • 使用read等待客户发布消息,并将消息保存到相应类型的文件中
    • 调用multicastMsg将消息转发到相同类型的其他客户
  6. 判断是否接收到退出标识,如果是,则调用removeClient删除该客户并关闭通讯句柄,然后线程退出

3.2 实现代码

首先进行一定义

//定义类型枚举量
typedef enum ClientType
{NEWS_TYPE,ENTERTAINMENT_TYPE,SUPPLY_TYPE
}ClientType;//定义客户节点量
typedef struct ClientNode
{int sockfd;enum ClientType type;char logname[20]; //登录名struct ClientNode * next;
}ClientNode;ClientNode *head=NULL;
pthread_rwlock_t lockList = PTHREAD_RWLOCK_INITIALIZER;
pthread_rwlock_t lockFile = PTHREAD_RWLOCK_INITIALIZER;char *name_msg = "请输入登录名:\n";
char *menu="\n"
" -------------------------\n"	
"      登录类型选择              \n"
"      1)  新闻              \n"
"      2)  娱乐              \n"
"      3)  交易              \n"
" -------------------------\n"	
"请选择登录的类目:";
  • ClientType:这是一个枚举类型,表示了客户端的类型,包括新闻(NEWS_TYPE)、娱乐(ENTERTAINMENT_TYPE)和供应(SUPPLY_TYPE)三种类型。
  • ClientNode:这是一个结构体类型,表示了链表中的一个节点,包含了客户端的套接字描述符(sockfd)、类型(type)、登录名(logname)以及指向下一个节点的指针(next)。
  • head:这是一个指向ClientNode类型的指针,表示了链表的头节点。初始值为NULL,表示链表为空。
  • lockList和lockFile:这两个是读写锁(pthread_rwlock_t)类型的变量,用于对链表和文件进行并发访问的控制。初始化为PTHREAD_RWLOCK_INITIALIZER,表示锁已经被初始化。

 初始化套接字,等待客户端请求

int init_socket(char *ip)
{//1. 创建一个套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);//2 定义一个 sockaddr_in 结构体,并初始化struct  sockaddr_in  serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(2000); //  h:host  n:network s : shortserveraddr.sin_addr.s_addr = htonl(INADDR_ANY);// INADDR_ANY  有内核帮你找一个合适的网卡IP地址socklen_t addrlen = sizeof(serveraddr);int on=1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));int r =  bind(sockfd,(struct sockaddr *)&serveraddr,addrlen);if(r < 0){perror("bind:");return -1;}r = listen(sockfd, 5);if(r < 0){perror("listen:");return -1;}return  sockfd;
}

使用socket系统调用创建一个套接字,指定地址族为AF_INET(表示IPv4),类型为SOCK_STREAM(表示面向连接的TCP协议)。

定义一个sockaddr_in结构体变量serveraddr,用于存储服务器的地址信息。

初始化serveraddr结构体的各个字段:

sin_family字段被设置为AF_INET,表示使用IPv4地址族。

sin_port字段被设置为htons(2000),表示服务器的端口号为2000。htons函数用于将主机字节序的16位整数转换为网络字节序。

sin_addr.s_addr字段被设置为htonl(INADDR_ANY),表示服务器可以接受来自任何IP地址的连接请求。htonl函数用于将主机字节序的32位整数转换为网络字节序。

使用setsockopt系统调用设置套接字选项,允许多个进程或线程同时绑定到同一个端口号上。

使用bind系统调用将套接字绑定到指定的IP地址和端口号上。如果绑定失败,则打印错误信息并返回-1。

使用listen系统调用开始监听套接字,等待客户端的连接请求。如果监听失败,则打印错误信息并返回-1。

如果一切正常,则返回套接字描述符sockfd。

 使用头插法,插入客户,形成一个链表

void insertNode(ClientNode * p)
{p->next = head;head = p;
}

 客户端断开连接后,删除此节点

void DeleteNode(ClientNode *client)
{ClientNode *p = head;ClientNode *q = head;while(p){if(p->sockfd == client->sockfd){if(p == q){//你要删除的是 第一个节点headhead = p->next;}q->next = p->next;free(p);break;}q = p; //保存上一节点p = p->next; }
}

  1. 定义两个指针变量p和q,其中p用于遍历链表,q用于保存p的前一个节点。
  2. 使用while循环遍历链表,直到找到要删除的节点或到达链表的末尾。
  3. 在循环内部,使用if语句判断当前节点是否为要删除的节点。如果当前节点的sockfd字段与要删除节点的sockfd字段相等,则表示找到了要删除的节点。
  4. 如果要删除的节点是链表的第一个节点,即p == q,则直接将头节点指向下一个节点,即head = p->next;。
  5. 如果要删除的节点不是链表的第一个节点,则将q的next指针指向p的下一个节点,即q->next = p->next;,从而将p从链表中删除。
  6. 使用free函数释放被删除节点所占用的内存空间。
  7. 使用break语句退出循环。

 发送历史记录

void sendHistory(char *filename,int newfd)
{char buf[1024]={};int n;FILE *fp = fopen(filename, "r");if(fp==NULL){perror("fopen:");return ;}while(fgets(buf, sizeof(buf),fp)){write(newfd, buf, strlen(buf));}fclose(fp);
}
  1. 定义一个字符数组buf作为缓冲区,用于存储从文件中读取的数据。
  2. 使用fopen函数以只读模式打开指定文件。如果文件打开失败,则打印错误信息并返回。
  3. 使用fgets函数从文件中读取一行数据,并将其存储在缓冲区buf中。如果读取成功,则使用write函数将数据发送给套接字连接。
  4. 重复步骤3,直到文件中的所有数据都读取完毕。
  5. 使用fclose函数关闭文件。

保存文件

void save2File(char *filename, char *buf)
{FILE *fp;fp = fopen(filename, "a+"); // 以追加和读写模式打开文件if (fp != NULL) {fputs(buf, fp); // 将数据写入文件末尾fclose(fp); // 关闭文件} else {perror("Error opening file"); // 如果文件打开失败,打印错误信息}
}

函数的实现步骤如下:

  1. 使用fopen函数以追加和读写模式("a+")打开指定的文件。如果文件不存在,则会创建一个新文件。如果文件已经存在,则会在文件末尾追加数据。
  2. 如果文件打开成功(fp不为NULL),则使用fputs函数将数据缓冲区buf中的内容写入到文件中。
  3. 无论文件打开是否成功,都需要使用fclose函数关闭文件。
  4. 如果文件打开失败(fp为NULL),则使用perror函数打印错误信息。

将一条消息从一个客户端节点广播到所有类型相同的其他客户端节点

void multicastNode(ClientNode * client,char * buf)
{ClientNode *p = head;while(p){//类型和本人相同,又不是本人的节点if((p->type == client->type) && (p->sockfd != client->sockfd) ){write(p->sockfd, client->logname,strlen(client->logname));write(p->sockfd," 说: ", 6);write(p->sockfd, buf, strlen(buf));}p= p->next;}
}
  1. 定义一个指针变量p,用于遍历链表。
  2. 使用while循环遍历链表,直到到达链表的末尾。
  3. 在循环内部,使用if语句判断当前节点是否满足广播条件,即类型与指定节点相同且不是指定节点本身。
  4. 如果满足条件,则使用write系统调用将指定节点的登录名、消息内容发送给当前节点。
  5. 继续遍历链表,直到到达链表的末尾。

需要注意的是,在调用multicastNode函数之前,需要确保链表中已经存在至少两个节点,并且指定节点的类型不为空。

 主函数

int main(int argc, char *argv[])
{char buf[1024]={0};int result,fd, newfd;fd_set rdset;pthread_t tid;struct sockaddr_in re;socklen_t addrlen = sizeof(re);int sockfd = init_socket(argv[1]);while(1){newfd = accept(sockfd, (struct sockaddr *)&re,&addrlen);printf("newfd=%d\n", newfd);printf("IP:%s\n", inet_ntoa(re.sin_addr));pthread_create(&tid, NULL, talk2client, &newfd);}}
  1. 定义一些变量:
    • char buf[1024]:用于存储接收到的数据或要发送的数据的缓冲区。
    • int result:用于存储接收或发送操作的结果。
    • int fd:文件描述符,未使用。
    • int newfd:用于存储新连接的套接字描述符。
    • fd_set rdset:用于存储要监视的文件描述符的集合。
    • pthread_t tid:用于存储新线程的ID。
    • struct sockaddr_in re:用于存储客户端的地址信息。
    • socklen_t addrlen:用于存储客户端地址信息的长度。
  2. 调用init_socket函数初始化服务器套接字,并将服务器的IP地址作为参数传递给它。init_socket函数返回一个套接字描述符sockfd,用于监听客户端的连接请求。
  3. 进入一个无限循环,不断接受客户端的连接请求并创建线程来处理。
    • 调用accept函数接受一个客户端的连接请求,并将新连接的套接字描述符存储在newfd中。同时,将客户端的地址信息存储在re中。
    • 打印出新连接的套接字描述符和客户端的IP地址。
    • 调用pthread_create函数创建一个新线程,并将talk2client函数的地址和newfd的地址作为参数传递给它。talk2client函数是用于处理客户端请求的函数,它将在新的线程中执行。
  4. 由于是无限循环,服务器将一直运行下去,不断接受新的客户端连接并创建线程来处理。

 线程实现

void *talk2client(void *arg)
{int result;int newfd = *(int *)arg;char * filename;char buf[200]={};//设置线程为 分离属性:自己回收 线程资源pthread_detach(pthread_self());ClientNode * p = (ClientNode *)malloc(sizeof(ClientNode));memset(p, 0, sizeof(ClientNode));memset(buf,0, sizeof(buf));// 1.填入:sockfd p->sockfd = newfd;p->next = NULL;//2.读登录名write(newfd, name_msg, strlen(name_msg));result = recv(newfd, buf, sizeof(buf)-1, 0);buf[result-1] = 0; //去掉行尾的 '\n'printf("buf:%s\n", buf);strcpy(p->logname,buf);//3. 登录的类型write(newfd, menu, strlen(menu));result = recv(newfd, buf, sizeof(buf)-1, 0);if(result<=0){printf("客户端断开连接\n");close(newfd);pthread_exit(NULL);}buf[result-1] = 0; //去掉行尾的 '\n'printf("buf:%s\n", buf);if(buf[0]=='1'){p->type = NEWS_TYPE;filename = "bbs_news.txt";}else if(buf[0]=='2'){p->type = ENTERTAINMENT_TYPE;filename = "bbs_trans.txt";}else if(buf[0]=='3'){p->type = SUPPLY_TYPE;filename = "bbs_fun.txt";}//插入客户结点到head链表pthread_rwlock_wrlock(&lockList);insertNode(p);pthread_rwlock_unlock(&lockList);//发送历史记录pthread_rwlock_rdlock(&lockFile);sendHistory(filename,newfd);pthread_rwlock_unlock(&lockFile);//进入主循环while(1){result = recv(newfd, buf, sizeof(buf)-1, 0);if(result < 0){perror("recv:");continue;}else if(result == 0){printf("客户端断开连接\n");pthread_rwlock_wrlock(&lockList);DeleteNode(p);pthread_rwlock_unlock(&lockList);break;}else {if(strncmp(buf, "exit\n", 5)==0){printf("客户端要主动离开\n");pthread_rwlock_wrlock(&lockList);DeleteNode(p);pthread_rwlock_unlock(&lockList);break;}//保存记录!buf[result -1]= 0; // 去掉 最后 \n 符号pthread_rwlock_wrlock(&lockFile);save2File(filename, buf);pthread_rwlock_unlock(&lockFile);// 多播信息pthread_rwlock_rdlock(&lockList);multicastNode(p, buf);pthread_rwlock_unlock(&lockList);}}
}

talk2client函数是一个处理与客户端通信的函数,它通过套接字与客户端进行数据的发送和接收。该函数被main函数中的pthread_create调用,在独立的线程中运行,以处理每个连接到服务器的客户端。

函数首先获取传递进来的参数,即新的套接字描述符newfd。然后,它创建一个ClientNode结构体的对象p,用于存储客户端的信息,如套接字描述符和登录名。接下来,函数与客户端进行交互,首先发送一个提示信息,要求客户端输入登录名,然后接收客户端的登录名并存储在p中。

接着,函数向客户端发送一个菜单,让客户端选择登录的类型,然后接收客户端的选择,根据选择设置p中的类型字段,并确定要保存消息的文件名。然后,函数将p插入到链表中,并发送历史记录给客户端。

最后,函数进入一个循环,不断接收客户端发送的消息。如果接收到的消息是"exit",则表示客户端主动断开连接,函数从链表中删除该客户端的信息并退出循环。否则,函数将消息保存到相应的文件中,并使用multicastNode函数将消息广播给其他类型相同的客户端。

需要注意的是,在函数中使用了读写锁来保护对共享资源(链表和文件)的访问,以避免并发访问导致的数据不一致问题。

如果有想自己试试的小伙伴们,为了方便代码我再单独放在下面

客户端代码全部

#include <sys/types.h>			
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>int init_socket(char *ip)
{//1. 创建一个套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);//2 定义一个 sockaddr_in 结构体,并初始化struct  sockaddr_in  serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(2000); //  h:host  n:network s : shortinet_aton(ip, &serveraddr.sin_addr);//3. connect 服务器connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));return  sockfd;
}int main(int argc, char *argv[])
{char buf[1024]={0};int result,fd;fd_set rdset;int sockfd = init_socket(argv[1]);while(1){FD_ZERO(&rdset);FD_SET(sockfd, &rdset);FD_SET(0, &rdset);if(select(sockfd+1, &rdset, NULL, NULL, NULL)<0){perror("select:");return -1;}if(FD_ISSET(sockfd,&rdset))//网络有数据可读{memset(buf,0,sizeof(buf));result = recv(sockfd, buf, sizeof(buf)-1, 0);if(result < 0){perror("recv:");return -1;}else if(result == 0){printf("服务器断开连接\n");break;}else{buf[result] = 0; //放 \0 作为buf的结束标志printf("%s\n",buf);}}if(FD_ISSET(0, &rdset)) //键盘有数据可读{memset(buf,0,sizeof(buf));if(fgets(buf, sizeof(buf), stdin)){write(sockfd,buf,strlen(buf));}}}
}

服务器代码全部

#include <sys/types.h>			
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>//定义类型枚举量
typedef enum ClientType
{NEWS_TYPE,ENTERTAINMENT_TYPE,SUPPLY_TYPE
}ClientType;//定义客户节点量
typedef struct ClientNode
{int sockfd;enum ClientType type;char logname[20]; //登录名struct ClientNode * next;
}ClientNode;ClientNode *head=NULL;
pthread_rwlock_t lockList = PTHREAD_RWLOCK_INITIALIZER;
pthread_rwlock_t lockFile = PTHREAD_RWLOCK_INITIALIZER;char *name_msg = "请输入登录名:\n";
char *menu="\n"
" -------------------------\n"	
"      登录类型选择              \n"
"      1)  新闻              \n"
"      2)  娱乐              \n"
"      3)  交易              \n"
" -------------------------\n"	
"请选择登录的类目:";int init_socket(char *ip)
{//1. 创建一个套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);//2 定义一个 sockaddr_in 结构体,并初始化struct  sockaddr_in  serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(2000); //  h:host  n:network s : shortserveraddr.sin_addr.s_addr = htonl(INADDR_ANY);// INADDR_ANY  有内核帮你找一个合适的网卡IP地址socklen_t addrlen = sizeof(serveraddr);int on=1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));int r =  bind(sockfd,(struct sockaddr *)&serveraddr,addrlen);if(r < 0){perror("bind:");return -1;}r = listen(sockfd, 5);if(r < 0){perror("listen:");return -1;}return  sockfd;
}//插入结点p到 Head链表中
void insertNode(ClientNode * p)
{p->next = head;head = p;
}
void DeleteNode(ClientNode *client)
{ClientNode *p = head;ClientNode *q = head;while(p){if(p->sockfd == client->sockfd){if(p == q){//你要删除的是 第一个节点headhead = p->next;}q->next = p->next;free(p);break;}q = p; //保存上一节点p = p->next; }
}void save2File(char *filename, char *buf)
{FILE *fp = fopen(filename,"a"); //appendfputs(buf, fp);fclose(fp);}
void sendHistory(char *filename,int newfd)
{char buf[1024]={};int n;FILE *fp = fopen(filename, "r");if(fp==NULL){perror("fopen:");return ;}while(fgets(buf, sizeof(buf),fp)){write(newfd, buf, strlen(buf));}fclose(fp);
}void multicastNode(ClientNode * client,char * buf)
{ClientNode *p = head;while(p){//类型和本人相同,又不是本人的节点if((p->type == client->type) && (p->sockfd != client->sockfd) ){write(p->sockfd, client->logname,strlen(client->logname));write(p->sockfd," 说: ", 6);write(p->sockfd, buf, strlen(buf));}p= p->next;}
}void *talk2client(void *arg)
{int result;int newfd = *(int *)arg;char * filename;char buf[200]={};//设置线程为 分离属性:自己回收 线程资源pthread_detach(pthread_self());ClientNode * p = (ClientNode *)malloc(sizeof(ClientNode));memset(p, 0, sizeof(ClientNode));memset(buf,0, sizeof(buf));// 1.填入:sockfd p->sockfd = newfd;p->next = NULL;//2.读登录名write(newfd, name_msg, strlen(name_msg));result = recv(newfd, buf, sizeof(buf)-1, 0);buf[result-1] = 0; //去掉行尾的 '\n'printf("buf:%s\n", buf);strcpy(p->logname,buf);//3. 登录的类型write(newfd, menu, strlen(menu));result = recv(newfd, buf, sizeof(buf)-1, 0);if(result<=0){printf("客户端断开连接\n");close(newfd);pthread_exit(NULL);}buf[result-1] = 0; //去掉行尾的 '\n'printf("buf:%s\n", buf);if(buf[0]=='1'){p->type = NEWS_TYPE;filename = "bbs_news.txt";}else if(buf[0]=='2'){p->type = ENTERTAINMENT_TYPE;filename = "bbs_trans.txt";}else if(buf[0]=='3'){p->type = SUPPLY_TYPE;filename = "bbs_fun.txt";}//插入客户结点到head链表pthread_rwlock_wrlock(&lockList);insertNode(p);pthread_rwlock_unlock(&lockList);//发送历史记录pthread_rwlock_rdlock(&lockFile);sendHistory(filename,newfd);pthread_rwlock_unlock(&lockFile);//进入主循环while(1){result = recv(newfd, buf, sizeof(buf)-1, 0);if(result < 0){perror("recv:");continue;}else if(result == 0){printf("客户端断开连接\n");pthread_rwlock_wrlock(&lockList);DeleteNode(p);pthread_rwlock_unlock(&lockList);break;}else {if(strncmp(buf, "exit\n", 5)==0){printf("客户端要主动离开\n");pthread_rwlock_wrlock(&lockList);DeleteNode(p);pthread_rwlock_unlock(&lockList);break;}//保存记录!buf[result -1]= 0; // 去掉 最后 \n 符号pthread_rwlock_wrlock(&lockFile);save2File(filename, buf);pthread_rwlock_unlock(&lockFile);// 多播信息pthread_rwlock_rdlock(&lockList);multicastNode(p, buf);pthread_rwlock_unlock(&lockList);}}
}int main(int argc, char *argv[])
{char buf[1024]={0};int result,fd, newfd;fd_set rdset;pthread_t tid;struct sockaddr_in re;socklen_t addrlen = sizeof(re);int sockfd = init_socket(argv[1]);while(1){newfd = accept(sockfd, (struct sockaddr *)&re,&addrlen);printf("newfd=%d\n", newfd);printf("IP:%s\n", inet_ntoa(re.sin_addr));pthread_create(&tid, NULL, talk2client, &newfd);}}

记得先运行服务器再运行客户端哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3030074.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

重装前端整体流程

用户管理 --汇总 -- 明细-CSDN博客 一、node 这个看环境变量 2023最新版Node.js下载安装及环境配置教程&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了_nodejs安装及环境配置-CSDN博客 配置到国内镜像的时候&#xff0c;去看&#xff0c;淘…

代码随想录算法训练营第六十二天|503.下一个更大元素II、42.接雨水

代码随想录算法训练营第六十二天|503.下一个更大元素II、42.接雨水 503.下一个更大元素II 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元…

小程序(三)

十三、自定义组件 &#xff08;二&#xff09;数据方法声明位置 在js文件中 A、数据声明位置&#xff1a;data中 B、方法声明位置methods中&#xff0c;这点和普通页面不同&#xff01; Component({/*** 组件的属性列表*/properties: {},/*** 组件的初始数据*/data: {isCh…

【系统架构师】-案例篇(七)信息安全

某软件公司拟开发一套信息安全支撑平台&#xff0c;为客户的局域网业务环境提供信息安全保护。该支撑平台的主要需求如下&#xff1a; 1.为局域网业务环境提供用户身份鉴别与资源访问授权功能&#xff1b; 2.为局域网环境中交换的网络数据提供加密保护&#xff1b; 3.为服务…

26、Qt使用QFontDatabase类加载ttf文件更改图标颜色

一、图标下载 iconfont-阿里巴巴矢量图标库 点击上面的链接&#xff0c;在打开的网页中搜索自己要使用的图标&#xff0c;如&#xff1a;最大化 找到一个自己想用图标&#xff0c;选择“添加入库” 点击“购物车”图标 能看到刚才添加的图标&#xff0c;点击“下载代码”(需要…

js教程(13)

一、作用域 作用域规定了变量能够被访问的范围&#xff0c;而离开变量作用域的变量则不能被访问&#xff08;有时也叫变量的生命周期&#xff09;。作用域又分为局部作用域和全局作用域。 1.局部作用域 在函数或代码块内部声明的变量只能在其内部被访问&#xff0c;在外部无法…

牛客周赛 Round 41 C-F

C 小红的循环移位 思路&#xff1a; 一个数是不是四的倍数&#xff0c;只用看最后两位是否能够整除4即可。 #include <bits/stdc.h>using namespace std; const int N 1e6 5; typedef long long ll; typedef pair<ll, ll> pll; typedef array<ll, 3> p3;…

暗区突围进不去/游戏无法启动/掉帧卡顿/报错的解决方法

暗区突围是一款高拟真硬核射击手游&#xff0c;打造了全新的沉浸式暗区战局体验&#xff0c;发行商是腾讯公司。这个游戏名词虽然看起来有些陌生&#xff0c;但其本身的玩法内核毫无疑问的是&#xff0c;这款游戏在画面质量和枪械操作方面&#xff0c;都是手游市场上同类游戏中…

【vulhub靶场】Apache 中间件漏洞复现

【vulhub靶场】Apache 中间件漏洞复现 一、Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;1. 漏洞详情2. 影响版本3. 漏洞复现 二、Apache多后缀解析漏洞&#xff08;apache_parsing_vulnerability&#xff09;1. 漏洞详情2. 漏洞复现 三、Apache HTTP Serv…

【LLM 论文】Step-Back Prompting:先解决更高层次的问题来提高 LLM 推理能力

论文&#xff1a;Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models ⭐⭐⭐⭐ Google DeepMind, ICLR 2024, arXiv:2310.06117 论文速读 该论文受到的启发是&#xff1a;人类再解决一个包含很多细节的具体问题时&#xff0c;先站在更高的层次上解…

第8章.STM32开发方式(库函数)介绍

目录 0. 《STM32单片机自学教程》专栏 8.1 单片机的开发方式 8.1.1 直接操作寄存器 8.1.2 使用库函数 8.2 STM32的库函数 8.2.1 标准外设库(STD库) 8.2.2 HAL库 8.2.3 LL库 0. 《STM32单片机自学教程》专栏 本文作为专栏《STM32单片机自学教程》专栏其中的一…

数据库调优-SQL语句优化

2. SQL语句优化 sql 复制代码 # 请问这两条SQL语句有什么区别呢&#xff1f;你来猜一猜那条SQL语句执行查询效果更好&#xff01; select id from sys_goods where goods_name华为 HUAWEI 麦芒7 魅海蓝 6G64G 全网通; ​ select id from sys_goods where goods_id14967325985…

搜索的未来:OpenAI 的 GPT 如何彻底改变行业

搜索的未来&#xff1a;OpenAI 的 GPT 如何彻底改变行业 概述 搜索引擎格局正处于一场革命的风口浪尖&#xff0c;而 OpenAI 的 GPT 处于这场变革的最前沿。最近出现了一种被称为“im-good-gpt-2-chatbot”的神秘聊天机器人&#xff0c;以及基于 ChatGPT 的搜索引擎的传言&am…

MySQL索引(聚簇索引、非聚簇索引)

了解MySQL索引详细&#xff0c;本文只做整理归纳&#xff1a;https://blog.csdn.net/wangfeijiu/article/details/113409719 概念 索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息。 索引分类 主键索引&#xff1a…

C++对象的赋值

同类的对象之间可以互相赋值&#xff0c;即一个对象的值可以赋值给另一个对象。对象之间的赋值通过“”进行。默认就是把一个对象所有非static数据成员的值依次赋值给另一个对象。 对象赋值的一般形式为&#xff1a; 对象名1 对象名2; 注意:对象名1和对象名2必须是属于同一个…

黑客如何进行IP伪装

在进行互联网访问时候&#xff0c;我们如果被查到IP访问记录&#xff0c;就能根据IP查到具体位置&#xff0c;如果从事非法事情就会被请去喝茶。那么IP是什么&#xff1f;为什么可以根据IP进行查找并且分析主机数据&#xff1f; IP是Internet Protocol&#xff08;网际互连协议…

《TAM》论文笔记(上)

原文链接 [2005.06803] TAM: Temporal Adaptive Module for Video Recognition (arxiv.org) 原文代码 GitHub - liu-zhy/temporal-adaptive-module: TAM: Temporal Adaptive Module for Video Recognition 原文笔记 What&#xff1a; TAM: Temporal Adaptive Module for …

【数据结构与算法】堆

定义 堆是是一个完全二叉树&#xff0c;其中每个节点的值都大于等于或小于等于其子节点的值。这取决于是最大堆还是最小堆。 小根堆&#xff1a;每个根都小于子节点。 大根堆&#xff1a;每个根都大于子节点。 以下部分图例说明来源&#xff1a;【从堆的定义到优先队列、堆排…

Python修改exe之类的游戏文件中的数值

文章目录 场景查找修改 补充字节to_bytes 场景 某些游戏数值&#xff08;攻击力、射程、速度…&#xff09;被写在exe之类的文件里 要先查找游戏数值&#xff0c;然后修改 查找 首先&#xff0c;要查找数值&#xff0c;大数重复较少&#xff0c;建议从大数找起 F 游戏原件…

【系统架构师】-案例篇(五)企业应用系统集成与ESB

在航空业中&#xff0c;Ramp Coordination是指飞机从降落到起飞过程中所需要进行的各种业务活动的协调过程。通常每个航班都有一位员工负责Ramp Coordination&#xff0c;称之为RampCoordinator。由Ramp Coordinator协调的业务活动包括检查机位环境、卸货和装货等。 由于航班类…