C语言 基于UDP的局域网多人聊天室的简单代码

阅读: 评论:0

C语言 基于UDP的局域网多人聊天室的简单代码

C语言 基于UDP的局域网多人聊天室的简单代码

[C]基于UDP的局域网多人聊天室的简单代码 ..编程小白,代码可能比较冗长,请各位大佬指出不足 [*・ω・]

    • 运行现象
    • 思路
    • 程序源码(LINUX)
      • 1.head.h(头文件)
      • 2.datalink.c (存放着各种自定义函数接口)
      • 3.server.c(服务端)
      • 4.client.c(客户端)
      • 5.Makefile()
    • 代码中的不足之处
    • 源码文件链接
    • 废话

运行现象

1.当客户端(client)打开,输入昵称后服务器(server)端以及客户端的现象


2.当客户端发送消息,以及服务端发送消息,以及客户端退出时现象


思路

1.因为服务端和客户端都需要具备接收数据和发送数据的能力,因此需要使用多个线程分别完成接收和发送的操作(相关函数pthread_create())
2.因为需要通过服务端给每个用户群发消息,所以用户的信息需要储存,用数组和链表都可以,这里本人用的是链表
3.因为UDP协议发送信息时用的是sedto()函数,每次发送数据都需要提供发送目标的ip和端口号,因此我们需要定义一个结构体来存放每个登录用户的端口号和ip号等信息.

程序源码(LINUX)

1.head.h(头文件)

#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <linux/in.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>//发送信息的标志变量
#define LOGIN	0
#define QUIT	1
#define SESSION 2
#define SERVER	3
#define	RENAME	4//存放客户端发送信息
typedef struct msg{int flag;char name[16];char info[64];
}msg;//用来保存单个用户的ip,端口号等信息
typedef struct usermsg{char user_ip[32];unsigned short user_port;char user_name[16];
}usermsg;//存放所有用户信息的链
typedef struct userlink{usermsg user;struct userlink *next;
}userlink;int link_creat(userlink **);			//创建链表
int link_add(userlink *,usermsg *,int);	//添加用户
int myperror(int,char*);				//报错信息(写到后面忘了这个函数了,后面写的函数几乎都没有做判断...)
int link_delete(userlink*,usermsg*,int);//用户退出,删除退出用户的信息
int mass(userlink*,usermsg*,msg*,int);	//服务器给各个客户端群发信息的函数
void* client_thread(void*);				//客户端thread_create()创建线程函数的最后一个参数,该线程用于接收来自服务器的消息
userlink* link_select(userlink*,usermsg*);//查找指定用户的函数
void* clientmsg_thread(void *);			//服务端thread_create()创建线程函数的最后一个参数,该线程用于给客户端发送服务端的消息
void* servermsg_thread(void *);			//服务端thread_create()创建线程函数的最后一个参数,该线程用于给客户端发送其他客户端的消息
#endif

2.datalink.c (存放着各种自定义函数接口)

#include"head.h"
//自定义的报错函数
int myperror(int flag,char *name){if(flag < 0){printf(">>server : %s is error",name);perror(":");exit(-1);return 0;}else{printf(">>server : %s is succeedn",name);return 0;}
}
//创建链表
int link_creat(userlink **p){if((*p) == NULL){(*p) = (userlink*)malloc(sizeof(userlink));(*p) -> next = NULL;(*p) -> user.user_port = 0;return 0;}puts("==SERVER== :link is exit");return 0;
}
//添加用户,并给所有人发送登录信息
int link_add(userlink *p,usermsg *umsg,int sockfd){if(p){userlink *new = (userlink*)malloc(sizeof(userlink));new -> next = p -> next;p -> next = new;p -> user.user_port ++;strcpy(new -> user.user_ip,umsg -> user_ip);new -> user.user_port = umsg -> user_port;strcpy(new -> user.user_name,umsg->user_name);//在服务端打印登入用户的名字,ip,以及端口号printf(">>server : %s is login,--[ip : %s][port : %u]--n",umsg -> user_name,umsg -> user_ip,umsg -> user_port);new = NULL;//定义一个新的数据包msg smsg;//给数据包定义一个flag标志位,然后往其中填充一条"xxx"用户登录的消息smsg.flag = SERVER;memset(smsg.info,'',sizeof(smsg.info));sprintf(smsg.info,">> %s << is login",umsg -> user_name);//使用mass()群发函数,发给所有用户(该函数在下方定义)mass(p,NULL,&smsg,sockfd);return 0;}else{puts("==SERVER== : link not exist");return 0;}
}
//寻找用户的前一个用户
userlink* link_select(userlink *p,usermsg *umsg){while(p -> next){if(!strcmp(p->next->user.user_ip,umsg->user_ip)&&p->next->user.user_port == umsg->user_port ){return p;break;}p = p -> next;}return NULL;
}
//用户退出
int link_delete(userlink *head,usermsg *umsg,int sockfd){userlink *temp,*p = NULL;if(head){if((p = link_select(head,umsg))){temp = p -> next;p -> next = temp -> next;//在服务端打印一下退出用户的名字printf(">>>server : user[ %s ] will be quitn",temp ->user.user_name);//定义一个新的数据包,操作和上文中的登录操作类似,将数据包填充后调用mass函数群发msg bmsg;bmsg.flag = QUIT;strcpy(bmsg.name,temp -> user.user_name);mass(head,umsg,&bmsg,sockfd);free(temp);temp = NULL;head -> user.user_port--;}else{puts("==SERVER== : user not exist");return 0;}}else{puts("==SERVER== : link not exist");return 0;}
}
//给所有用户转发消息
int mass(userlink *p,usermsg *umsg,msg *bmsg,int sockfd){struct sockaddr_in clientaddr;socklen_t clientlen = sizeof(clientaddr);//当该信息来自客户端时if(bmsg->flag == SESSION){userlink *namep = NULL;if((namep = link_select(p,umsg))){strcpy(bmsg->name,namep->next->user.user_name);}else{puts("==SERVER== : error,the user is exit???");}//当该信息来自服务端时}else if(bmsg -> flag == SERVER){strcpy(bmsg->name,"server");//当该信息是客户端的退出指令时}else if(bmsg -> flag == QUIT){memset(bmsg->info,'',strlen(bmsg->info));sprintf(bmsg->info,"[%s is quit]",bmsg->name);strcpy(bmsg->name,"server");}//在服务端打印一下这条信息的内容printf(">>> [%s] : %sn",bmsg->name,bmsg->info);p = p->next;//遍历整个链表,将打包好的信息发送给各个客户端while(p){clientaddr.sin_family = AF_INET;clientaddr.sin_port = htons(p->user.user_port);clientaddr.sin_addr.s_addr = inet_addr(p->user.user_ip);sendto(sockfd,bmsg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientlen);p = p-> next;}
}

3.server.c(服务端)

#include"head.h"//线程信号量
sem_t sem_server;//线程从该结构体中拿数据
struct thread_pass{int sockfd;userlink *head;msg *bmsg;usermsg *umsg;
}pass;//同上,也是用来给线程拿数据的
struct buf{int sockfd;userlink *head;
}bufpack;int main(int argc, const char *argv[])
{//创建链表userlink *head = NULL;link_creat(&head);//生成套接字文件描述符int sockfd = socket(AF_INET,SOCK_DGRAM,0);myperror(sockfd,"socket");struct sockaddr_in serveraddr;//服务端的ip,协议族,端口号等信息serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);myperror(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)),"bind");//用来接收客户端的ip,端口号信息的结构体struct sockaddr_in clientaddr;socklen_t clientlen = sizeof(clientaddr);//自定义的存放客户端信息的结构体,便于往链表中填充usermsg umsg;//存放正文信息以及信息标志符的结构体数据包msg bmsg,smsg;pthread_t tid_send_clientmsg,tid_send_servermsg;bufpack.head = head;bufpack.sockfd = sockfd;//创建两个线程pthread_create(&tid_send_servermsg,NULL,servermsg_thread,NULL);pthread_create(&tid_send_clientmsg,NULL,clientmsg_thread,NULL);//线程分离pthread_detach(tid_send_clientmsg);pthread_detach(tid_send_clientmsg);//初始化线程信号量sem_init(&sem_server,0,0);//循环接收数据while(1){recvfrom(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&clientaddr,&clientlen);//将存放在clientaddr中的客户端ip和端口号存放在我们自定义的umsg结构体里strcpy(umsg.user_ip,(char*)inet_ntoa(clientaddr.sin_addr.s_addr));umsg.user_port = ntohs(clientaddr.sin_port);switch(bmsg.flag){//当信号量为LOGIN时,为用户登录,添加链表case LOGIN:strcpy(umsg.user_name,bmsg.info);link_add(head,&umsg,sockfd);break;//为QUIT为用户退出,删除链表中该用户的信息case QUIT:	link_delete(head,&umsg,sockfd);break;//SESSION为用户发送过来的正文消息,释放一个信号量,交给线程处理case SESSION:pass.head = head;pass.bmsg = &bmsg;pass.umsg = &umsg;pass.sockfd = sockfd;sem_post(&sem_server);}}return 0;
}
//用来群发服务端消息的线程
void* servermsg_thread(void *p){msg smsg;smsg.flag = SERVER;while(1){//fgets阻塞,从终端的输入缓存区获取信息,然后发送,没有信息则阻塞fgets(smsg.info,sizeof(smsg.info),stdin);(smsg.info)[strlen(smsg.info) - 1] = '';mass(bufpack.head,NULL,&smsg,bufpack.sockfd);}return 0;
}
//用啦群发客户端消息的线程
void* clientmsg_thread(void *p){while(1){//当只有主线程中信号量释放时才会向下运行,否则阻塞sem_wait(&sem_server);mass(pass.head,pass.umsg,pass.bmsg,pass.sockfd);	}
}

4.client.c(客户端)

#include"head.h"int main(int argc, const char *argv[])
{//创建套接字文件描述符int sockfd = socket(AF_INET,SOCK_DGRAM,0);myperror(sockfd,"socket");struct sockaddr_in serveraddr;pthread_t tid;msg bmsg;//服务端的各种信息,用于后面sendto()函数serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//输入名字的提示语句printf(">>>name (Less than 15 character) : ");fgets(bmsg.info,16,stdin);(bmsg.info)[strlen(bmsg.info) - 1] = '';bmsg.flag = LOGIN;sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));//创建线程pthread_create(&tid,NULL,client_thread,&sockfd);pthread_detach(tid);while(1){//从终端输入缓存区获取信息,没有则阻塞fgets(bmsg.info,sizeof(bmsg.info),stdin);(bmsg.info)[strlen(bmsg.info) - 1] = '';//当输入#quit  ---这一退出指令时执行的语句,将标志位赋值为QUITif(!strcmp(bmsg.info,"#quit")){bmsg.flag =	QUIT; sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));sleep(1);pthread_cancel(tid);break;}else{bmsg.flag = SESSION;}sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));}return 0;
}
//用于接收服务端发来的消息,并打印在终端上
void* client_thread(void *p){msg recvmsg; while(1){recvfrom(*(int *)p,&recvmsg,sizeof(recvmsg),0,NULL,NULL);printf(">>> [%s] : %sn",recvmsg.name,recvmsg.info);}	
}

5.Makefile()

.PHONY:all
all:client server
OBJS1=client.o datalink.o
OBJS2=server.o datalink.o
CC=gcc
CFLAGS=-gclient:$(OBJS1)$(CC) $(CFLAGS) $^ -o $@ -lpthread
server:$(OBJS2)$(CC) $(CFLAGS) $^ -o $@ -lpthread
.PHONY:clean
clean:rm $(OBJS)

代码中的不足之处

1.没有输入字符溢出时的优化语句
2.用户重名无法判断
3.服务端没有正常退出的语句
4.代码冗长,定义的变量以及结构体太多了,不方便阅读

源码文件链接

源文件下载地址

废话

本人为刚学嵌入式不久的编程小白,目前只学习了一些C语言,文件IO和TCP、UDP协议的一些基础知识,代码写的很烂,非常渴望各位大佬批评以及指点。
最后,非常感谢各位大佬点开这篇帖子,你们的点击也一定是我学习的动力,感谢✧ʕ̢̣̣̣̣̩̩̩̩·͡˔·ོɁ̡̣̣̣̣̩̩̩̩✧

本文发布于:2024-01-29 11:55:56,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170650056015100.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:局域网   聊天室   语言   代码   简单
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23