学习了多线程与Socket套接字后,打算写一个类似qq的本地群聊聊天室作为练习,于是就写了下面的这些代码。运行环境为linux,编程语言为c++11。
话不多说直接上代码。
服务器代码:
#include <iostream>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <queue>
#include <utility>
using namespace std;
#define SERV_PORT 5555 // port number
queue<pair<pthread_t,string>>WriteQueue; //this helps to contain the message which has been read and need to be send to other sockets.
vector<pair<pthread_t,int>>Thread; //the second of the pair is the file describer of the socket.
pthread_mutex_t mutex;
void* WriteMessageOut(void *)
{
while(1)
{
while(!WriteQueue.empty())
{
pthread_mutex_lock(&mutex);
pthread_t sender=WriteQueue.front().first;
string s=WriteQueue.front().second;
WriteQueue.pop();
s="user "+to_string(sender%1000)+":"+s;
cout<<s<<endl;
for(auto pair:Thread)
{
if(sender!=pair.first)
{
int fd=pair.second;
char str[BUFSIZ];
strcpy(str,s.c_str());
write(fd,str,strlen(str));
}
}
pthread_mutex_unlock(&mutex);
}
usleep(1000); //sleep for 1000 microseconds.(weimiao)
}
return NULL;
}
void* ReadMessageIn(void *arg)
{
int fd=*reinterpret_cast<int*>(arg);
int ret;
char buf[BUFSIZ];
while((ret=read(fd, buf, BUFSIZ))>0)
{
buf[ret] = '\0';
string s(buf);
WriteQueue.emplace(pthread_self(),s);
}
close(fd);
string s="user " +to_string(pthread_self() % 1000)+" exited.\n";
cout << s << endl;
WriteQueue.emplace(pthread_self(), s);
pthread_exit(0);
return NULL;
}
pthread_t CreateThread(void*(*f)(void *),void *arg)
{
pthread_t ret;
pthread_create(&ret,NULL,f,arg);
//pthread_detach(ret); //分离该线程,防止僵尸线程
return ret;
}
int main()
{
int sid;
int ret;
int fd;
pthread_mutex_init(&mutex, NULL);
sid=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in servaddr;
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SERV_PORT);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
ret=bind(sid,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(sid,128);
pthread_t WriteIn=CreateThread(WriteMessageOut,NULL);
cout<<"Server Started."<<endl;
while((fd=accept(sid,NULL,NULL))!=-1)
{
pthread_t pid_thread =CreateThread(ReadMessageIn, (void *)&fd);
Thread.emplace_back(pid_thread, fd);
string s = "user " + to_string(pid_thread % 1000) + " connected.\n";
cout << s<<endl;
WriteQueue.emplace(pid_thread, s);
}
close(sid);
pthread_mutex_destroy(&mutex);
return 0;
}
代码运行截图:
代码使用方法:
首先使用g++进行编译,接着启动服务器。由于该程序比较简单就没有设置客户端的代码,这里在linux下打开任意终端后,输入下述指令来连接本地服务器。
nc 127.0.0.1 5555
nc是netcat的意思,后面的127.0.0.1是本地的虚拟地址,在后面的5555是服务器的端口号,这样就可以连上服务器。
连上服务器后就可以在终端里打字说话,其他在服务器的用户可以收到你的信息。
接着如果需要断开与服务器的连接,在控制台输入 ctrl+c 来终止程序,或者直接关闭终端,这样就可以断开服务器了。
代码分析:
queue<pair<pthread_t,string>>WriteQueue;,用来存储待发送的消息队列,二元组pair的第一个参数用来储存发送该消息的线程id,第二个用来储存待发送的消息。该队列中的所有消息都会被发送给服务器中的其他用户。
vector<pair<pthread_t,int>>Thread;,用来储存打开的线程与其对应分配的套接字的文件描述符。
程序运行后,首先新建一个线程用于监听WriteQueue队列,该线程负责转发用户消息到其他用户。
接着服务器调用accept函数等待连接,并为每个用户分配Socket套接字的文件描述符和一个线程来收发数据。
服务器关闭,释放资源。
代码不足:
Thread数组本来是设计用于服务器关闭时销毁资源的,该程序由于会一直在accept循环中无法出来,所以就没有显示销毁资源。线程没有得到显示的释放。
对于临界区的访问:mutex锁应该有更好的用法,该程序的并发性不好,还没有测试过在并发访问情况下的性能如何。
最后,使用英文注释是因为我没有打开搜狗输入法hhh,VMware切换输入法不是很舒服(应该是因为我还没有设置的原因)。