LINUX 网络编程 聊天室(一个服务器 多客户端通信)

聊天室程序要求
(1)要求实现一个聊天室程序,至少三个客户端可以同时进入聊天室(在服务器开启状态下,客户端随时可进入该聊天室)
(2)服务器程序有日志记录功能,日志中记录每个客户端接入的端口和IP地址以及聊天话术

问题分析
客户端负责发送信息以及接收信息
服务器端负责对客户端发送的信息进行转发,由于是群聊在转发时应当除去发送该信息的客户端
服务器端

/*************************************************************************
    > File Name: server_.c
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年12月16日 星期一 20时01分10秒
 ************************************************************************/
#include"hand.h"
#include"aeh.h"
#include"link.h"
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>
#define SERV_PORT 5050

#define BACKLOG 5 
#define BUFSIZE 150 

struct thread_arg{
	int new_fd;
	LINK head; 
	int fd2;//重定向到日志文件 
};

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int number = 0;
void *thread_rcv (void *);    
int name_lens(const char *);  //获取客户端信息中的名字长度
int main(int argc,char *argv[])
{
	int fd;
	int socket_fd;
	struct sockaddr_in sin,cin;	 //以IPV4方式  
	struct thread_arg arg;
	//日志文件
	fd = open("journal",O_RDWR | O_CREAT |O_APPEND, 0777);
	if (fd < 0)
		err_sys("open");	
	/*  将标准输出从定向到日志文件*/
	close(1);
	arg.fd2 = dup2(fd,1);
	/*  创建套接字*/
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (socket_fd == -1)
		err_sys("socket");
	//绑定 本地IP与端口 
	/* IP地址快速重用*/
	int b_reuse = 1;
	setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); 
	bzero(&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	/*  可绑定在任意IP*/
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(SERV_PORT);
	if (bind(socket_fd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
		err_sys("bind");
	//设置监听 
	if (listen(socket_fd, BACKLOG) == -1)
		err_sys("listen");
	int new_fd = -1;
	socklen_t len = sizeof(struct sockaddr);
	//设置线程的分离属性
	int err;	
	pthread_t tid;
	pthread_attr_t tattr;
	err = pthread_attr_init(&tattr);
	if (err != 0){
		err_exit(err, "pthread_attr_init");
	}
	err = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	if (err != 0){
		err_exit(err, "pthread_attr_setdetachstate");
	}
	LINK head = NULL;
	while(1)
	{
		new_fd = accept(socket_fd, (struct sockaddr *)&cin, &len);
		if(new_fd == -1){
			perror("accept");
			continue;
		}
		create_link(&head,new_fd);
		printf("client IP_ADDR:%s\n client PORT:%d\n new_fd:%d\n",
	 			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),new_fd);
		/* 将新的套接字 加入链表*/
		arg.head = head;
		arg.new_fd = new_fd;
		/* 创建 处理该套接字对应客户端的线程*/
		err = pthread_create(&tid, &tattr, thread_rcv,(void *)&arg);
		if (err != 0)
			err_exit(err, "pthread_rcv"); 
		//对当前连接人数 进行同步
		pthread_rwlock_wrlock(&rwlock);
		number++;
		pthread_rwlock_unlock(&rwlock);
		printf("Client number is--------------%d\n",number);
		fflush(stdout);
	}
	close(socket_fd);
	return 0;
}
void * thread_rcv(void *arg)
{	
	char buf[BUFSIZE];
	struct thread_arg arg_= *(struct thread_arg*)arg;
	int ret = -1;

	while(1)
	{
		do{
			bzero(buf,BUFSIZE);
			ret = read(arg_.new_fd, buf, BUFSIZE-1);		
		}while(ret < 0 && errno == EINTR);
		if (ret < 0){
			perror("read");
			break;
		}
		if (!ret) break;	//客户端断开连接 
		//将客户端发送的数据写入日志文件中
		write(arg_.fd2, buf, ret);
		//给其他客户端转发信息
		LINK p = (arg_.head)->next;
		while(p != NULL)  
		{
			if(p->sock_fd != arg_.new_fd){
				ret = write(p->sock_fd, buf, strlen(buf));
				if(ret < 0){
					perror("write p->sock_fd");
 			 		break;
		 	 	}				
			} 
			p = p->next;
		}
		/* 将信息中的名字去掉 如果是退出标致则退出*/
		if (!strncmp(buf+name_lens(buf),"end",3)){ 
			printf("new_fd:%d exit--\n",arg_.new_fd);
			break;
		}		
	}
	/*  退出 
	 *  当前人数 -1
	 *  在链表中删除当前socket_fd 
	 *	关闭当前socket_fd 
	 *	刷新缓冲区
	 *  */
	pthread_rwlock_wrlock(&rwlock);
	number--;
	pthread_rwlock_unlock(&rwlock);
	free_link(arg_.head,arg_.new_fd);
	close(arg_.new_fd);
	fflush(stdout);
	pthread_exit(NULL);
}
int name_lens(const char *str)
{
	int n=0;
	while (*str != ':')
	{
		*str++;
		n++;
	}
	return n+1;
}

服务器端

/*************************************************************************
    > File Name: client3.c
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年12月17日 星期二 13时19分44秒
 ************************************************************************/
//多路IO复用的客户端
#include<stdio.h>
#include<stdlib.h>
#include"aeh.h"
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

#define SERV_IP_ADDR "192.168.188.79"
#define SERV_PORT 5050 

#define BUFSIZE 128
#define MAXSIZE 150
int name_lens(const char *);
int main(int argc,char *argv[])
{
	int sock_fd;
	struct sockaddr_in sin;
	//创建套接字 
	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(sock_fd == -1)
		err_sys("sock");
	//连接 
	bzero(&sin, sizeof(sin));
	sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
	sin.sin_port = htons(SERV_PORT);
	sin.sin_family = AF_INET;
	if (connect(sock_fd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
		err_sys("connect");	
	int ret = -1;
	/* IO多路复用*/
	fd_set rdfds;
	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	char name[20]={0};  //获取在服务器断开连接的客户端姓名
	char buf[MAXSIZE]= {0};
	char client_buf[MAXSIZE]={0};
	printf("input your name:");
	fgets(client_buf,20,stdin);	 //去掉fgets输入的'\n'
	if(client_buf[strlen(client_buf)-1] == '\n'){
		client_buf[strlen(client_buf)-1] = '\0';
	}
	strcat(client_buf,":");
	while(1)
	{
		FD_ZERO(&rdfds);
		FD_SET(0, &rdfds);
		FD_SET(sock_fd, &rdfds);			
		ret = select(sock_fd+1, &rdfds, NULL, NULL, &tv); 
		if (ret == -1)
			err_sys("select");
		if (!ret)    //时间到达返回 则重新等待
			continue;
		if (FD_ISSET(0,&rdfds)){//标准输入上有数据 从键盘有数据输入
	/*		do{				
 				bzero(buf, sizeof(buf));
				ret = read(STDIN_FILENO, buf, BUFSIZE-1);
			} while (ret < 0 && errno == EINTR);
			if (ret < 0 ){
 				perror("read");
				continue;
			}  */ 
			fgets(buf,BUFSIZE-1, stdin);
			strncat(client_buf,buf,strlen(buf));
			// 将数据写入到 套接字 
			if (write(sock_fd, client_buf, strlen(client_buf)) < 0) {
				perror("write");
 		 		continue;
			}
			//保留姓名 
			client_buf[strlen(client_buf)-strlen(buf)] = '\0';
			if (!strncmp(buf,"end",3)){
 		 		printf("input exit......\n");
				break;
			}			
		}
		//当服务器端有数据发送时
		if (FD_ISSET(sock_fd, &rdfds)){
			do{
				bzero(buf,MAXSIZE);
				ret = read(sock_fd, buf, MAXSIZE);
			}while(ret < 0 && errno == EINTR);
			if (ret < 0){
 				perror("read");
				break;
			}
			if (!ret) {//服务器已断开{
 				printf("Server disconnect.....\n");			
				break;	
			}
			if (!strncmp(buf+name_lens(buf),"end",3)){
				memcpy(name,buf,name_lens(buf)-1);
				printf("%s exit....\n",name);
				continue;
			}
			printf("%s",buf);			
		}
	}
	close(sock_fd);	
	return 0;
}
int name_lens(const char *str)
{
	int n = 0;
	while(*str != ':')
	{
		*str++;
		n++;
	}
	return n+1;
}

相关头文件
aeh.h

/*************************************************************************
    > File Name: aeh.h
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年12月11日 星期三 21时29分34秒
 ************************************************************************/
/* 
 *出错处理
 *
 * */ 

#ifndef __AEH_H__
#define __AEH_H__ 

#include<errno.h>
#include<stdarg.h>
#include<stdlib.h>

void 
err_sys(const char *str)
{
	perror(str);
	exit(EXIT_FAILURE);
}

void 
err_exit(int errnum, const char *str)
{
	errno = errnum;
	perror(str);
	exit(EXIT_FAILURE);
}

#endif

/*防止头文件重复定义*/

link.h

/*************************************************************************
    > File Name: link.h
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年12月17日 星期二 10时01分04秒
 ************************************************************************/

#ifndef  __LINK_H__
#define	 __LINK_H__
typedef struct  link 
{
	int sock_fd;
	struct link *next;
}link_,*LINK;
extern void create_link(LINK *head,int fd);
extern void print_link(LINK head);
extern void free_link(LINK head,int fd);
extern int link_number(LINK head);
#endif
/*防止头文件重复定义*/

link.c

/*************************************************************************
    > File Name: link.c
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年12月17日 星期二 08时53分45秒
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct link 
{
	int sock_fd;
	struct link *next;
}link_,*LINK;
void create_link(LINK *,int );
void print_link(LINK);
void free_link(LINK ,int );
void create_link(LINK *head, int fd)
{
	LINK p,end;	
	if (*head == NULL)
	{
		p = (LINK)malloc(sizeof(link_));
		p->sock_fd = fd;		
		*head = (LINK)malloc(sizeof(link_));
		(*head)->next = p;
		p->next = NULL;
	}
	else  
	{
		end = *head;
		while(end->next != NULL)
		{
			end = end->next;
		}
		p = (LINK)malloc(sizeof(link_));
		p->sock_fd = fd;
		end->next = p;
		p->next = NULL;
	}
}
void print_link(LINK head)
{
	if (head == NULL){
		return ;
	}		
	LINK p = head->next;
	while(p != NULL)
	{
		printf("%d\n",p->sock_fd);
		p = p->next;
	}
}
void free_link(LINK head,int fd)
{
	LINK pre,end;
	pre = head;
	end = head->next;
	while (end != NULL)
	{
		if (end->sock_fd == fd)
		{
			pre->next = end->next;
			free(end);
		}
		pre = end;
		end = end->next;
	}
}

int link_number(LINK head)
{
	int n = 0;
	while(head->next != NULL)
		n++;
	return n;
}

具体运行效果

启动服务器
在这里插入图片描述
创建多个客户端
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值