根据网络编程中的内容,我们本篇文章将讲解一个bbs通信的项目,首先让我们了解一下什么是bbs.
一、bbs介绍
BBS,即Bulletin Board System的缩写,中文译为“电子公告板系统”或“网络论坛”。它是一个在网络上进行信息交流和讨论的平台。早期的BBS主要用于公布股市价格等信息,只能在苹果计算机上运行。随着个人计算机的普及,BBS逐渐转移到个人计算机上,功能也得到了扩展,用户可以在BBS上发布文章、收发电子邮件、交流聊天、发布广告等。BBS的发展历程可以追溯到20世纪70年代,最早的BBS系统出现在美国芝加哥。在中国,第一个BBS站出现在1991年。如今,BBS仍然是一种常见的网络交流方式,被广泛应用于教学、推广、地方交流和一般性讨论等领域。
二、bbs客户端
2.1、客户端流程图
简单来看
- 使用
socket
创建通讯句柄- 使用
connect
连接到主机- 使用
select
进行键盘和网络的多路选择
- 如果有键盘数据,则读入键盘数据并发送到网络
- 如果有网络数据,则接收网络数据并上屏显示
- 判断是否接收到退出符,如果是,则使用
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 : short
inet_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 服务器流程
- 使用socket创建监听句柄
- 使用bind绑定端口
- 使用listen监听端口
- 使用accept等待客户接入,并使用pthread_create创建线程与客户交互
- 在线程中,使用write发送选择菜单,并使用read等待客户选择
- 如果选择有效,则调用addClient添加新客户到链表中
- 使用read等待客户发布消息,并将消息保存到相应类型的文件中
- 调用multicastMsg将消息转发到相同类型的其他客户
- 判断是否接收到退出标识,如果是,则调用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 : short
serveraddr.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)
{
//你要删除的是 第一个节点head
head = p->next;
}
q->next = p->next;
free(p);
break;
}
q = p; //保存上一节点
p = p->next;
}
}
- 定义两个指针变量p和q,其中p用于遍历链表,q用于保存p的前一个节点。
- 使用while循环遍历链表,直到找到要删除的节点或到达链表的末尾。
- 在循环内部,使用if语句判断当前节点是否为要删除的节点。如果当前节点的sockfd字段与要删除节点的sockfd字段相等,则表示找到了要删除的节点。
- 如果要删除的节点是链表的第一个节点,即p == q,则直接将头节点指向下一个节点,即head = p->next;。
- 如果要删除的节点不是链表的第一个节点,则将q的next指针指向p的下一个节点,即q->next = p->next;,从而将p从链表中删除。
- 使用free函数释放被删除节点所占用的内存空间。
- 使用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);
}
- 定义一个字符数组buf作为缓冲区,用于存储从文件中读取的数据。
- 使用fopen函数以只读模式打开指定文件。如果文件打开失败,则打印错误信息并返回。
- 使用fgets函数从文件中读取一行数据,并将其存储在缓冲区buf中。如果读取成功,则使用write函数将数据发送给套接字连接。
- 重复步骤3,直到文件中的所有数据都读取完毕。
- 使用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"); // 如果文件打开失败,打印错误信息
}
}
函数的实现步骤如下:
- 使用fopen函数以追加和读写模式("a+")打开指定的文件。如果文件不存在,则会创建一个新文件。如果文件已经存在,则会在文件末尾追加数据。
- 如果文件打开成功(fp不为NULL),则使用fputs函数将数据缓冲区buf中的内容写入到文件中。
- 无论文件打开是否成功,都需要使用fclose函数关闭文件。
- 如果文件打开失败(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;
}
}
- 定义一个指针变量
p
,用于遍历链表。- 使用
while
循环遍历链表,直到到达链表的末尾。- 在循环内部,使用
if
语句判断当前节点是否满足广播条件,即类型与指定节点相同且不是指定节点本身。- 如果满足条件,则使用
write
系统调用将指定节点的登录名、消息内容发送给当前节点。- 继续遍历链表,直到到达链表的末尾。
需要注意的是,在调用
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);
}
}
- 定义一些变量:
- char buf[1024]:用于存储接收到的数据或要发送的数据的缓冲区。
- int result:用于存储接收或发送操作的结果。
- int fd:文件描述符,未使用。
- int newfd:用于存储新连接的套接字描述符。
- fd_set rdset:用于存储要监视的文件描述符的集合。
- pthread_t tid:用于存储新线程的ID。
- struct sockaddr_in re:用于存储客户端的地址信息。
- socklen_t addrlen:用于存储客户端地址信息的长度。
- 调用init_socket函数初始化服务器套接字,并将服务器的IP地址作为参数传递给它。init_socket函数返回一个套接字描述符sockfd,用于监听客户端的连接请求。
- 进入一个无限循环,不断接受客户端的连接请求并创建线程来处理。
- 调用accept函数接受一个客户端的连接请求,并将新连接的套接字描述符存储在newfd中。同时,将客户端的地址信息存储在re中。
- 打印出新连接的套接字描述符和客户端的IP地址。
- 调用pthread_create函数创建一个新线程,并将talk2client函数的地址和newfd的地址作为参数传递给它。talk2client函数是用于处理客户端请求的函数,它将在新的线程中执行。
- 由于是无限循环,服务器将一直运行下去,不断接受新的客户端连接并创建线程来处理。
线程实现
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 : short
inet_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 : short
serveraddr.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)
{
//你要删除的是 第一个节点head
head = 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"); //append
fputs(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);
}
}
记得先运行服务器再运行客户端哦