该系统采用多线程方式实现不同主机之间的通信。
可实现多个用户间的通信以及可支持私聊模式。(输入用户名即可进入私聊模式,输入ALL返回群聊模式)
某一客户端退出并不影响其他客户的使用,所有用户全部退出,服务器关闭端口,结束进程。
服务器主线程程序框图
服务器接收连接线程程序框图(不完全,代码后续更新了,没改框图)
客户端主线程程序框图)
客户端消息接发线程程序框图
实验结果
服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#define PORT 8080 //1023~65535
#define MAX 100
#define IP "172.17.0.6"
#define USER 20
//设置总的用户连接数
pthread_mutex_t mm;//发送数据的时候加锁,防止阻塞
typedef struct
{
char buf[100];
}userbuf;//读写缓存
typedef struct sockaddr_in SIN;
SIN cli_addr[USER];//
int cli_fd[USER];//客户端套接字
int tag=0;//群聊or私聊判断标志位
int count=0;//退出聊天用户数
int user=0;//实际用户数
int sockfd;
typedef struct sockaddr SA;
char siliao[20]="[PRIVATE]";//私聊信息附加显示
userbuf username[USER];//存储用户民信息
userbuf username_plus[USER];//给用户名增加冒号显示
userbuf recvbuf[USER];//服务器端接收缓存
userbuf sendbuf[USER];//服务器端发送缓
int length[USER];
int rank(char a[])
{
int i;
for(i=0;i<user;i++)
if(strncmp(a,username[i].buf,length[i])==0)
return i;
return 88;
}
void *talk(void *arg)//每个客户端分配一个线程,实现消息的接收和转发
{
int num=tag++;//获取线程编号
int m;
int flag=88;
char name[100]={0};
memset(username[num].buf,0,100);
length[num]=recv(cli_fd[num],username[num].buf,MAX,0);//接收用户名
strcpy(username_plus[num].buf,username[num].buf);//
strcat(username_plus[num].buf,":");
strcpy(name,username_plus[num].buf);
while(1)
{
memset(recvbuf[num].buf,0,100);//
recv(cli_fd[num],recvbuf[num].buf,MAX,0);//接收客户端发送的信息
if(strncmp(recvbuf[num].buf,"quit",4)==0)//如果输入的是quit则表示该客户端结束聊天
{
count++;
close(cli_fd[num]);
pthread_exit(NULL);//线程结束
}
if(strlen(recvbuf[num].buf)==0)
break;
int flag=rank(recvbuf[num].buf);
if(flag==88)
{
strcpy(username[num].buf,name);
strcpy(sendbuf[num].buf,strcat(username[num].buf,recvbuf[num].buf));//用户信息和发送信息组装
system("date");
write(1,sendbuf[num].buf,100);
pthread_mutex_lock(&mm);//发送消息
for(m=0;m<num;m++)//接收用户消息发给其他用户
{
send(cli_fd[m],sendbuf[num].buf,strlen(sendbuf[num].buf),MSG_NOSIGNAL);//其中MSG_NOSIGNAL是为了解决有客户退出后仍然可以发消息的问题
}
for(m=num+1;m<user;m++)
{
send(cli_fd[m],sendbuf[num].buf,strlen(sendbuf[num].buf),MSG_NOSIGNAL);//发送接收到的消息给其他用户端
}
memset(sendbuf[num].buf,0,100);//清空接收的缓存
pthread_mutex_unlock(&mm);
} else{
while(1){
memset(recvbuf[num].buf,0,100);//
recv(cli_fd[num],recvbuf[num].buf,MAX,0);//接收客户端发送的信息
if(strncmp(recvbuf[num].buf,"quit",4)==0)//如果输入的是quit则表示该客户端结束聊天
{
count++;
close(cli_fd[num]);
pthread_exit(NULL);//线程结束
}
if(strncmp(recvbuf[num].buf,"ALL",3)==0)
break;
strcpy(username[num].buf,name);
strcat(username[num].buf,siliao);
strcpy(sendbuf[num].buf,strcat(username[num].buf,recvbuf[num].buf));//用户信息和发送信息组装
pthread_mutex_lock(&mm);
send(cli_fd[flag],sendbuf[num].buf,strlen(sendbuf[num].buf),0);
pthread_mutex_unlock(&mm);
}
}
}
close(cli_fd[num]);
pthread_exit(NULL);
}
void *user_connect(void *arg)//专门使用一个线程来处理连接,动态处理用户的连接
{
pthread_t tid[USER];//为每个客户端分配一个线程
sockfd=socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket create failure\r\n");
return 0;
}
//2、创建结构,初始化数据 struct sockaddr 使用 struct sockaddr_in
SIN ser_addr;//服务器端
ser_addr.sin_family = AF_INET;//选择ipv4协议族
ser_addr.sin_port=htons(PORT); //端口号要转换端绪,从小端绪转换从大端绪
ser_addr.sin_addr.s_addr=inet_addr(IP);//十进制的字符ip转换成网端数据格式
//3、绑定端口号和IP地址 bind()
if(bind(sockfd,(SA *)&ser_addr,sizeof(ser_addr)) == -1)
{
perror("bind failure\r\n");
return 0;
}
//4、创建监听 listen() 数量
if(listen(sockfd, USER) == -1)
{
perror("listen failure\r\n");
return 0;
}
printf("waiting connect\r\n");
int i;
for(i=0;i<USER;i++)
memset(&cli_addr[i],0,sizeof(cli_addr[i]));
while(1)
{
int len=sizeof(cli_addr[user]);
cli_fd[user]=accept(sockfd,(SA *)&cli_addr[user],&len);//接受客户端的连接
if(cli_fd[user] == -1)
{
perror("wait failure\r\n");
return 0;
}
else{
printf("THE %d USER CONNECT SUCCESS!\n",user+1);
pthread_create(&tid[user],NULL,talk,NULL);
}
user++;
}
}
int main(int argc,char *argv[])
{
//1、创建套接字文件,返回套接字文件描述符 socket()
pthread_mutex_init(&mm, NULL);
pthread_t CON;
pthread_create(&CON,NULL,user_connect,NULL);//分配连接的线程
sleep(30);//30s等待连接
while(count!=user);
printf("-------**********************************-------\n");
printf("-------WELCOME JOIN DADONG TALK ROOM AGAIN-------\n");
//7、关闭文件描述符 close(
close(sockfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/ip.h>
#define IP "172.17.0.6"
#define PORT 8080
#define MAX 100
int final=0;
// 创建socket套接字文件,并连接
// 接受数据 client 客户端
typedef struct sockaddr_in SIN;
typedef struct sockaddr SA;
SIN ser_addr;
int sockfd;
void *message(void *arg)
{
printf("thread creat success!\n");
char buf[100];
int signal;
while(1)
{
if(final==1)
break;
memset(buf,0,100);
signal=recv(sockfd,buf,MAX,0);
if(signal!=0){
system("date");
write(1,buf,strlen(buf));
memset(buf,0,100);}
}
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
//1、创建套接字文件,返回套接字文件描述符 socket()
sockfd=socket(AF_INET, SOCK_STREAM, 0);
pthread_t tid;
if(sockfd == -1)
{
perror("socket create failure\r\n");
return 0; }
//2、创建结构,初始化数据 struct sockaddr 使用 struct sockaddr_in
SIN ser_addr;
ser_addr.sin_family = AF_INET;//选择ipv4协议族
ser_addr.sin_port=htons(PORT); //端口号要转换端绪,从小端绪转换从大端绪
ser_addr.sin_addr.s_addr=inet_addr(IP);//十进制的字符ip转换成网端数据格式 服务端IP
int len=sizeof(ser_addr);
if(connect(sockfd,(SA *)&ser_addr,len)==-1)
{
perror("connect failure\r\n");
return 0;
}else
{
printf("WELCOME TO DADONG TALK ROOM!\r\n");
printf("Please sign you name:");
char temp[20]={0};
memset(temp,0,100);
scanf("%s",temp);
send(sockfd,temp,strlen(temp),0);//发送信息
pthread_create(&tid,NULL,message,NULL);
}
char wbuf[100]={0};
while(1)
{
memset(wbuf,0,100);
read(0,wbuf,100);
if(strncmp(wbuf,"quit",4)==0)
{
send(sockfd,wbuf,strlen(wbuf),0);
final=1;
close(sockfd);
return 0;
}
send(sockfd,wbuf,strlen(wbuf),MSG_NOSIGNAL);
memset(wbuf,0,100);
usleep(20);
}
close(sockfd);
return 0;
}