基于linux下的聊天程序设计与实现
一、 实现的内容及要求:
用C语言编程实现linux简单的聊天室功能。
- 用户程序命名为client.c;服务器程序命名为server.c
- 绑定端口等信息见实验方法内容;
- 要求client可以通过socket连接server
- 在client,提示输入服务器ip
- 若连接server 的socket建立成功,返回提示信息
- Client输入的聊天内容在client端(多个client端)和server端同时显示;
- 多个client可同时接入server,进入聊天室,最多支持20个client;
- Client端输入quit退出连接,server端提示client退出。
- 可选择使用多线程实现多客户端;
- 其他细节见输出结果;
二、方法及内容
1. 需要的头文件
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<pthread.h>
2. 主要的常量变量
客户端:
#define TRUE 1
#define PORT 5000
int quit=0; //quit表示是否用户确定退出
服务器端:
#define MAXLINE 1000 //在一条消息中最大的输出字符数
#define LISTENQ 20 //最大监听队列
#define PORT 5000 //监听端口
#define MAXFD 20 //最大的在线用户数量
void *get_client(void *);
int i,maxi=-1;//maxi表示当前client数组中最大的用户的i值
int client[MAXFD];
3.主要模块
客户端:
intmain(void)
void*get_server(void* sockfd)
//get_server函数,用于接受服务器转发的消息
服务器端:
intmain()
void*get_client(void *sockfd) //运行get_client函数,处理用户请求
三.完整代码实现
注:参考socket编程
/******* 客户端程序client.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define TRUE 1
#define PORT 5000
static int sockfd;
void recvfromserver() //接受服务器消息线程入口函数
{
charmes[1024];
int nbytes=0;
while(1)
{
memset(mes,0,sizeof(mes));
nbytes=read(sockfd,mes,sizeof(mes));
if(nbytes>0)
{
mes[nbytes]='\0';
printf("%s\n",mes);
}
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
// int sockfd;
charbuffer[1024];
structsockaddr_in server_addr;
structhostent *host;
intportnumber,nbytes;
charstrhost[16];
charclientname[20];
charmes[1024];
int thr_id; /* thread ID for the newly createdthread */
pthread_t p_thread; /* thread's structure */
if(argc!=1)
{
fprintf(stderr,"Usage:%s\a\n",argv[0]);
exit(1);
}
printf("请输入服务器ip地址\n");
scanf("%s",strhost);
if((host=gethostbyname(strhost))==NULL)
{
fprintf(stderr,"Gethostnameerror\n");
exit(1);
}
/* 客户程序开始建立 sockfd 描述符 */
printf("正在建立套接口...\n");
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"SocketError:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序填充服务端的资料 */
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr=*((structin_addr *)host->h_addr);
printf("套接口创建成功,正在链接服务器...\n");
/* 客户程序发起连接请求 */
if(connect(sockfd,(structsockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"ConnectError:%s\a\n",strerror(errno));
exit(1);
}
/* 连接成功了 */
printf("链接服务器成功\n欢迎来到聊天室\n");
printf("请输入你的用户昵称\n");
scanf("%s",clientname);
// write(sockfd,clientname,sizeof(clientname));
printf("\n\n开始聊天吧(\"Quit\"断开连接)\n\n");
thr_id =pthread_create(&p_thread, NULL, recvfromserver, NULL);
while(1)
{
memset(buffer,0,sizeof(buffer));
memset(mes,0,sizeof(mes));
scanf("%s",buffer);
strcat(mes,clientname);
strcat(mes,":");
strcat(mes,buffer);
// printf("main thread %s\n",mes);
if((write(sockfd,mes,sizeof(mes)))==-1)
{
fprintf(stderr,"WriteError:%s\n",strerror(errno));
exit(1);
}
if(strcmp(buffer,"Quit")==0)
{
break;
}
}
/* 结束通讯 */
close(sockfd);
exit(0);
}
/******* 服务器程序(server.c) ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MAXLINE 1000 //在一条消息中最大的输出字符数
#define LISTENQ 20 //最大监听队列
#define PORT 5000 //监听端口
#define MAXFD 20 //最大的在线用户数量
void *get_client(void *);
int sockfd,i;
static int maxi=0;//maxi表示当前client数组中最大的用户的i值
static int client[MAXFD];
void recvandsend(void) //监听转发线程入口函数
{
int index=0;
int nbytes=0;
charbuffer[1024];
int len;
intoutindex=0;
while(1)
{
if(maxi>0)
{
memset(buffer,0,sizeof(buffer));
nbytes=0;
//index++;
nbytes=read(client[index++],buffer,sizeof(buffer));
// printf("%d,%d\n",index,client[index]);
if(nbytes>0)
{
buffer[nbytes]='\0';
printf("%s\n",buffer);
outindex=0;
while(outindex<maxi)
if(write(client[outindex++],buffer,sizeof(buffer))==-1)
{
fprintf(stderr,"WriteError:%s\n",strerror(errno));
exit(1);
}
}
}
if(index>=maxi)
index=0;
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
// intclient_fd[LISTENQ],clientnum=0;;
structsockaddr_in server_addr;
structsockaddr_in client_addr;
intsin_size,portnumber;
charhello[]="Hello! Are You Fine?\n";
int thr_id; /* thread ID for the newly createdthread */
pthread_t p_thread; /* thread's structure */
int new_fd=0;
memset(client,0,sizeof(client));
if(argc!=1)
{
fprintf(stderr,"Usage:%sportnumber\a\n",argv[0]);
exit(1);
}
/* 服务器端开始建立 socket 描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socketerror:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充 sockaddr 结构 */
bzero(&server_addr,sizeof(structsockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(PORT);
/* 捆绑 sockfd 描述符 */
if(bind(sockfd,(structsockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"Binderror:%s\n\a",strerror(errno));
exit(1);
}
printf("服务器监听端口%d...\n",PORT);
/* 监听 sockfd 描述符 */
if(listen(sockfd,LISTENQ)==-1)
{
fprintf(stderr,"Listenerror:%s\n\a",strerror(errno));
exit(1);
}
thr_id =pthread_create(&p_thread, NULL, recvandsend, NULL);
printf("欢迎来到本聊天室\n");
while(1)
{
/* 服务器阻塞,直到客户程序建立连接 */
if(maxi>=20)
{
printf("以达到人数上线\n");
continue;
}
sin_size=sizeof(structsockaddr_in);
if((new_fd=accept(sockfd,(structsockaddr *)(&client_addr),&sin_size))==-1)
{
fprintf(stderr,"Accepterror:%s\n\a",strerror(errno));
exit(1);
}
/*fprintf(stderr,"Serverget connection from %s\n",inet_ntoa(client_addr.sin_addr));*/
client[maxi++]=new_fd;
printf("\n新用户进入聊天室%d\n",new_fd);
}
close(sockfd);
exit(0);
}
四、测试结果显示
1. 执行结果
l 服务器打开
l 客户端打开,并输入了地址,昵称
l 服务器端显示
l 客户端2进入
l 服务器显示
l 张三输入
l 李四端显示
l 服务器显示
l 李四输入
l 张三显示
l 服务器显示
2. 结果分析
这是一个聊天室程序,可以实现群聊的功能,即当某个客户发出消息后,服务器和其他个客户端都能收到此消息。且能够显示客户端的用户名。但客户端退出聊天室后,服务器和其他在线客户端会有提示。
实现群聊的机制是:当某个客户端需要发送消息是,它将此消息发送给服务器,服务器再将此消息转发给各客户端,各客户端之间是无连接的,即相互之间不能直接通信。因此,在服务器中,有两个线程,主线程用来监听是否有客户端登录服务器,若有,建立与其连接的套接字,并存入在线客户序列里,辅助线程是接收转发线程,其依次读取个客户端,看是否有消息送达,若有,取出,并转发给各其他客户端。在客户端也有两个线程,主线程用来向服务器发送消息,辅助线程用来接收服务器发出的消息。
存在的问题是:1.当有用户下线是,虽会在服务器和各客户端提示用户下线,但是并未删除其在服务器中的套接字,致使后来用户不能进入。2.服务器的辅助线程对各客户端采取轮流监听的策略,但是因为使用read() 函数会阻塞线程,致使出现各客户端必须按登陆顺序依次发言的尴尬情况。经过查找,可以使用select()函数跨过阻塞,正在试验中。