1.当客户端(client)打开,输入昵称后服务器(server)端以及客户端的现象
2.当客户端发送消息,以及服务端发送消息,以及客户端退出时现象
1.因为服务端和客户端都需要具备接收数据和发送数据的能力,因此需要使用多个线程分别完成接收和发送的操作(相关函数pthread_create())
2.因为需要通过服务端给每个用户群发消息,所以用户的信息需要储存,用数组和链表都可以,这里本人用的是链表
3.因为UDP协议发送信息时用的是sedto()函数,每次发送数据都需要提供发送目标的ip和端口号,因此我们需要定义一个结构体来存放每个登录用户的端口号和ip号等信息.
#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
#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 succeed\n",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,'\0',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 quit\n",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,'\0',strlen(bmsg->info));
sprintf(bmsg->info,"[%s is quit]",bmsg->name);
strcpy(bmsg->name,"server");
}
//在服务端打印一下这条信息的内容
printf(">>> [%s] : %s\n",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;
}
}
#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] = '\0';
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);
}
}
#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] = '\0';
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] = '\0';
//当输入#quit ---这一退出指令时执行的语句,将标志位赋值为QUIT
if(!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] : %s\n",recvmsg.name,recvmsg.info);
}
}
.PHONY:all
all:client server
OBJS1=client.o datalink.o
OBJS2=server.o datalink.o
CC=gcc
CFLAGS=-g
client:$(OBJS1)
$(CC) $(CFLAGS) $^ -o $@ -lpthread
server:$(OBJS2)
$(CC) $(CFLAGS) $^ -o $@ -lpthread
.PHONY:clean
clean:
rm $(OBJS)
1.没有输入字符溢出时的优化语句
2.用户重名无法判断
3.服务端没有正常退出的语句
4.代码冗长,定义的变量以及结构体太多了,不方便阅读
源文件下载地址
本人为刚学嵌入式不久的编程小白,目前只学习了一些C语言,文件IO和TCP、UDP协议的一些基础知识,代码写的很烂,非常渴望各位大佬批评以及指点。
最后,非常感谢各位大佬点开这篇帖子,你们的点击也一定是我学习的动力,感谢✧ʕ̢̣̣̣̣̩̩̩̩·͡˔·ོɁ̡̣̣̣̣̩̩̩̩✧