聊天室基本功能:
1:群发的上下线通知
2:服务器显示全部聊天信息,并且可以群发系统消息
3:输入Q下线
架构:
客户端分为两个线程,主线程用来发送信息,子线程用来接收信息。发送的信息包是一个字符数组,包括1byte标识符、10byte用户昵称、128byte消息内容。它和记录ip地址和端口号的结构体共同组成一个结构体变量:struct usrmsg。
服务器也分为两个线程,主线程用来群发消息,子线程根据接收到的客户端信息,处理消息。服务器内用一个链表存储在线用户的ip地址与端口号。用来定位用户,以便未来的进一步更新系统,加入如禁言、踢下线等功能。
结构体定义在头文件内
//用户信息
struct usrmsg
{
//char type; //协议 登录:L,退出:Q,说话:C,服务器组发:F
//char name[10]; //用户名
//char buf[128]; //消息内容
struct sockaddr_in sin;
char message[139]; //消息主体 type + name + buf
};
typedef struct Node
{
union
{
struct sockaddr_in sin; //存入用户的地址信息和端口号
int len;
};
struct Node *next;
}Linklist; //链表的结构体变量名:Linklist
服务器中用到的链表操作函数不多,仅有:创建链表、申请节点、判空、头插、遍历、头删、根据值查找位置、任意位置删除、全链表销毁
就全部贴在下方,如有需要,可以自取
//创建
Linklist *list_create()
{
Linklist* L = (Linklist*)malloc( sizeof(Linklist) );
if( NULL == L)
{
printf("创建失败\n");
return NULL;
}
//初始化
L->len = 0;
L->next = NULL;
printf("链表创建成功\n");
return L;
}
//节点申请函数
Linklist* node_add(struct sockaddr_in e)
{
Linklist *p = (Linklist*)malloc(sizeof(Linklist));
if(NULL == p)
{
printf("节点申请失败\n");
return NULL;
}
//数据存放
p->sin = e;
p->next = NULL;
}
//判空
int list_empty( Linklist *L )
{
if(L->next == NULL)
{
printf("链表为空\n");
return 1;
}else
{
return 0;
}
}
//头插
int list_intsrt_head(Linklist *L ,struct sockaddr_in e)
{
if(NULL == L)
{
printf("链表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//完成头插
p->next = L->next;
L->next = p;
//表的变化
L->len++;
printf("信息录入成功\n");
}
//遍历
void list_show(Linklist *L)
{
if(NULL == L || list_empty(L)) //先判断非法
{
printf("表空或表非法,遍历失败\n");
return;
}
//遍历
printf("链表元素为:");
Linklist *q = L->next;
while(q != NULL)
{
//printf("%c\t",q->sin);
q = q->next;
}
printf("\n");
}
//尾插
int list_intsrt_tail(Linklist *L, struct sockaddr_in e)
{
//判空
if(NULL == L)
{
printf("所给表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//遍历指针指向最后一个结点
Linklist *q = L;
while(q->next != NULL)
{
q = q->next;
}
q->next = p;
//表的变化
L->len++;
}
//头删
int delete_head(Linklist *L)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("删除失败\n");
return -1;
}
Linklist *p = L->next; //标记
L->next = p->next; //也可以写成L->next->next
free(p); //释放内存
p = NULL;
//表的变化
L->len--;
printf("删除成功\n");
}
//任意位置插入(重点)
int list_intsrt_pos(Linklist *L, int pos, struct sockaddr_in e)
{
//逻辑判断
if(NULL==L || pos<1 || pos>L->len+1)
{
printf("表非法,无法插入\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//查找要插入位置的前区节点
Linklist *q = find_node(L, pos-1);
//插入逻辑
p->next = q->next;
q->next = p;
//表的变化
L->len++;
printf("插入成功\n");
}
//按位置查找,返回查找到的节点
Linklist *find_node(Linklist *L, int pos)
{
//判断
if(NULL == L || pos<0 || pos>L->len)
{
printf("查找失败\n");
return NULL;
}
//查找节点
Linklist *q = L;
for(int i=1; i<=pos; i++ )
{
q = q->next;
}
return q; //返回找到的节点
}
//任意位置删除
int delete_pos(Linklist *L, int pos)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("表违法,不存在表或表为空\n");
return -1;
}
//任意位置删除
//找到要删位置的前区
Linklist *q = find_node(L, pos-1);
//删除逻辑
Linklist *p = q->next;
q->next = p->next;
free(p);
//表的变化
L->len--;
}
//按值查找,返回查找到的位置
int list_search_value(Linklist *L, struct sockaddr_in e)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("查找失败\n");
return -1;
}
//查找逻辑
int index = 1;
Linklist *q = L->next;
for(int i=1; i<=L->len; i++)
{
if(q->sin.sin_addr.s_addr == e.sin_addr.s_addr)
{
return i;
}else
{
q = q->next;
}
}
return 0;
}
//销毁表
void list_delete_all(Linklist *L)
{
if(NULL == L)
{
printf("表不合法");
}
for(int i=0; L->next != NULL; i++)
{
delete_head(L);
}
free(L);
printf("\n表已删除完毕\n");
}
客户端与服务器的基础架构相同该代码预先设置了ip地址与端口号,也可以修改代码,在程序运行后输入。先包裹打印错误的宏函数以及宏定义和全局变量
//打印错误信息的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__", __LINE__);\
perror(msg);\
}while(0)
#define IP "0.0.0.0"
#define POST "8888"
int sfd; //因为两个线程都要用
//同时服务器的子线程还要传递一个链表地址进去
//加之我不像传结构体,所以干脆设置全局变量了
客户端的框架一如既往
//创建报式套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
fprintf(stderr,"create socket success\n");
//绑定服务器地址信息结构体--->非必须
//填充服务器的IP地址以及端口号
//定义用户信息变量
struct usrmsg usr;
// char post[6];
// fprintf(stderr,"请输入端口号>>>");
// scanf("%s",post);
usr.sin.sin_family = AF_INET;
usr.sin.sin_port = htons(atoi(POST)); //要整形,但是是字符串,所以用转换
usr.sin.sin_addr.s_addr = inet_addr(IP);
bzero(usr.message, sizeof(usr.message)); //初始化信息
首先第一个功能,客户端首次登录,先输入昵称,然后把标识符改成‘L’,代表初次登录,然后用fprintf函数把这些信息录入usr.message数组中,发送给服务器
//客户端登录
fprintf(stderr,"请输入用户名>>>");
scanf("%s", usr.message+1);
getchar();
usr.message[0] = 'L'; //登录
sprintf(usr.message,"%c%s%s",usr.message[0], usr.message+1, usr.message+11);//把信息录入message
//发送信息,由服务器判断为初次登陆还是在线用户
if(sendto(sfd, usr.message, sizeof(usr.message), 0, (struct sockaddr*)&(usr.sin), sizeof(usr.sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
fprintf(stderr,"sendto success\n");
当服务器接收到客户端的信息,并发现标识符为‘L’时,就向除了登录用户以外的全部用户群发一个“xx用户上线”的通知(把标识符改为F,代表系统消息),并且把该用户ip结构体用头插法置入链表
然后在服务器窗口输出该用户ip地址、端口号、昵称以及登陆成功信息
if(cin.message[0] == 'L')
{
//把信息挂载到链表上
list_intsrt_head(L, cin.sin); //头插
Linklist *q = L->next; //挪到第一,也就是刚刚插入的那一位
char arr[8] = "上线";
//发送给所有人
while(q->next != NULL) //如果后面没人了,不需要发送
{
q = q->next;
//标识符置为'F'
cin.message[0] = 'F';
char *p = cin.message+11; //指向内容位
strcpy(p, cin.message+1); //把昵称发到消息位置
strcat(p, arr);
if(sendto(sfd, cin.message, sizeof(cin.message), 0, \
(struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
//在服务器窗口打印该用户基本信息以及登陆成功的信息
strcpy(cin.message+11, "登录成功");
if(sendto(sfd, cin.message, sizeof(cin.message), 0, \
(struct sockaddr*)&(L->next->sin), sizeof(L->next->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
客户端收到消息后,若发现标识符为F,则输出系统消息以提示,并输出消息内容
else if(cin.message[0] == 'F') //服务器消息
{
fprintf(stderr,"\b\b\b系统消息:%s\n", cin.message+11);
fprintf(stderr,">>>"); //为了美观性,每次输出信息后,都会留下该语句
//保证用户每次要输入信息前,前方都是>>>标志
//这也导致,输出消息要用三个\b来消除>>>
}
正常会话时,服务器只充当中继器的功能
/***************************正常会话***************************/
/*************************服务器端****************************/
else if(cin.message[0] == 'C')
{
Linklist *q = L; //挪到头部
//发送给所有人
while(q->next != NULL)
{
q = q->next;
if(memcmp(&(cin.sin), &(q->sin), sizeof(cin.sin)) != 0) //排除掉发信息的用户
{
if(sendto(sfd, cin.message, sizeof(cin.message), 0,\
(struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
}
}
//memcmp函数,会一个字节一个字节的比较两个变量
/**************************客户端输入*******************************/
char buf[128] = "";
usr.message[0] = 'C'; //标识符替换为'C'(通话)
bzero(usr.message+11, sizeof(usr.message+11)); //清空消息内容
fprintf(stderr,">>>");
scanf("%s", usr.message+11);
getchar();
if(usr.message[11] == 'Q') //当输入Q时,把标识符从 C 改为 Q
{
usr.message[0] = 'Q';
}
if(sendto(sfd, usr.message, sizeof(usr.message), 0, (struct sockaddr*)&(usr.sin), sizeof(usr.sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(usr.message[0] == 'Q') //当标识符为Q时,退出
{
break;
}
//以上内容包裹于死循环中
/**************************客户端接收***************************/
else if(cin.message[0] == 'C') //正常会话
{
fprintf(stderr,">>>%s:%s\n", cin.message+1, cin.message+11);
//昵称偏移1位,消息偏移11位
fprintf(stderr,">>>");
}
当有用户退出时,服务器群发消息
/*************************有用户退出**************************/
else if(cin.message[0] == 'Q')
{
Linklist *q = L;
char arr[8] = "下线";
//标识符置为'F',代表是服务器发送
cin.message[0] = 'F';
char *p = cin.message+11; //指向内容位
strcpy(p,cin.message+1); //把昵称发到消息位置
strcat(p, arr); //再把“下线”二字输入
printf("用户%s已下线\n",cin.message+1);
//发送给所有人
while(q->next != NULL)
{
q = q->next;
if(memcmp(&(cin.sin), &(q->sin), sizeof(cin.sin)) != 0) //排除掉发信息的用户
{
if(sendto(sfd, cin.message, sizeof(cin.message), 0, \
(struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
}
//把该用户从链表上删除
//找到链表上的位置
int pos = list_search_value(L, cin.sin);
//删除
delete_pos(L, pos);
}
服务器群发系统消息
//系统消息发送
sev.message[0] = 'F'; //设置为服务器发送
fprintf(stderr,"请输入群发命令>>>");
scanf("%s",sev.message+11);
getchar();
if(strcasecmp(sev.message+11, "Q") == 0) //输入Q退出
{
break;
}
Linklist *q = L;
while(q->next != NULL)
{
q = q->next;
if(sendto(sfd, sev.message, sizeof(sev.message), 0, \
(struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
}
以下为全代码
使用Linux的gcc编译时注意,客户端与服务器要分别与 linklist.c 文件联合编译,同时,要在语句后链接-pthread库
gcc 客户端.c linklist.c -o cli -pthread
gcc 服务器.c linklist.c -o ser -pthread
客户端
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"./linklist.h"
#include
//打印错误信息的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__", __LINE__);\
perror(msg);\
}while(0)
#define IP "0.0.0.0"
#define POST "8888"
int sfd;
void *MSG_rcv(void *arg) //接收服务器信息
{
struct usrmsg cin; //创建消息结构体
while(1)
{
bzero(cin.message, sizeof(cin.message)); //数据包信息部分清空
socklen_t addrlen = sizeof(cin.sin);
//接收服务器发送过来的数据包
//if(recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL) < 0)
if(recvfrom(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(cin.sin), &addrlen) < 0)
{
ERR_MSG("recvfrom");
return NULL;
}/*if(cin.message[0] == 'Q') //被服务器踢下线
{
exit(0);
}*/else if(cin.message[0] == 'C') //正常会话
{
fprintf(stderr,">>>%s:%s\n", cin.message+1, cin.message+11);
//昵称偏移1位,消息偏移11位
fprintf(stderr,">>>");
}else if(cin.message[0] == 'F') //服务器消息
{
fprintf(stderr,"\b\b\b系统消息:%s\n", cin.message+11);
fprintf(stderr,">>>");
}
}
//杀死主线程
}
//客户端
int main(int argc, const char *argv[])
{
//创建报式套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
fprintf(stderr,"create socket success\n");
//绑定服务器地址信息结构体--->非必须
//填充服务器的IP地址以及端口号
//定义用户信息变量
struct usrmsg usr;
// fprintf(stderr,"请输入端口号>>>");
// scanf("%d",&post);
usr.sin.sin_family = AF_INET;
usr.sin.sin_port = htons(atoi(POST)); //要整形,但是是字符串,所以用转换
usr.sin.sin_addr.s_addr = inet_addr(IP);
bzero(usr.message, sizeof(usr.message)); //初始化信息
// fprintf(stderr,"填充服务器端口成功\n");
//登录
fprintf(stderr,"请输入用户名>>>");
scanf("%s", usr.message+1);
getchar();
usr.message[0] = 'L'; //登录
sprintf(usr.message,"%c%s%s",usr.message[0], usr.message+1, usr.message+11);//把信息录入message
//发送信息,由服务器判断为初次登陆还是在线用户
if(sendto(sfd, usr.message, sizeof(usr.message), 0, (struct sockaddr*)&(usr.sin), sizeof(usr.sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
fprintf(stderr,"sendto success\n");
//开辟线程
pthread_t tid;
if(pthread_create(&tid, NULL, MSG_rcv, NULL) < 0)
{
perror("pthread_create");
return -1;
}
while(1)
{
char buf[128] = "";
usr.message[0] = 'C'; //标识符替换为'C'(通话)
bzero(usr.message+11, sizeof(usr.message+11)); //清空消息内容
fprintf(stderr,">>>");
scanf("%s", usr.message+11);
getchar();
if(usr.message[11] == 'Q')
{
usr.message[0] = 'Q';
}
if(sendto(sfd, usr.message, sizeof(usr.message), 0, (struct sockaddr*)&(usr.sin), sizeof(usr.sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(usr.message[0] == 'Q')
{
break;
}
}
close(sfd);
return 0;
}
服务器
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"./linklist.h"
#include
//打印错误信息的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__", __LINE__);\
perror(msg);\
}while(0)
#define IP "0.0.0.0"
#define POST "8888"
int sfd;
void *MSG_rcv(void *arg)
{
Linklist *L = (Linklist*)arg; //取出链表
struct usrmsg cin;
while(1)
{
bzero(cin.message, sizeof(cin.message)); //清空数据包
socklen_t addrlen = sizeof(cin.sin);
//接收客户端发来的数据包
if(recvfrom(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(cin.sin), &addrlen) < 0)
{
ERR_MSG("recvfrom");
return NULL;
}
printf("[%s:%d]%s:%s\n", inet_ntoa(cin.sin.sin_addr), ntohs(cin.sin.sin_port), cin.message+1, cin.message+11);
/**************************登录*******************************/
if(cin.message[0] == 'L')
{
//把信息挂载到链表上
list_intsrt_head(L, cin.sin); //头插
Linklist *q = L->next; //挪到第一,也就是刚刚插入的那一位
char arr[8] = "上线";
//发送给所有人
while(q->next != NULL) //如果后面没人了,不需要发送
{
q = q->next;
//标识符置为'F'
cin.message[0] = 'F';
char *p = cin.message+11; //指向内容位
strcpy(p, cin.message+1); //把昵称发到消息位置
strcat(p, arr);
if(sendto(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
strcpy(cin.message+11, "登录成功");
if(sendto(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(L->next->sin), sizeof(L->next->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
/***************************正常会话***************************/
else if(cin.message[0] == 'C')
{
Linklist *q = L; //挪到头部
//发送给所有人
while(q->next != NULL)
{
q = q->next;
if(memcmp(&(cin.sin), &(q->sin), sizeof(cin.sin)) != 0) //排除掉发信息的用户
{
if(sendto(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
}
}
/*************************有用户退出**************************/
else if(cin.message[0] == 'Q')
{
Linklist *q = L;
char arr[8] = "下线";
//标识符置为'F',代表是服务器发送
cin.message[0] = 'F';
char *p = cin.message+11; //指向内容位
strcpy(p,cin.message+1); //把昵称发到消息位置
strcat(p, arr); //再把“下线”二字输入
printf("用户%s已下线\n",cin.message+1);
//发送给所有人
while(q->next != NULL)
{
q = q->next;
if(memcmp(&(cin.sin), &(q->sin), sizeof(cin.sin)) != 0) //排除掉发信息的用户
{
if(sendto(sfd, cin.message, sizeof(cin.message), 0, (struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
}
//把该用户从链表上删除
//找到链表上的位置
int pos = list_search_value(L, cin.sin);
//删除
delete_pos(L, pos);
}
}
}
//服务器
int main(int argc, const char *argv[])
{
//创建链表,存放客户端信息
Linklist *L = list_create();
//创建报式套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
fprintf(stderr,"报式套接字创建成功\n");
struct usrmsg sev;
sev.sin.sin_family = AF_INET;
sev.sin.sin_port = htons(atoi(POST)); //要整形,但是是字符串,所以用转换
sev.sin.sin_addr.s_addr = inet_addr(IP);
fprintf(stderr,"服务器地址绑定完成\n");
//绑定服务器的地址信息结构体
if(bind(sfd, (struct sockaddr*)&sev.sin, sizeof(sev.sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//开辟线程
pthread_t tid;
if(pthread_create(&tid, NULL, MSG_rcv, (void*)L) < 0)
{
perror("pthread_create");
return -1;
}
fprintf(stderr,"线程创建成功\n");
fprintf(stderr,"****************服务器已启动************\n");
while(1)
{
//系统消息发送
sev.message[0] = 'F'; //设置为服务器发送
fprintf(stderr,"请输入群发命令>>>");
scanf("%s",sev.message+11);
getchar();
if(strcasecmp(sev.message+11, "Q") == 0)
{
break;
}
Linklist *q = L;
while(q->next != NULL)
{
q = q->next;
if(sendto(sfd, sev.message, sizeof(sev.message), 0, (struct sockaddr*)&(q->sin), sizeof(q->sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
}
}
//销毁表
list_delete_all(L);
close(sfd);
return 0;
}
函数包
#include
#include
#include"./linklist.h"
#include
#include
#include
#include
#include
#include
#include
#include"./linklist.h"
//创建
Linklist *list_create()
{
Linklist* L = (Linklist*)malloc( sizeof(Linklist) );
if( NULL == L)
{
printf("创建失败\n");
return NULL;
}
//初始化
L->len = 0;
L->next = NULL;
printf("链表创建成功\n");
return L;
}
//节点申请函数
Linklist* node_add(struct sockaddr_in e)
{
Linklist *p = (Linklist*)malloc(sizeof(Linklist));
if(NULL == p)
{
printf("节点申请失败\n");
return NULL;
}
//数据存放
p->sin = e;
p->next = NULL;
}
//判空
int list_empty( Linklist *L )
{
if(L->next == NULL)
{
printf("链表为空\n");
return 1;
}else
{
return 0;
}
}
//头插
int list_intsrt_head(Linklist *L ,struct sockaddr_in e)
{
if(NULL == L)
{
printf("链表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//完成头插
p->next = L->next;
L->next = p;
//表的变化
L->len++;
printf("信息录入成功\n");
}
//遍历
void list_show(Linklist *L)
{
if(NULL == L || list_empty(L)) //先判断非法
{
printf("表空或表非法,遍历失败\n");
return;
}
//遍历
printf("链表元素为:");
Linklist *q = L->next;
while(q != NULL)
{
//printf("%c\t",q->sin);
q = q->next;
}
printf("\n");
}
//尾插
int list_intsrt_tail(Linklist *L, struct sockaddr_in e)
{
//判空
if(NULL == L)
{
printf("所给表不合法\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//遍历指针指向最后一个结点
Linklist *q = L;
while(q->next != NULL)
{
q = q->next;
}
q->next = p;
//表的变化
L->len++;
}
//头删
int delete_head(Linklist *L)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("删除失败\n");
return -1;
}
Linklist *p = L->next; //标记
L->next = p->next; //也可以写成L->next->next
free(p); //释放内存
p = NULL;
//表的变化
L->len--;
printf("删除成功\n");
}
//任意位置插入(重点)
int list_intsrt_pos(Linklist *L, int pos, struct sockaddr_in e)
{
//逻辑判断
if(NULL==L || pos<1 || pos>L->len+1)
{
printf("表非法,无法插入\n");
return -1;
}
//申请节点
Linklist *p = node_add(e);
//查找要插入位置的前区节点
Linklist *q = find_node(L, pos-1);
//插入逻辑
p->next = q->next;
q->next = p;
//表的变化
L->len++;
printf("插入成功\n");
}
//按位置查找,返回查找到的节点
Linklist *find_node(Linklist *L, int pos)
{
//判断
if(NULL == L || pos<0 || pos>L->len)
{
printf("查找失败\n");
return NULL;
}
//查找节点
Linklist *q = L;
for(int i=1; i<=pos; i++ )
{
q = q->next;
}
return q; //返回找到的节点
}
//任意位置删除
int delete_pos(Linklist *L, int pos)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("表违法,不存在表或表为空\n");
return -1;
}
//任意位置删除
//找到要删位置的前区
Linklist *q = find_node(L, pos-1);
//删除逻辑
Linklist *p = q->next;
q->next = p->next;
free(p);
//表的变化
L->len--;
}
//按值查找,返回查找到的位置
int list_search_value(Linklist *L, struct sockaddr_in e)
{
//判定逻辑
if(NULL == L || list_empty(L))
{
printf("查找失败\n");
return -1;
}
//查找逻辑
int index = 1;
Linklist *q = L->next;
for(int i=1; i<=L->len; i++)
{
if(q->sin.sin_addr.s_addr == e.sin_addr.s_addr)
{
return i;
}else
{
q = q->next;
}
}
return 0;
}
//销毁表
void list_delete_all(Linklist *L)
{
if(NULL == L)
{
printf("表不合法");
}
for(int i=0; L->next != NULL; i++)
{
delete_head(L);
}
free(L);
printf("\n表已删除完毕\n");
}
头文件
#ifndef __LINKLIST__
#define __LINKLIST__
#include
#include
#include"./linklist.h"
#include
#include
#include
#include
#include
#include
#include
#include"./linklist.h"
//用户信息
struct usrmsg
{
//char type; //协议 登录:L,退出:Q,说话:C,服务器组发:F
//char name[10]; //用户名
struct sockaddr_in sin;
//char buf[128]; //消息内容
char message[139]; //消息主体 type + name + buf
};
typedef struct Node
{
union
{
struct sockaddr_in sin; //存入用户的地址信息和端口号
int len;
};
struct Node *next;
}Linklist; //链表的结构体变量名:Linklist
//申请节点
Linklist *node_add(struct sockaddr_in e);
//创建
Linklist *list_create();
//判空
int list_empty( Linklist *L );
//头插
int list_intsrt_head(Linklist *L ,struct sockaddr_in e);
//尾插
int list_intsrt_tail(Linklist *L, struct sockaddr_in e);
//遍历
void list_show(Linklist *L);
//头删
int delete_head(Linklist *L);
//按位置查找,返回查找到的节点(地址)
Linklist *find_node(Linklist *L, int pos);
//任意位置插入(重点)
int list_intsrt_pos(Linklist *L, int pos, struct sockaddr_in e);
//任意位置删除
int delete_pos(Linklist *L, int pos);
//按值查找,返回查找到的位置
int list_search_value(Linklist *L, struct sockaddr_in e);
//销毁表
void list_delete_all(Linklist *L);
#endif