基于UDP的简易网络聊天室

聊天室基本功能:

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<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include"./linklist.h"
#include<net/if.h>

//打印错误信息的宏函数
#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<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include"./linklist.h"
#include<net/if.h>

//打印错误信息的宏函数
#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<stdio.h>
#include<string.h>
#include"./linklist.h"
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<pthread.h>
#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<stdio.h>
#include<string.h>
#include"./linklist.h"
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<pthread.h>
#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

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
下面是基于UDP简易聊天室的Python代码示例: 服务器端代码: ```python import socket import threading # 客户端信息列表 clients = [] # 创建UDP Socket对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定IP地址和端口号 host = '127.0.0.1' port = 8888 server_socket.bind((host, port)) # 接收数据的函数 def receive_data(): while True: # 接收数据报 data, addr = server_socket.recvfrom(1024) message = data.decode('utf-8') print(f'{addr[0]}:{addr[1]}说:{message}') # 转发数据给所有客户端 for client in clients: if addr != client: server_socket.sendto(data, client) # 发送数据的函数 def send_data(): while True: # 从标准输入获取数据 message = input() # 将数据发送给所有客户端 for client in clients: server_socket.sendto(message.encode('utf-8'), client) if __name__ == '__main__': # 启动接收数据的线程 receive_thread = threading.Thread(target=receive_data) receive_thread.start() while True: # 接收客户端连接 data, addr = server_socket.recvfrom(1024) if addr not in clients: clients.append(addr) print(f'{addr[0]}:{addr[1]}已连接') # 启动发送数据的线程 send_thread = threading.Thread(target=send_data) send_thread.start() ``` 客户端代码: ```python import socket import threading # 创建UDP Socket对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 服务器IP地址和端口号 server_host = '127.0.0.1' server_port = 8888 # 发送数据的函数 def send_data(): while True: # 从标准输入获取数据 message = input() # 将数据发送到服务器 client_socket.sendto(message.encode('utf-8'), (server_host, server_port)) # 接收数据的函数 def receive_data(): while True: # 接收数据报 data, addr = client_socket.recvfrom(1024) message = data.decode('utf-8') print(f'{addr[0]}:{addr[1]}说:{message}') if __name__ == '__main__': # 启动发送数据的线程 send_thread = threading.Thread(target=send_data) send_thread.start() # 启动接收数据的线程 receive_thread = threading.Thread(target=receive_data) receive_thread.start() ``` 以上是基于UDP简易聊天室的Python代码示例,您可以根据需求进行相应的修改和完善。需要注意的是,这只是一个简单的示例,实际应用中还需要考虑一些其他的因素,比如数据的安全性、数据的可靠性等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老K殿下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值