linux多人聊天室是如何实现的?

linux多人聊天室是如何实现的?,第1张

多人聊天室可通过保持网络连接、保持进程运行保持通信。想要自己搭建多人聊天室其实也很简单,你可以直接用ZEGO即时通讯,内置文本、图片、语音、视频、地理位置等各种消息类型,支持单聊、群聊、房间聊天,自由组合IM能力,也可针对自身业务场景,定制所需服务规格。

自从开始学linux网络编程后就想写个聊天室,一开始原本打算用多进程的方式来写,可是发觉进程间的通信有点麻烦,而且开销也大,后来想用多线程能不能实现呢,于是便去看了一下linux里线程的用法,实际上只需要知道 pthread_create 就差不多了,于是动手开干,用了两天时间,调试的过程挺痛苦的,一开始打算用纯C来撸,便用简单的数组来存储客户端的连接信息,可是运行时出现了一些很奇怪的问题,不知道是不是访问了临界资源,和线程间的互斥有关等等;奇怪的是,当改用STL的set或map时问题就解决了,但上网搜了下发现STL也不是线程安全的,至于到底是什么问题暂时不想去纠结了,可能是其它一些小细节的错误吧。先贴上代码:

首先是必要的头文件 header.h:

#ifndef  __HEADER_H#define  __HEADER_H#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <error.h>#include <signal.h>#include <sys/wait.h>#include <assert.h>#include <pthread.h>#define  bool  int                  // the 3 lines is for c originally#define  true   1#define  false  0#define  PORT  9003#define  BUF_LEN  1024              // 缓冲区大小#define  MAX_CONNECTION  6          // 服务器允许的最大连接数,可自行更改#define  For(i,s,t)  for(i = (s)i != (t)++i)#endif // __HEADER_H

然后是客户端部分 client.cpp,相对来说简单一些:

#include "header.h"// 客户端接收消息的线程函数void* recv_func(void *args)

{    char buf[BUF_LEN]   int sock_fd = *(int*)args   while(true) {        int n = recv(sock_fd, buf, BUF_LEN, 0)       if(n <= 0)   break                 // 这句很关键,一开始不知道可以用这个来判断通信是否结束,用了其它一些很奇葩的做法来结束并关闭 sock_fd 以避免 CLOSE_WAIT 和 FIN_WAIT2 状态的出现T.T        write(STDOUT_FILENO, buf, n)

}

close(sock_fd)

exit(0)

}// 客户端和服务端进行通信的处理函数void process(int sock_fd)

{

pthread_t td

pthread_create(&td, NULL, recv_func, (void*)&sock_fd)     // 新开个线程来接收消息,避免了一读一写的原始模式,一开始竟把它放进 while 循环里面了,泪崩。。。

char buf[BUF_LEN]   while(true) {        int n = read(STDIN_FILENO, buf, BUF_LEN)

buf[n++] = '\0'                           // 貌似标准读入不会有字符串结束符的,需要自己手动添加

send(sock_fd, buf, n, 0)

}

close(sock_fd)

}int main(int argc, char *argv[])

{

assert(argc == 2)   struct sockaddr_in cli

bzero(&cli, sizeof(cli))

cli.sin_family = AF_INET

cli.sin_addr.s_addr = htonl(INADDR_ANY)

cli.sin_port = htons(PORT)                    // 少了 htons 的话就连接不上了,因为小端机器的原因???

int sc = socket(AF_INET, SOCK_STREAM, 0)   if(sc <0) {

perror("socket error")

exit(-1)

}

inet_pton(AF_INET, argv[1], &(cli.sin_addr))          // 用第一个参数作为连接服务器端的地址

int err = connect(sc, (struct sockaddr*)&cli, sizeof(cli))   if(err <0) {

perror("connect error")

exit(-2)

}

process(sc)

close(sc)   return 0

}

最后是服务端 server.cpp:

#include <map>#include "header.h"using std::map

map<int, struct sockaddr_in*>socks        // 用于记录各个客户端,键是与客户端通信 socket 的文件描述符,值是对应的客户端的 sockaddr_in 的信息// 群发消息给 socks 中的所有客户端inline void send_all(const char *buf, int len)

{    for(auto it = socks.begin()it != socks.end()++it)

send(it->first, buf, len, 0)

}// 服务端端接收消息的线程函数void* recv_func(void* args)

{    int cfd = *(int*)args   char buf[BUF_LEN]   while(true) {        int n = recv(cfd, buf, BUF_LEN, 0)       if(n <= 0)   break                    // 关键的一句,用于作为结束通信的判断        write(STDOUT_FILENO, buf, n)       if(strcmp(buf, "bye\n") == 0) {         // 如果接收到客户端的 bye,就结束通信并从 socks 中删除相应的文件描述符,动态申请的空间也应在删除前释放

printf("close connection with client %d.\n", cfd)           free(socks[cfd])

socks.erase(cfd)           break

}

send_all(buf, n)          // 群发消息给所有已连接的客户端    }

close(cfd)                // 关闭与这个客户端通信的文件描述符}// 和某一个客户端通信的线程函数void* process(void *argv)

{

pthread_t td

pthread_create(&td, NULL, recv_func, (void*)argv)        // 在主处理函数中再新开一个线程用于接收该客户端的消息

int sc = *(int*)argv   char buf[BUF_LEN]   while(true) {        int n = read(STDIN_FILENO, buf, BUF_LEN)

buf[n++] = '\0'               // 和客户端一样需要自己手动添加字符串结束符

send_all(buf, n)              // 服务端自己的信息输入需要发给所有客户端    }

close(sc)

}int main(int argc, char *argv[])

{    struct sockaddr_in serv

bzero(&serv, sizeof(serv))

serv.sin_family = AF_INET

serv.sin_addr.s_addr = htonl(INADDR_ANY)

serv.sin_port = htons(PORT)   int ss = socket(AF_INET, SOCK_STREAM, 0)   if(ss <0) {

perror("socket error")       return 1

}    int err = bind(ss, (struct sockaddr*)&serv, sizeof(serv))   if(err <0) {

perror("bind error")       return 2

}

err = listen(ss, 2)   if(err <0) {

perror("listen error")       return 3

}

socks.clear()         // 清空 map

socklen_t len = sizeof(struct sockaddr)   while(true) {        struct sockaddr_in *cli_addr = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in))       int sc = accept(ss, (struct sockaddr*)cli_addr, &len)       if(sc <0) {            free(cli_addr)           continue

}        if(socks.size() >= MAX_CONNECTION) {            // 当将要超过最大连接数时,就让那个客户端先等一下

char buf[128] = "connections is too much, please waiting...\n"

send(sc, buf, strlen(buf) + 1, 0)

close(sc)           free(cli_addr)           continue

}

socks[sc] = cli_addr                       // 指向对应申请到的 sockaddr_in 空间

printf("client %d connect me...\n", sc)

pthread_t td

pthread_create(&td, NULL, process, (void*)&sc)      // 开一个线程来和 accept 的客户端进行交互    }    return 0

}

makefile文件:

all: server client

server: server.cpp

g++ -std=c++11 -o server server.cpp -lpthread

client: client.cpp

g++ -std=c++11 -o client client.cpp -lpthread

clean:

rm -f *.o

在我的ubuntu 14.04 64 位的机器上测试过没有什么问题,客户端与服务端能正常的交互和退出,能通过服务端接收其它客户端发送的消息,运行时cpu和内存占用情况正常,不会产生什么奇怪的bug。暂时只写了个终端的界面,客户端的UI迟点再去弄吧~

*****************************************************************************************************************************************

今天试了下用 PyQt4 去写个客户端的界面,调了好一天,总算能看到点东西了,先上图:

而命令行下的客户端(上面的 client.cpp 文件)的运行界面是这样子的:

服务端的运行情况是:

PyQt4 编写的客户端(pyqt_client.py)代码是:

#!/usr/bin/env python#-*- coding: utf-8 -*-from PyQt4 import QtGui, QtCoreimport sysimport socketimport threadclass Client(QtGui.QWidget):

BUF_LEN = 1024    def __init__(self, parent=None):

QtGui.QWidget.__init__(self, parent)

self.setWindowTitle(u'TCP客户端')

self.resize(600, 500)

self.center()

layout = QtGui.QGridLayout(self)

label_ip = QtGui.QLabel(u'远程主机IP:')

layout.addWidget(label_ip, 0, 0, 1, 1)

self.txt_ip = QtGui.QLineEdit('127.0.0.1')

layout.addWidget(self.txt_ip, 0, 1, 1, 3)

label_port = QtGui.QLabel(u'端口:')

layout.addWidget(label_port, 0, 4, 1, 1)

self.txt_port = QtGui.QLineEdit('9003')

layout.addWidget(self.txt_port, 0, 5, 1, 3)

self.isConnected = False

self.btn_connect = QtGui.QPushButton(u'连接')

self.connect(self.btn_connect, QtCore.SIGNAL(            'clicked()'), self.myConnect)

layout.addWidget(self.btn_connect, 0, 8, 1, 2)

label_recvMessage = QtGui.QLabel(u'消息内容:')

layout.addWidget(label_recvMessage, 1, 0, 1, 1)

self.btn_clearRecvMessage = QtGui.QPushButton(u'↓ 清空消息框')

self.connect(self.btn_clearRecvMessage, QtCore.SIGNAL(            'clicked()'), self.myClearRecvMessage)

layout.addWidget(self.btn_clearRecvMessage, 1, 7, 1, 3)

self.txt_recvMessage = QtGui.QTextEdit()

self.txt_recvMessage.setReadOnly(True)

self.txt_recvMessage.setStyleSheet('background-color:yellow')

layout.addWidget(self.txt_recvMessage, 2, 0, 1, 10)

lable_name = QtGui.QLabel(u'姓名(ID):')

layout.addWidget(lable_name, 3, 0, 1, 1)

self.txt_name = QtGui.QLineEdit()

layout.addWidget(self.txt_name, 3, 1, 1, 3)

self.isSendName = QtGui.QRadioButton(u'发送姓名')

self.isSendName.setChecked(False)

layout.addWidget(self.isSendName, 3, 4, 1, 1)

label_sendMessage = QtGui.QLabel(u' 输入框:')

layout.addWidget(label_sendMessage, 4, 0, 1, 1)

self.txt_sendMessage = QtGui.QLineEdit()

self.txt_sendMessage.setStyleSheet("background-color:cyan")

layout.addWidget(self.txt_sendMessage, 4, 1, 1, 7)

self.btn_send = QtGui.QPushButton(u'发送')

self.connect(self.btn_send, QtCore.SIGNAL('clicked()'), self.mySend)

layout.addWidget(self.btn_send, 4, 8, 1, 2)

self.btn_clearSendMessage = QtGui.QPushButton(u'↑ 清空输入框')

self.connect(self.btn_clearSendMessage, QtCore.SIGNAL(            'clicked()'), self.myClearSendMessage)

layout.addWidget(self.btn_clearSendMessage, 5, 6, 1, 2)

self.btn_quit = QtGui.QPushButton(u'退出')

self.connect(self.btn_quit, QtCore.

//下面是一个实例

/**    

* socket.io chat    

*    

*/    

     

var web = require('QuickWeb')    

     

// undefined    

var _ = undefined     

     

/**    

 * 创建一个房间    

 *    

 * @param {string} room 房间名称    

 * @param {socket.io} io socket.io实例    

 */    

var Room = module.exports = function (room, io) {    

// 初始化socket.io实例,仅在第一次创建房间时需要设置io参数    

if (typeof io != 'undefined')    

Room.prototype.io = io    

var io = this.io    

   

// 房间成员列表    

var nicknames = this.nicknames = {}    

var onlinesum = this.onlinesum = 0    

   

// 握手验证,如果是登录用户,则自动获取其昵称    

io.set('authorization', function (handshakeData, callback) {    

// 通过客户端的cookie字符串来获取其session数据    

var sessionObject = handshakeData.sessionObject = web.session.getByCookie(handshakeData.headers.cookie)    

   

// 如果不是登录用户,则自动为其设置一个昵称    

var nickname = sessionObject.data.nickname    

if (typeof nickname != 'string' || nickname == '')    

nickname = '#' + Math.floor(Math.random() * 1000) + '' + (new Date().getTime() % 86400000)    

sessionObject.data.nickname = nickname    

   

callback(null, true)    

})    

   

/** 连接处理 */    

var connectionHandle = function (socket) {    

onlinesum++    

// 获取session    

var session = socket.handshake.sessionObject.data    

var nickname = session.nickname    

   

// 保持session,以免session过期    

var hold_session = socket.handshake.sessionObject.hold    

   

/** 刷新在线列表 */    

refresh_online = function () {    

var n = []    

for (var i in nicknames)    

n.push(i)    

socket.broadcast.emit('online list', n)    

socket.emit('online list', n)    

}    

   

// 新成员加入时,通知其他成员    

nicknames[nickname] = socket    

refresh_online()    

socket.broadcast.emit('system message', nickname + '回来了,大家赶紧去喷他~~')    

   

/** 公共消息 */    

socket.on('public message', function (msg, cb) {    

hold_session()    

var timestamp = new Date().getTime()    

socket.broadcast.emit('public message', nickname, msg, timestamp)    

cb()    

})    

   

/** 私人消息 */    

socket.on('private message', function (to, msg, cb) {    

hold_session()    

var timestamp = new Date().getTime()    

var err = ''    

for (var i in to) {    

var target = nicknames[to[i]]    

if (target) {    

cb()    

target.emit('private message', nickname, msg, timestamp)    

}    

else {    

err += '“' + to[i] + '”不在线\n'    

}    

}    

if (err != '')    

cb(err)    

})    

   

/** 断开来连接 */    

socket.on('disconnect', function () {    

delete nicknames[nickname]    

onlinesum--    

socket.broadcast.emit('system message', nickname + '悄悄地离开了。。。')    

refresh_online()    

})    

   

/** 命令 */    

socket.on('command', function (args, cb) {    

if (args.length < 1) {    

cb('无效的命令')    

return    

}    

switch (args[0]) {    

/* 查询或更改昵称 */    

case 'nick':    

var nick = args[1]    

if (typeof nick == 'undefined')    

cb(_, '你的昵称是:' + nickname)    

else    

if (nick == nickname)    

cb('你的昵称本来就是“' + nick + '”嘛,不需要改')    

else if (nicknameIsUsed(nick))    

cb('昵称“' + nick + '”已被占用')    

else {    

nicknames[nick] = nicknames[nickname]    

delete nicknames[nickname]    

var oldnick = nickname    

session.nickname = nickname = nick    

cb(_, '昵称已更改为“' + nick + '”')    

// 通知其他人    

refresh_online()    

socket.broadcast.emit('system message', '“' + oldnick + '”的昵称已改为“' + nick + '”')    

}    

break    

   

/* 在线人数 */    

case 'online':    

cb(_, '当前共有' + onlinesum + '个人在线')    

break    

   

/* 帮助 */    

default:    

cb(_, strHelp)    

}    

})    

}    

   

/* 注册聊天室 */    

if (typeof room == 'undefined')    

room = ''    

io.of('/' + room).on('connection', connectionHandle)    

   

   

/** 检查昵称是否被占用 */    

var nicknameIsUsed = function (nickname) {    

for (var i in nicknames)    

if (i == nickname)    

return true    

return false    

}    

}    

   

var strHelp = '输入$help获取帮助\n\    

========= 系统命令 ========\n\    

**$nick** [昵称] 查看或更改昵称\n\    

**$online** 当前在线人数\n\    

**$clear** 清空消息\n\    

========= 使用技巧 ========\n\    

**给某人发送消息** @对方昵称 消息内容(可同时@多个人)\n\    

**发送图片** !图片url\n\    

**发送链接** [网址]\n\    

'


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7156511.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-02
下一篇 2023-04-02

发表评论

登录后才能评论

评论列表(0条)

保存