这一节我们再讲一个tcp长连接的例子,实现网络聊天室的基本功能。
聊天室的基本原理:采用Client/Server TCP架构,客户端发送消息给服务器,服务器再把消息转发给所有的客户端。
一、需求分析
聊天室功能清单,总结的很好,来自博客:
http://blog.csdn.net/ccj2020/article/details/7838910
一个在Linux下可以使用的聊天软件,要求至少实现如下功能:
- 采用Client/Server架构
- Client A 登陆聊天服务器前,需要注册自己的ID和密码
- 注册成功后,Client A 就可以通过自己的ID和密码登陆聊天服务器
- 多个Client X 可以同时登陆聊天服务器之后,与其他用户进行通讯聊天
- Client A成功登陆后可以查看当前聊天室内其他在线用户Client x
- Client A可以选择发消息给某个特定的Client X,即”悄悄话”功能
- Client A 可以选择发消息全部的在线用户,即”群发消息”功能
- Client A 在退出时需要保存聊天记录
- Server端维护一个所有登陆用户的聊天会的记录文件,以便备查
可以选择实现的附加功能:
- Server可以内建一个特殊权限的账号admin,用于管理聊天室
- Admin可以将某个Client X “提出聊天室”
- Admin可以将某个Client X ”设为只能旁听,不能发言”
- Client 端发言增加表情符号,可以设置某些自定义的特殊组合来表达感情.如输入:),则会自动发送”XXX向大家做了个笑脸”
- Client段增加某些常用话语,可以对其中某些部分进行”姓名替换”,例如,输入/ClientA/welcome,则会自动发送”ClientA 大侠,欢迎你来到咱们的聊天室”
附加功能:
- 文件传输
这里我只完成了最基本的功能4,多个客户同时聊天,这也是聊天室的核心功能,其它功能以后再一一实现。
二、chat服务器实现
程序的实现是采用Client/Server TCP架构,服务器负责监听客户端的连接。
当有客户端连接上服务器时,服务器会专门为连接上的客户端开一个线程,用来接收客户端发送过来的消息并把此消息转发给所有的客户端。此外,程序还开了一个线程专门处理关闭服务器的线程,当我们在终端输入字符’Q’时,服务器将关闭所有的连接并退出进程。
程序基本架构:
- 主线程:监听来自客户端的连接,如果没有连接,则阻塞在accept函数。
- pthread_handle线程处理函数:接收客户发来的消息并群发出去。
- quit线程处理函数:可实现通过终端随时关闭服务器。
实现代码:
//server_chat3.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/ipc.h>
#include<errno.h>
#include<sys/shm.h>
#include<time.h>
#include<pthread.h>
#include <arpa/inet.h>
#define PORT 9878
#define SIZE 1024
#define SIZE_SHMADD 2048
#define LISTEN_MAX 10
int listenfd;
int connfd[LISTEN_MAX];
//套接字描述符
int get_sockfd()
{
struct sockaddr_in server_addr;
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(-1);
}
printf("Socket successful!\n");
//sockaddr结构
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(PORT);
// 设置套接字选项避免地址使用错误,为了允许地址重用,我设置整型参数(on)为 1 (不然,可以设为 0 来禁止地址重用)
int on=1;
if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
perror("setsockopt failed");
exit(-1);
}
//绑定服务器的ip和服务器端口号
if(bind(listenfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
perror("bind");
exit(-1);
}
printf("Bind successful!\n");
//设置允许连接的最大客户端数
if(listen(listenfd,LISTEN_MAX)==-1)
{
perror("bind");
exit(-1);
}
printf("Listening.....\n");
return listenfd;
}
void* pthread_handle(void * arg)
{
int index,i;
index = *(int *)arg;
printf("in pthread_recv,index = %d,connfd = %d\n",index,connfd[index]);
char buffer[SIZE];