聊天室程序要求
(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;
}
具体运行效果
启动服务器
创建多个客户端