├── README.md ├── client ├── client.pro ├── connect.jpeg ├── disconnect.jpeg ├── file.ico ├── file.jpeg ├── file.png ├── icon.ico ├── login_win.cpp ├── login_win.h ├── login_win.ui ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── message_head.h ├── picture.ico ├── picture.jpeg ├── picture.png ├── recv_file.cpp ├── recv_file.h ├── recv_file.ui ├── rename.cpp ├── rename.h ├── rename.ui ├── res.qrc ├── send_pic.cpp └── send_pic.h ├── image ├── chat1.png ├── chat2.png ├── cs.jpg ├── cs2.jpg ├── 数据格式.jpg ├── 登录.png └── 聊天.png ├── server_C++ ├── makefile ├── message_head.h ├── servercpp ├── servercpp.cpp ├── wrap.c └── wrap.h └── server_Go ├── go.mod ├── main.go └── server ├── message_head.go ├── server.go └── user.go /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 多人网络聊天室 [ Qt(客户端) <==> Socket(C++服务端)/Go(服务端) ] 3 | ## 1.简介 4 | 基于Qt和socket网络编程实现的简易网络聊天室,采用select进行监听,使用Tcp连接,可同时容纳最高1024个连接,支持发送文字消息、图片和文件消息。 5 | ## 2.由来 6 | 上网搜了下,发现有很多用Qt实现的聊天软件实例,都是只有文字或是只有图片传输的实例,没有看到哪位老哥整合过这个。本着学了就要试试的原则,完成了这个项目。项目的逻辑就是消息的转发与控制,虽然看着挺简单,但实现过程会遇到各种各样的问题,总之还是挺有意思的,就上传上来了。 7 | ## 3.详情 8 | #### 消息数据头部 9 | ``` 10 | enum message_type { 11 | m_mesage=0, 12 | m_picture=1, 13 | m_status=2, 14 | m_file=3 15 | }; 16 | 17 | typedef struct { 18 | message_type m_type; 19 | int size; 20 | int connect_num; 21 | char name[64]; 22 | char filename[64]; 23 | }message_head; 24 | ``` 25 | #### 传输的数据格式: 26 | ![](https://github.com/k5stray/ChatRoom/blob/main/image/%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F.jpg) 27 | 28 | #### 通信的数据流程: 29 | ![](https://github.com/k5stray/ChatRoom/blob/main/image/cs2.jpg) 30 | 31 | #### 登录: 32 | ![](https://github.com/k5stray/ChatRoom/blob/main/image/%E7%99%BB%E5%BD%95.png) 33 | 34 | #### 聊天 35 | 坤与神明 36 | ![](https://github.com/k5stray/ChatRoom/blob/main/image/chat1.png) 37 | ![](https://github.com/k5stray/ChatRoom/blob/main/image/chat2.png) 38 | 39 | ## 4.关于Go后端 40 | 使用Go实现后端,功能实际上跟C++没有什么区别,只是没有了最高1024个连接的限制,可接纳的同时在线人数要更高,这也是select的缺陷之一。后端根据Go的特性使用了协程和管道,与C++后端略微有一些差别,写法参照([https://www.bilibili.com/video/BV1gf4y1r79E?p=37](https://www.bilibili.com/video/BV1gf4y1r79E?p=37))。GO在数据类型转换的时候就没有C和C++那么的自由了,struct转byte切片参考大佬博客([go中struct和[]byte互相转换](https://blog.csdn.net/JineD/article/details/121605278))。个人体会:传输层往下的封包与解包都是由操作系统完成的,网络编程实际是利用操作系统给出的传输层接口进行上层逻辑的开发,与使用什么语言无关,只要传输层使用相同的协议即可通信。 41 | ## 5.参考 42 | 43 | ##### 《Linux高性能服务器编程》-中国-游双 44 | ##### Qt多线程网络通信: [https://www.bilibili.com/video/BV1LB4y1F7P7/?vd_source=21b2b7195bef3c50374169c309007961](https://www.bilibili.com/video/BV1LB4y1F7P7/?vd_source=21b2b7195bef3c50374169c309007961) 45 | ##### 黑马程序员-Linux网络编程: [https://www.bilibili.com/video/BV1iJ411S7UA/?vd_source=21b2b7195bef3c50374169c309007961](https://www.bilibili.com/video/BV1iJ411S7UA/?vd_source=21b2b7195bef3c50374169c309007961) 46 | 47 | -------------------------------------------------------------------------------- /client/client.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2022-09-05T22:39:34 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui network quick 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = client 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | 26 | SOURCES += \ 27 | main.cpp \ 28 | mainwindow.cpp \ 29 | send_pic.cpp \ 30 | login_win.cpp \ 31 | recv_file.cpp \ 32 | rename.cpp 33 | 34 | HEADERS += \ 35 | mainwindow.h \ 36 | send_pic.h \ 37 | login_win.h \ 38 | message_head.h \ 39 | recv_file.h \ 40 | rename.h 41 | 42 | FORMS += \ 43 | mainwindow.ui \ 44 | login_win.ui \ 45 | recv_file.ui \ 46 | rename.ui 47 | 48 | RESOURCES += \ 49 | res.qrc 50 | -------------------------------------------------------------------------------- /client/connect.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/connect.jpeg -------------------------------------------------------------------------------- /client/disconnect.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/disconnect.jpeg -------------------------------------------------------------------------------- /client/file.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/file.ico -------------------------------------------------------------------------------- /client/file.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/file.jpeg -------------------------------------------------------------------------------- /client/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/file.png -------------------------------------------------------------------------------- /client/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/icon.ico -------------------------------------------------------------------------------- /client/login_win.cpp: -------------------------------------------------------------------------------- 1 | #include "login_win.h" 2 | #include "ui_login_win.h" 3 | 4 | #include 5 | 6 | login_win::login_win(QWidget *parent) : 7 | QDialog(parent), 8 | ui(new Ui::login_win) 9 | { 10 | ui->setupUi(this); 11 | this->setWindowIcon(QIcon(":/icon.ico")); 12 | } 13 | 14 | login_win::~login_win() 15 | { 16 | delete ui; 17 | } 18 | 19 | /*检测ip合法性*/ 20 | bool login_win::checkIPV4(std::string s) 21 | { 22 | int k=0; //记录每个segment起始位置 23 | int pCnt=0; //记录'.'的个数 24 | s.push_back('.'); //方便atoi使用 25 | for(int i=0; i1) //以0开头的情况 30 | || !(atoi(&s[k])<=255 && atoi(&s[k])>=0)) //不符合区间范围 31 | { 32 | return false; 33 | } 34 | k = i+1; 35 | ++pCnt; 36 | } else if(!(s[i]>='0' && s[i]<='9')) { //包含非 0-9或'.' 的情况 37 | return false; 38 | } 39 | } 40 | 41 | if(pCnt != 3+1) return false; //'.'不是3段,最后一个1是自己加的 42 | 43 | return true; 44 | } 45 | 46 | void login_win::on_cancel_clicked() 47 | { 48 | ui->nickname->clear(); 49 | ui->ip->clear(); 50 | } 51 | 52 | void login_win::on_submit_clicked() 53 | { 54 | QString ip = ui->ip->text(); 55 | QString nickname = ui->nickname->text(); 56 | if(!checkIPV4(ip.toStdString())) { 57 | QMessageBox::warning(this, "警告", "您输入的IP地址不正确!"); 58 | return; 59 | } 60 | if(nickname.size() > 64) { 61 | QMessageBox::warning(this, "警告", "您输入的昵称过长!"); 62 | return; 63 | } 64 | accept(); 65 | } 66 | 67 | void login_win::getIP_Name(QString &ip, QString &name) 68 | { 69 | ip = ui->ip->text(); 70 | name = ui->nickname->text(); 71 | } 72 | -------------------------------------------------------------------------------- /client/login_win.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGIN_WIN_H 2 | #define LOGIN_WIN_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class login_win; 8 | } 9 | 10 | class login_win : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit login_win(QWidget *parent = 0); 16 | bool checkIPV4(std::string s); 17 | void getIP_Name(QString &ip, QString &name); 18 | ~login_win(); 19 | 20 | private slots: 21 | void on_cancel_clicked(); 22 | 23 | void on_submit_clicked(); 24 | 25 | private: 26 | Ui::login_win *ui; 27 | QString ip; 28 | QString nickname; 29 | 30 | }; 31 | 32 | #endif // LOGIN_WIN_H 33 | -------------------------------------------------------------------------------- /client/login_win.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | login_win 4 | 5 | 6 | 7 | 0 8 | 0 9 | 471 10 | 349 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 21 22 | 23 | 24 | 25 | 登录 26 | 27 | 28 | Qt::AlignCenter 29 | 30 | 31 | 32 | 33 | 34 | 35 | Qt::Vertical 36 | 37 | 38 | 39 | 20 40 | 72 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 13 52 | 53 | 54 | 55 | IP地址: 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 16 64 | 65 | 66 | 67 | 127.0.0.1 68 | 69 | 70 | 127.0.0.1 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 13 79 | 80 | 81 | 82 | 昵 称 : 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 16 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Qt::Vertical 101 | 102 | 103 | 104 | 20 105 | 71 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Qt::Horizontal 116 | 117 | 118 | 119 | 40 120 | 20 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 15 130 | 131 | 132 | 133 | 确认 134 | 135 | 136 | 137 | 138 | 139 | 140 | Qt::Horizontal 141 | 142 | 143 | 144 | 40 145 | 20 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 15 155 | 156 | 157 | 158 | 取消 159 | 160 | 161 | 162 | 163 | 164 | 165 | Qt::Horizontal 166 | 167 | 168 | 169 | 40 170 | 20 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /client/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "login_win.h" 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | QApplication a(argc, argv); 10 | MainWindow w; 11 | if(w.checkLoginAxec()) { 12 | w.show(); 13 | return a.exec(); 14 | } 15 | 16 | return 0; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /client/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "send_pic.h" 6 | #include "mainwindow.h" 7 | #include "ui_mainwindow.h" 8 | 9 | //#define MULT_THREAD 10 | #define PORT 9527 11 | 12 | MainWindow::MainWindow(QWidget *parent) : 13 | QMainWindow(parent), 14 | ui(new Ui::MainWindow) 15 | { 16 | ui->setupUi(this); 17 | 18 | this->setWindowIcon(QIcon(":/icon.ico")); 19 | setWindowTitle("聊天窗口"); 20 | //登录界面 21 | login = new login_win; 22 | 23 | //文件接收界面 24 | recv = new recv_file; 25 | 26 | //昵称冲突 27 | rename = new Rename; 28 | 29 | 30 | m_tcp = new QTcpSocket; 31 | 32 | //子线程处理图片上传 33 | QThread *t = new QThread; 34 | Send_Pic *worker = new Send_Pic; 35 | worker->moveToThread(t); 36 | connect(this, &MainWindow::send_pic, worker, &Send_Pic::send_pic); 37 | t->start(); 38 | 39 | qRegisterMetaType("qintptr"); 40 | 41 | message_head curr_head; 42 | connect(m_tcp, &QTcpSocket::readyRead, this, [=](){ 43 | 44 | m_tcp->read((char*)&curr_head, sizeof(curr_head)); 45 | connect_num->setText(QString::number((int)curr_head.connect_num)); 46 | strcpy(m_peer_name, curr_head.name); 47 | if((strncmp(m_peer_name, "name", 4) == 0 || strncmp(m_peer_name, "rename", 6) == 0) && curr_head.size == -1) { 48 | this->check_head.size = curr_head.size; 49 | strcpy(this->check_head.name, curr_head.name); 50 | this->check_head.connect_num = curr_head.connect_num; 51 | emit CheckBack(); 52 | return; 53 | } 54 | if(strncmp(m_peer_name, m_name, strlen(m_name)) == 0) { 55 | QByteArray data = m_tcp->read(curr_head.size); 56 | int total = curr_head.size - data.size(); 57 | while(total > 0) { 58 | if(m_tcp->waitForReadyRead()) { 59 | QByteArray tp = m_tcp->read(total); 60 | total -= tp.size(); 61 | } 62 | } 63 | return; 64 | } 65 | QString tmp = ""; 66 | if(curr_head.m_type == message_type::m_mesage) { 67 | QByteArray data = m_tcp->read(curr_head.size); 68 | int total = curr_head.size - data.size(); 69 | while(total > 0) { 70 | if(m_tcp->waitForReadyRead()) { 71 | QByteArray tp = m_tcp->readAll(); 72 | data.append(tp); 73 | total -= tp.size(); 74 | } 75 | } 76 | 77 | tmp = pack_message(QString(m_peer_name), data, false); 78 | } else if(curr_head.m_type == message_type::m_picture) { 79 | QFile *file = new QFile(curr_head.filename); 80 | file->open(QFile::WriteOnly); 81 | QByteArray data = m_tcp->read(curr_head.size); 82 | file->write(data, data.size()); 83 | int total = curr_head.size - data.size(); 84 | while(total > 0) { 85 | if(m_tcp->waitForReadyRead()) { 86 | QByteArray n = m_tcp->read(total); 87 | file->write(n, n.size()); 88 | total -= n.size(); 89 | } 90 | } 91 | file->close(); 92 | 93 | tmp = pack_pic(QString(m_peer_name), curr_head.filename, false); 94 | } else if(curr_head.m_type == message_type::m_status) { 95 | connect_num->setText(QString::number(curr_head.connect_num)); 96 | } else if(curr_head.m_type == message_type::m_file) { 97 | recv->init(); 98 | recv->setFileName(curr_head.filename); 99 | recv->setOwn(curr_head.name); 100 | recv->setFileSize((float)curr_head.size / 1024 / 1024); 101 | QByteArray data = m_tcp->read(curr_head.size); 102 | int total = curr_head.size - data.size(); 103 | while(total > 0) { 104 | if(m_tcp->waitForReadyRead()) { 105 | QByteArray n = m_tcp->read(total); 106 | data.append(n); 107 | total -= n.size(); 108 | } 109 | } 110 | tmp = pack_pre_file(curr_head.name, false, curr_head.filename, curr_head.size); 111 | ui->record->append(tmp); 112 | if(recv->exec() == QDialog::Accepted) { 113 | QFile *file = new QFile(curr_head.filename); 114 | file->open(QFile::WriteOnly); 115 | file->write(data); 116 | file->close(); 117 | tmp = pack_file_mes(curr_head.filename, "接收完成"); 118 | } else { 119 | tmp = pack_file_mes(curr_head.filename, "取消接收"); 120 | } 121 | } 122 | if(curr_head.m_type != message_type::m_status) { 123 | ui->record->append(tmp); 124 | } 125 | }); 126 | 127 | connect(m_tcp, &QTcpSocket::connected, this, [=](){ 128 | 129 | m_status->setPixmap(QPixmap(":/connect.jpeg").scaled(20, 20)); 130 | ui->record->append("已成功连接到服务器...........\n"); 131 | 132 | }); 133 | 134 | connect(m_tcp, &QTcpSocket::disconnected, this, [=](){ 135 | m_tcp->close(); 136 | m_tcp->deleteLater(); 137 | m_status->setPixmap(QPixmap(":/disconnect.jpeg").scaled(20, 20)); 138 | ui->record->append("已断开服务器连接...........\n"); 139 | 140 | t->exit(); 141 | t->deleteLater(); 142 | worker->deleteLater(); 143 | }); 144 | 145 | 146 | //连接状态设置 147 | m_status = new QLabel; 148 | m_status->setPixmap(QPixmap("://disconnect.jpeg").scaled(20, 20)); 149 | connect_num = new QLabel; 150 | connect_num->setText("0"); 151 | ui->statusBar->addWidget(new QLabel("连接状态:")); 152 | ui->statusBar->addWidget(m_status); 153 | ui->statusBar->addWidget(new QLabel(" 在线人数(")); 154 | ui->statusBar->addWidget(connect_num); 155 | ui->statusBar->addWidget(new QLabel(")")); 156 | 157 | ui->picture->setIcon(QIcon(":/picture.ico")); 158 | ui->file->setIcon(QIcon(":/file.ico")); 159 | 160 | } 161 | 162 | MainWindow::~MainWindow() 163 | { 164 | delete ui; 165 | } 166 | 167 | void MainWindow::PrintMesaageHead(QString pre, const message_head& curr_head) 168 | { 169 | qDebug()<message->text(); 180 | if(msg == "") { 181 | return; 182 | } 183 | 184 | message_head curr_head; 185 | 186 | strcpy(curr_head.name, m_name); 187 | curr_head.m_type = message_type::m_mesage; 188 | curr_head.size = msg.toUtf8().size(); 189 | m_tcp->write((char*)&curr_head, sizeof(curr_head)); 190 | 191 | m_tcp->write(msg.toUtf8()); 192 | 193 | QString tmp = pack_message(m_name, msg, true); 194 | 195 | ui->record->append(tmp); 196 | ui->message->clear(); 197 | } 198 | 199 | 200 | QString MainWindow::pack_message(QString name, QString mes, bool isMe) 201 | { 202 | QString align = "", color = "", res = ""; 203 | QDateTime current_date_time = QDateTime::currentDateTime(); 204 | QString cdt = current_date_time.toString("yyyy-MM-dd hh:mm:ss"); 205 | if(isMe) { 206 | align = "right"; 207 | color = "aquamarine"; 208 | res = "
" + cdt + " " + QString(name) + "
"; 209 | } else { 210 | align = "left"; 211 | color = "aliceblue"; 212 | res = "
" + QString(name)+ " " + cdt + "
"; 213 | } 214 | res += "
"+ mes + "

"; 215 | return res; 216 | } 217 | 218 | QString MainWindow::pack_pic(QString name, QString mes, bool isMe) 219 | { 220 | QString align = "", color = "", res = ""; 221 | QDateTime current_date_time = QDateTime::currentDateTime(); 222 | QString cdt = current_date_time.toString("yyyy-MM-dd hh:mm:ss"); 223 | if(isMe) { 224 | align = "right"; 225 | res = "
" + cdt + " " + QString(name) + "
"; 226 | } else { 227 | align = "left"; 228 | res = "
" + QString(name)+ " " + cdt + "
"; 229 | } 230 | res += "

"; 231 | return res; 232 | } 233 | 234 | QString MainWindow::pack_pre_file(QString name, bool isMe, QString filename, int filesize) 235 | { 236 | QString align = "", color = "", res = ""; 237 | QDateTime current_date_time = QDateTime::currentDateTime(); 238 | QString cdt = current_date_time.toString("yyyy-MM-dd hh:mm:ss"); 239 | if(isMe) { 240 | align = "right"; 241 | res = "
" + cdt + " " + QString(name) + "
"; 242 | } else { 243 | align = "left"; 244 | res = "
" + QString(name)+ " " + cdt + "
"; 245 | } 246 | res += "
文件
"+ 247 | filename +"("+ QString::number((float)filesize/1024/1024, 'f', 2) +"MB)

"; 248 | return res; 249 | } 250 | 251 | QString MainWindow::pack_file(QString name, bool isMe, QString filename, int filesize, QString mes) 252 | { 253 | QString res = pack_pre_file(name, isMe, filename, filesize); 254 | res += pack_file_mes(filename, mes); 255 | return res; 256 | } 257 | 258 | QString MainWindow::pack_file_mes(QString filename, QString mes) 259 | { 260 | return "
----文件\""+ filename +"\""+ mes +"----
"; 261 | } 262 | 263 | void MainWindow::on_message_returnPressed() 264 | { 265 | on_sendMassage_clicked(); 266 | } 267 | 268 | void MainWindow::on_picture_clicked() 269 | { 270 | QString filepath=QFileDialog::getOpenFileName(this,tr("Open Image"),QDir::homePath(),tr("(*.jpg)\n(*.bmp)\n(*.png)\n(*.gif)")); 271 | if(filepath.isEmpty()) { 272 | return; 273 | } 274 | QString filename = filepath.mid(filepath.lastIndexOf('/')+1); 275 | QString mes = pack_pic(m_name, filepath, true); 276 | ui->record->append(mes); 277 | 278 | #ifndef MULT_THREAD 279 | QFile file(filepath); 280 | QFileInfo info(filepath); 281 | int fileSize = info.size(); 282 | file.open(QFile::ReadOnly); 283 | message_head curr_head; 284 | curr_head.m_type = message_type::m_picture; 285 | strcpy(curr_head.name, m_name); 286 | strcpy(curr_head.filename, filename.toStdString().c_str()); 287 | curr_head.filename[strlen(curr_head.filename)] = '\0'; 288 | curr_head.size = fileSize; 289 | m_tcp->write((char*)&curr_head, sizeof(curr_head)); 290 | 291 | while(!file.atEnd()) { 292 | QByteArray line = file.readLine(); 293 | m_tcp->write(line); 294 | } 295 | #else 296 | qintptr cfd = m_tcp->socketDescriptor(); 297 | if(cfd == -1) { 298 | return ; 299 | } 300 | emit send_pic(cfd, filepath, m_name); 301 | #endif 302 | } 303 | 304 | 305 | void MainWindow::on_record_textChanged() 306 | { 307 | ui->record->moveCursor(QTextCursor::End); 308 | } 309 | 310 | bool MainWindow::checkLoginAxec() 311 | { 312 | start: 313 | if(login->exec() == QDialog::Accepted) { 314 | QString ip, name; 315 | this->Ip = ip; 316 | this->login->getIP_Name(ip, name); 317 | strcpy(m_name, name.toStdString().c_str()); 318 | unsigned short port = PORT; 319 | m_tcp->connectToHost(ip, port); 320 | 321 | //检测名字合法 322 | message_head curr_head; 323 | curr_head.m_type = message_type::m_status; 324 | curr_head.size = 0; 325 | strcpy(curr_head.name, m_name); 326 | m_tcp->write((char*)&curr_head, sizeof(curr_head)); 327 | 328 | QEventLoop eventLoop; 329 | connect(this, &MainWindow::CheckBack, &eventLoop, &QEventLoop::quit); 330 | eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 331 | 332 | if(strcasecmp(this->check_head.name , "rename") == 0 && this->check_head.size == -1) { 333 | this->rename->SetName(m_name); 334 | this->rename->exec(); 335 | goto start; 336 | } 337 | return true; 338 | } 339 | return false; 340 | } 341 | 342 | void MainWindow::on_file_clicked() 343 | { 344 | QString filepath=QFileDialog::getOpenFileName(); 345 | if(filepath.isEmpty()) { 346 | return; 347 | } 348 | 349 | QString filename = filepath.mid(filepath.lastIndexOf('/')+1); 350 | if(m_tcp->state() != QAbstractSocket::ConnectedState) { 351 | QString mes = pack_file_mes(filename, "发送失败:未连接到服务器!!"); 352 | ui->record->append(mes); 353 | return; 354 | } 355 | 356 | QFile file(filepath); 357 | QFileInfo info(filepath); 358 | int fileSize = info.size(); 359 | file.open(QFile::ReadOnly); 360 | message_head curr_head; 361 | curr_head.m_type = message_type::m_file; 362 | strcpy(curr_head.name, m_name); 363 | strcpy(curr_head.filename, filename.toStdString().c_str()); 364 | curr_head.size = fileSize; 365 | m_tcp->write((char*)&curr_head, sizeof(curr_head)); 366 | 367 | while(!file.atEnd()) { 368 | QByteArray line = file.readLine(); 369 | m_tcp->write(line); 370 | } 371 | QString mes = pack_pre_file(curr_head.name, true, filename, curr_head.size); 372 | // mes += pack_file_mes(filename, "已成功发送!"); 373 | ui->record->append(mes); 374 | } 375 | -------------------------------------------------------------------------------- /client/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "login_win.h" 10 | #include "message_head.h" 11 | #include "recv_file.h" 12 | #include "rename.h" 13 | 14 | #define NAME_SIZE 64 15 | 16 | namespace Ui { 17 | class MainWindow; 18 | 19 | } 20 | 21 | class MainWindow : public QMainWindow 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | explicit MainWindow(QWidget *parent = 0); 27 | bool checkLoginAxec(); 28 | void PrintMesaageHead(QString pre, const message_head& curr_head); 29 | ~MainWindow(); 30 | 31 | private slots: 32 | 33 | void on_sendMassage_clicked(); 34 | 35 | void on_message_returnPressed(); 36 | 37 | void on_picture_clicked(); 38 | 39 | void on_record_textChanged(); 40 | 41 | void on_file_clicked(); 42 | 43 | private: 44 | Ui::MainWindow *ui; 45 | QTcpSocket *m_tcp; 46 | QLabel *m_status; 47 | QLabel *connect_num; 48 | char m_name[NAME_SIZE]; 49 | char m_peer_name[NAME_SIZE]; 50 | login_win *login; 51 | recv_file *recv; 52 | Rename *rename; 53 | message_head check_head; 54 | bool connect_stat; 55 | QString Ip; 56 | private: 57 | QString pack_message(QString name, QString mes, bool isMe); 58 | QString pack_pic(QString name, QString mes, bool isMe); 59 | QString pack_pre_file(QString name, bool isMe, QString filename, int filesize); 60 | QString pack_file(QString name, bool isMe, QString filename, int filesize, QString mes); 61 | QString pack_file_mes(QString filename, QString mes); 62 | 63 | 64 | signals: 65 | void send_pic(qintptr cfd, QString path, const char* m_name); 66 | void CheckBack(); 67 | }; 68 | 69 | #endif // MAINWINDOW_H 70 | -------------------------------------------------------------------------------- /client/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 589 10 | 840 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Qt::Vertical 32 | 33 | 34 | 35 | 20 36 | 40 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 12 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Qt::Horizontal 56 | 57 | 58 | 59 | 40 60 | 20 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 11 70 | 71 | 72 | 73 | 发送 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 13 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Qt::Horizontal 97 | 98 | 99 | 100 | 40 101 | 20 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 13 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | Qt::Horizontal 122 | 123 | 124 | 125 | 40 126 | 20 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | Qt::Horizontal 135 | 136 | 137 | 138 | 40 139 | 20 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 0 152 | 0 153 | 589 154 | 26 155 | 156 | 157 | 158 | 159 | 160 | TopToolBarArea 161 | 162 | 163 | false 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /client/message_head.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGE_HEAD_H 2 | #define MESSAGE_HEAD_H 3 | 4 | enum message_type { 5 | m_mesage=0, 6 | m_picture=1, 7 | m_status=2, 8 | m_file=3 9 | }; 10 | 11 | typedef struct { 12 | message_type m_type; 13 | int size; 14 | int connect_num; 15 | char name[64]; 16 | char filename[64]; 17 | }message_head; 18 | 19 | #endif // MESSAGE_HEAD_H 20 | -------------------------------------------------------------------------------- /client/picture.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/picture.ico -------------------------------------------------------------------------------- /client/picture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/picture.jpeg -------------------------------------------------------------------------------- /client/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/client/picture.png -------------------------------------------------------------------------------- /client/recv_file.cpp: -------------------------------------------------------------------------------- 1 | #include "recv_file.h" 2 | #include "ui_recv_file.h" 3 | 4 | recv_file::recv_file(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::recv_file) 7 | { 8 | ui->setupUi(this); 9 | 10 | init(); 11 | } 12 | 13 | recv_file::~recv_file() 14 | { 15 | delete ui; 16 | } 17 | 18 | void recv_file::on_accpet_clicked() 19 | { 20 | accept(); 21 | } 22 | 23 | void recv_file::on_cancel_clicked() 24 | { 25 | reject(); 26 | } 27 | 28 | void recv_file::setFileName(QString filename) 29 | { 30 | ui->filename->setText(filename); 31 | } 32 | 33 | void recv_file::init() 34 | { 35 | ui->filename->setText(""); 36 | ui->own->setText(""); 37 | setWindowTitle("接收文件"); 38 | this->setWindowIcon(QIcon(":/icon.ico")); 39 | } 40 | 41 | void recv_file::setOwn(QString name) 42 | { 43 | ui->own->setText(name); 44 | } 45 | 46 | void recv_file::setFileSize(float size) 47 | { 48 | ui->filesize->setText(QString::number(size, 'f', 2)); 49 | } 50 | -------------------------------------------------------------------------------- /client/recv_file.h: -------------------------------------------------------------------------------- 1 | #ifndef RECV_FILE_H 2 | #define RECV_FILE_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class recv_file; 8 | } 9 | 10 | class recv_file : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit recv_file(QWidget *parent = 0); 16 | void setFileName(QString filename); 17 | void init(); 18 | void setOwn(QString name); 19 | void setFileSize(float size); 20 | ~recv_file(); 21 | 22 | private slots: 23 | void on_accpet_clicked(); 24 | 25 | void on_cancel_clicked(); 26 | 27 | private: 28 | Ui::recv_file *ui; 29 | }; 30 | 31 | #endif // RECV_FILE_H 32 | -------------------------------------------------------------------------------- /client/recv_file.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | recv_file 4 | 5 | 6 | 7 | 0 8 | 0 9 | 368 10 | 245 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 12 24 | 25 | 26 | 27 | 来自 28 | 29 | 30 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 12 39 | 75 40 | true 41 | 42 | 43 | 44 | Qt::RightToLeft 45 | 46 | 47 | 48 | 49 | 50 | Qt::AlignCenter 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 12 59 | 60 | 61 | 62 | 的文件: 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 12 75 | 75 76 | true 77 | true 78 | true 79 | 80 | 81 | 82 | Qt::LeftToRight 83 | 84 | 85 | 86 | 87 | 88 | Qt::AlignCenter 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 12 97 | 98 | 99 | 100 | 大小: 101 | 102 | 103 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 12 112 | true 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 12 125 | true 126 | 127 | 128 | 129 | MB 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | Qt::Vertical 139 | 140 | 141 | 142 | 20 143 | 40 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | Qt::Horizontal 154 | 155 | 156 | 157 | 40 158 | 20 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 接收 167 | 168 | 169 | 170 | 171 | 172 | 173 | Qt::Horizontal 174 | 175 | 176 | 177 | 40 178 | 20 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 取消 187 | 188 | 189 | 190 | 191 | 192 | 193 | Qt::Horizontal 194 | 195 | 196 | 197 | 40 198 | 20 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /client/rename.cpp: -------------------------------------------------------------------------------- 1 | #include "rename.h" 2 | #include "ui_rename.h" 3 | 4 | Rename::Rename(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::Rename) 7 | { 8 | ui->setupUi(this); 9 | this->setWindowIcon(QIcon(":/icon.ico")); 10 | setWindowTitle("昵称冲突"); 11 | 12 | } 13 | 14 | Rename::~Rename() 15 | { 16 | delete ui; 17 | } 18 | 19 | void Rename::SetName(QString name) 20 | { 21 | ui->name->setText(name); 22 | } 23 | 24 | void Rename::on_pushButton_clicked() 25 | { 26 | accept(); 27 | } 28 | 29 | void Rename::on_pushButton_2_clicked() 30 | { 31 | accept(); 32 | } 33 | -------------------------------------------------------------------------------- /client/rename.h: -------------------------------------------------------------------------------- 1 | #ifndef RENAME_H 2 | #define RENAME_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class Rename; 8 | } 9 | 10 | class Rename : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit Rename(QWidget *parent = 0); 16 | void SetName(QString name); 17 | ~Rename(); 18 | 19 | private slots: 20 | void on_pushButton_clicked(); 21 | 22 | void on_pushButton_2_clicked(); 23 | 24 | private: 25 | Ui::Rename *ui; 26 | }; 27 | 28 | #endif // RENAME_H 29 | -------------------------------------------------------------------------------- /client/rename.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rename 4 | 5 | 6 | 7 | 0 8 | 0 9 | 281 10 | 200 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 13 24 | 25 | 26 | 27 | 昵称" 28 | 29 | 30 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 13 39 | 75 40 | true 41 | true 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | Qt::AlignCenter 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 13 58 | 59 | 60 | 61 | "已被使用 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Qt::Horizontal 73 | 74 | 75 | 76 | 40 77 | 20 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 11 87 | 88 | 89 | 90 | 确定 91 | 92 | 93 | 94 | 95 | 96 | 97 | Qt::Horizontal 98 | 99 | 100 | 101 | 40 102 | 20 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 11 112 | 113 | 114 | 115 | 取消 116 | 117 | 118 | 119 | 120 | 121 | 122 | Qt::Horizontal 123 | 124 | 125 | 126 | 40 127 | 20 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /client/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | connect.jpeg 4 | disconnect.jpeg 5 | file.jpeg 6 | picture.jpeg 7 | icon.ico 8 | picture.ico 9 | file.ico 10 | file.png 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/send_pic.cpp: -------------------------------------------------------------------------------- 1 | #include "send_pic.h" 2 | #include "message_head.h" 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | Send_Pic::Send_Pic(QObject *parent) : QObject(parent) 9 | { 10 | 11 | } 12 | 13 | void Send_Pic::send_pic(qintptr cfd, QString filepath, const char* m_name) 14 | { 15 | QString filename = filepath.mid(filepath.lastIndexOf('/')+1); 16 | QTcpSocket *tcp = new QTcpSocket(this); 17 | tcp->setSocketDescriptor(cfd); 18 | QFile file(filepath); 19 | QFileInfo info(filepath); 20 | int fileSize = info.size(); 21 | file.open(QFile::ReadOnly); 22 | message_head curr_head; 23 | curr_head.m_type = message_type::m_picture; 24 | strcpy(curr_head.name, m_name); 25 | strcpy(curr_head.filename, filename.toStdString().c_str()); 26 | curr_head.size = fileSize; 27 | tcp->write((char*)&curr_head, sizeof(curr_head)); 28 | 29 | while(!file.atEnd()) { 30 | QByteArray line = file.readLine(); 31 | tcp->write(line); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/send_pic.h: -------------------------------------------------------------------------------- 1 | #ifndef SEND_PIC_H 2 | #define SEND_PIC_H 3 | 4 | #include 5 | 6 | class Send_Pic : public QObject 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit Send_Pic(QObject *parent = nullptr); 11 | void send_pic(qintptr cfd, QString path, const char* m_name); 12 | 13 | signals: 14 | 15 | public slots: 16 | }; 17 | 18 | #endif // SEND_PIC_H 19 | -------------------------------------------------------------------------------- /image/chat1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/chat1.png -------------------------------------------------------------------------------- /image/chat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/chat2.png -------------------------------------------------------------------------------- /image/cs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/cs.jpg -------------------------------------------------------------------------------- /image/cs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/cs2.jpg -------------------------------------------------------------------------------- /image/数据格式.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/数据格式.jpg -------------------------------------------------------------------------------- /image/登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/登录.png -------------------------------------------------------------------------------- /image/聊天.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/image/聊天.png -------------------------------------------------------------------------------- /server_C++/makefile: -------------------------------------------------------------------------------- 1 | all: serverhpp 2 | 3 | serverhpp: servercpp.cpp wrap.c 4 | g++ servercpp.cpp wrap.c -o servercpp -Wall 5 | 6 | -------------------------------------------------------------------------------- /server_C++/message_head.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGE_HEAD_H 2 | #define MESSAGE_HEAD_H 3 | 4 | enum message_type { 5 | m_mesage=0, 6 | m_picture=1, 7 | m_status=2, 8 | m_file=3 9 | }; 10 | 11 | typedef struct { 12 | message_type m_type; 13 | int size; 14 | int connect_num; 15 | char name[64]; 16 | char filename[64]; 17 | }message_head; 18 | 19 | #endif // MESSAGE_HEAD_H 20 | -------------------------------------------------------------------------------- /server_C++/servercpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k5stray/ChatRoom/2d97254b43dabf5072a7fec5400d555f25e5c6f3/server_C++/servercpp -------------------------------------------------------------------------------- /server_C++/servercpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "message_head.h" 8 | #include "wrap.h" 9 | 10 | #define SRV_PORT 9527 11 | 12 | using namespace std; 13 | 14 | class Server 15 | { 16 | public: 17 | static Server *GetInstance(); 18 | void Init(); 19 | void Run(); 20 | void UpdateConnect(); 21 | void DoAccept(); 22 | void DoSendMessage(int sfd, message_head &curr_head); 23 | void DoSendPicture(int sfd, message_head &curr_head); 24 | void DoSendFile(int sfd, message_head &curr_head); 25 | void DoCheck(int sfd, message_head &curr_head); 26 | private: 27 | 28 | Server(); 29 | ~Server(); 30 | Server(const Server&); 31 | const Server &operator=(const Server&); 32 | 33 | private: 34 | static int connect_num; //连接人数 35 | int lfd; //监听文件描述符 36 | int cfd; //客户端文件描述符 37 | int n=0; //read到的字节数 38 | int i=0; //遍历因子 39 | int maxi=0; //客户端文件描述符最大下标 40 | int nready; //描述符集合中包含的文件描述符的数量 41 | int maxfd; //最大文件描述符 42 | struct sockaddr_in srv_addr; //服务器结构体 43 | struct sockaddr_in clt_addr; //客户端结构体 44 | socklen_t clt_addr_len; //客户端地址长度 45 | char buf[BUFSIZ]; //数据缓存 46 | int client[FD_SETSIZE]; //客户端文件描述符数组 47 | fd_set rset; //文件描述符集合 48 | fd_set allset; //文件描述符集合备份 49 | map UserList; //用户列表 50 | }; 51 | 52 | Server::Server() 53 | { 54 | 55 | } 56 | 57 | Server::~Server() 58 | { 59 | 60 | } 61 | 62 | Server *Server::GetInstance() 63 | { 64 | static Server server; 65 | return &server; 66 | } 67 | 68 | void Server::Init() 69 | { 70 | bzero(&srv_addr, sizeof(srv_addr)); 71 | srv_addr.sin_family = AF_INET; 72 | srv_addr.sin_port = htons(SRV_PORT); 73 | srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 74 | 75 | lfd = Socket(AF_INET, SOCK_STREAM, 0); 76 | 77 | //设置服务器端口复用 78 | int opt = 1; 79 | setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 80 | 81 | Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); 82 | 83 | Listen(lfd, 128); 84 | 85 | printf("Accept client connect......\n"); 86 | 87 | maxfd = lfd; 88 | for(i=0;i::iterator it = UserList.begin();it != UserList.end();it++) { 128 | if(it->second == sfd) { 129 | UserList.erase(it); 130 | break; 131 | } 132 | } 133 | 134 | client[i]=-1; 135 | if(connect_num > 0) { 136 | connect_num--; 137 | } 138 | //更新状态 139 | UpdateConnect(); 140 | }else if(n>0) { 141 | if(curr_head.m_type == m_mesage) { 142 | DoSendMessage(sfd, curr_head); 143 | } else if(curr_head.m_type == m_picture) { 144 | DoSendPicture(sfd, curr_head); 145 | } else if(curr_head.m_type == m_file) { 146 | DoSendFile(sfd, curr_head); 147 | } else if(curr_head.m_type == m_status) { 148 | DoCheck(sfd, curr_head); 149 | } 150 | 151 | } 152 | 153 | } 154 | } 155 | } 156 | close(lfd); 157 | } 158 | 159 | void Server::DoAccept() 160 | { 161 | char str[INET_ADDRSTRLEN]; 162 | clt_addr_len = sizeof(clt_addr); 163 | 164 | //由于是有连接请求才触发到这里,所以accept不会阻塞等待 165 | cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len); 166 | printf("received from %s at PORT %d\n", 167 | inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, str, sizeof(str)), 168 | ntohs(clt_addr.sin_port)); 169 | 170 | FD_SET(cfd, &allset); 171 | 172 | //连接人数加1 173 | connect_num++; 174 | 175 | for(i=0;i 0) { 234 | n = Read(sfd, buf, sizeof(buf)); 235 | fwrite(buf, n, 1, fp); 236 | size -= n; 237 | } 238 | fclose(fp); 239 | for(int k=0;k<=maxi;k++) { 240 | if(client[k] < 0 || client[k] == sfd) { 241 | continue; 242 | } 243 | 244 | Write(client[k], (char*)&curr_head, sizeof(curr_head)); 245 | 246 | int filefd = open(curr_head.filename, O_RDONLY); 247 | 248 | struct stat st; 249 | stat(curr_head.filename, &st); 250 | 251 | sendfile(client[k], filefd, NULL, static_cast(curr_head.size)); 252 | 253 | } 254 | bzero(buf, sizeof(buf)); 255 | } 256 | 257 | void Server::DoSendFile(int sfd, message_head &curr_head) 258 | { 259 | curr_head.connect_num = connect_num; 260 | int size = curr_head.size; 261 | 262 | FILE *fp = fopen(curr_head.filename, "wb+"); 263 | 264 | while(size > 0) { 265 | n = Read(sfd, buf, sizeof(buf)); 266 | fwrite(buf, n, 1, fp); 267 | size -= n; 268 | } 269 | fclose(fp); 270 | for(int k=0;k<=maxi;k++) { 271 | if(client[k] < 0 || client[k] == sfd) { 272 | continue; 273 | } 274 | 275 | Write(client[k], (char*)&curr_head, sizeof(curr_head)); 276 | 277 | int filefd = open(curr_head.filename, O_RDONLY); 278 | 279 | struct stat st; 280 | stat(curr_head.filename, &st); 281 | 282 | sendfile(client[k], filefd, NULL, static_cast(curr_head.size)); 283 | 284 | } 285 | bzero(buf, sizeof(buf)); 286 | } 287 | 288 | void Server::DoCheck(int sfd, message_head &curr_head) 289 | { 290 | message_head re_head; 291 | re_head.size = -1; 292 | re_head.m_type = m_status; 293 | re_head.connect_num = connect_num; 294 | if(UserList[curr_head.name] == 0) { 295 | UserList[curr_head.name] = sfd; 296 | strcpy(re_head.name, "name"); 297 | Write(sfd, (char*)&re_head, sizeof(re_head)); 298 | } else { 299 | strcpy(re_head.name, "rename"); 300 | Write(sfd, (char*)&re_head, sizeof(re_head)); 301 | } 302 | 303 | } 304 | 305 | int Server::connect_num = 0; 306 | 307 | int main() 308 | { 309 | Server *server = Server::GetInstance(); 310 | server->Run(); 311 | return 0; 312 | } 313 | 314 | -------------------------------------------------------------------------------- /server_C++/wrap.c: -------------------------------------------------------------------------------- 1 | #include "wrap.h" 2 | 3 | void sys_err(const char* str) 4 | { 5 | perror(str); 6 | exit(1); 7 | } 8 | 9 | int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) 10 | { 11 | int n; 12 | again: 13 | if((n = accept(sockfd, addr, addrlen)) < 0) { 14 | if((errno == ECONNABORTED) || (errno == EINTR)) 15 | goto again; 16 | else 17 | sys_err("accept error"); 18 | } 19 | return n; 20 | } 21 | 22 | int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 23 | { 24 | int n; 25 | if((n = bind(sockfd, addr, addrlen)) < 0) 26 | sys_err("bind error"); 27 | return n; 28 | } 29 | 30 | int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 31 | { 32 | int n; 33 | if((n = connect(sockfd, addr, addrlen)) < 0) 34 | sys_err("connect error"); 35 | return n; 36 | } 37 | 38 | int Socket(int domain, int type, int protocol) 39 | { 40 | int n; 41 | if((n=socket(domain, type, protocol)) < 0) 42 | sys_err("socket error"); 43 | return n; 44 | } 45 | 46 | ssize_t Read(int fd, void *buf, size_t count) 47 | { 48 | ssize_t n; 49 | again: 50 | if((n=read(fd, buf, count)) == -1) { 51 | if( errno == EINTR) 52 | goto again; 53 | else { 54 | sys_err("Read error"); 55 | return -1; 56 | } 57 | } 58 | return n; 59 | } 60 | 61 | ssize_t Write(int fd, const void *buf, size_t count) 62 | { 63 | ssize_t n; 64 | again: 65 | if((n=write(fd, buf, count)) == -1) { 66 | if( errno == EINTR) 67 | goto again; 68 | else 69 | return -1; 70 | } 71 | return n; 72 | } 73 | 74 | int Close(int fd) 75 | { 76 | int n; 77 | if((n=close(fd)) == -1) 78 | sys_err("close error"); 79 | return n; 80 | } 81 | 82 | ssize_t Readn(int fd, void *vptr, size_t n) 83 | { 84 | size_t nleft; 85 | ssize_t nread; 86 | char *ptr; 87 | 88 | ptr = (char*)vptr; 89 | nleft = n; 90 | 91 | while(nleft >0 ) { 92 | if((nread = read(fd, ptr, nleft)) < 0) { 93 | if( errno == EINTR) 94 | nread = 0; 95 | else 96 | return -1; 97 | }else if(nread == 0) 98 | break; 99 | nleft -= nread; 100 | ptr += nread; 101 | } 102 | return n - nleft; 103 | } 104 | 105 | ssize_t Writen(int fd, void *vptr, size_t n) 106 | { 107 | size_t nleft; 108 | ssize_t nwritten; 109 | char *ptr; 110 | 111 | ptr = (char*)vptr; 112 | nleft = n; 113 | 114 | while(nleft >0 ) { 115 | if((nwritten = read(fd, ptr, nleft)) < 0) { 116 | if( errno == EINTR) 117 | nwritten = 0; 118 | else 119 | return -1; 120 | }else if(nwritten == 0) 121 | break; 122 | nleft -= nwritten; 123 | ptr += nwritten; 124 | } 125 | return n - nleft; 126 | } 127 | 128 | static ssize_t my_read(int fd, char *ptr) 129 | { 130 | static int read_cnt; 131 | static char *read_ptr; 132 | static char read_buf[100]; 133 | 134 | if(read_cnt <=0 ) { 135 | again: 136 | if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 137 | if(errno == EINTR) 138 | goto again; 139 | return -1; 140 | }else if(read_cnt == 0) 141 | return 0; 142 | 143 | read_ptr = read_buf; 144 | } 145 | read_cnt--; 146 | *ptr = *read_ptr++; 147 | 148 | return 1; 149 | } 150 | 151 | ssize_t Readline(int fd, void *vptr, int maxlen) 152 | { 153 | ssize_t n,rc; 154 | char c, *ptr; 155 | ptr = (char*)vptr; 156 | 157 | for(n=1;n 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void sys_err(const char* str); 12 | int Socket(int domain, int type, int protocol); 13 | int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 14 | int Listen(int sockfd, int backlog); 15 | int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 16 | ssize_t Read(int fd, void *buf, size_t count); 17 | ssize_t Write(int fd, const void *buf, size_t count); 18 | int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 19 | int Close(int fd); 20 | ssize_t Readn(int fd, void *vptr, size_t n); 21 | ssize_t Writen(int fd, void *vptr, size_t n); 22 | ssize_t Readline(int fd, void *ptr, size_t maxlen); 23 | 24 | 25 | -------------------------------------------------------------------------------- /server_Go/go.mod: -------------------------------------------------------------------------------- 1 | module ChatServer 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /server_Go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ChatServer/server" 5 | ) 6 | 7 | func main() { 8 | server := server.NewServer(9527) 9 | server.Run() 10 | } 11 | -------------------------------------------------------------------------------- /server_Go/server/message_head.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | const ( 4 | g_message = int32(0) 5 | g_picture = int32(1) 6 | g_status = int32(2) 7 | g_file = int32(3) 8 | ) 9 | 10 | type message_head struct { 11 | message_type int32 12 | size int32 13 | connect_num int32 14 | name [64]byte 15 | filename [64]byte 16 | } 17 | 18 | type SliceMock struct { 19 | addr uintptr 20 | len int 21 | cap int 22 | } 23 | 24 | func Bytes64String(p [64]byte) string { 25 | for i := 0; i < len(p); i++ { 26 | if p[i] == 0 { 27 | return string(p[0:i]) 28 | } 29 | } 30 | return string(p[:]) 31 | } 32 | -------------------------------------------------------------------------------- /server_Go/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net" 8 | "os" 9 | "sync" 10 | "unsafe" 11 | ) 12 | 13 | type Server struct { 14 | Port int 15 | OnlineMap map[string]*User 16 | mapLock sync.RWMutex 17 | numLock sync.RWMutex 18 | connnect_num int 19 | MessageCore chan []byte 20 | } 21 | 22 | func NewServer(port int) *Server { 23 | return &Server{ 24 | Port: port, 25 | OnlineMap: make(map[string]*User), 26 | MessageCore: make(chan []byte), 27 | connnect_num: 0, 28 | } 29 | } 30 | 31 | func (this *Server) ListenMessagCore() { 32 | for { 33 | msg := <-this.MessageCore 34 | this.mapLock.Lock() 35 | for _, cli := range this.OnlineMap { 36 | cli.Message <- msg 37 | } 38 | this.mapLock.Unlock() 39 | } 40 | } 41 | 42 | func (this *Server) Run() { 43 | listen, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", 9527)) 44 | if err != nil { 45 | fmt.Println("net.Listen error:", err) 46 | return 47 | } 48 | fmt.Println("Accept connect...........") 49 | defer listen.Close() 50 | 51 | go this.ListenMessagCore() 52 | 53 | for { 54 | conn, err := listen.Accept() 55 | if err != nil { 56 | fmt.Println("Accept() error:", err) 57 | return 58 | } 59 | fmt.Println(conn.RemoteAddr(), " connect....") 60 | this.numLock.Lock() 61 | this.connnect_num++ 62 | this.numLock.Unlock() 63 | go this.Handler(conn) 64 | } 65 | 66 | } 67 | 68 | func (this *Server) BroadCast(msg []byte) { 69 | this.MessageCore <- msg 70 | } 71 | 72 | func (this *Server) BroadCastFile(head []byte, file *os.File, size int64) { 73 | this.mapLock.Lock() 74 | for _, cli := range this.OnlineMap { 75 | cli.conn.Write(head) 76 | io.CopyN(cli.conn, file, size) 77 | } 78 | this.mapLock.Unlock() 79 | defer file.Close() 80 | } 81 | 82 | func (this *Server) UpdateConnectNum() { 83 | var curr_head message_head 84 | curr_head.connect_num = int32(this.connnect_num) 85 | curr_head.message_type = g_status 86 | copy(curr_head.name[:], "") 87 | curr_head.size = int32(0) 88 | midleBytes := &SliceMock{ 89 | addr: uintptr(unsafe.Pointer(&curr_head)), 90 | len: int(unsafe.Sizeof(curr_head)), 91 | cap: int(unsafe.Sizeof(curr_head)), 92 | } 93 | re_head := *(*[]byte)(unsafe.Pointer(midleBytes)) 94 | this.BroadCast(re_head) 95 | } 96 | 97 | func (this *Server) PrintHead(pre string, xx *message_head) { 98 | fmt.Println(pre, ".message_type:", xx.message_type) 99 | fmt.Println(pre, ".size:", xx.size) 100 | fmt.Println(pre, ".connect_num:", xx.connect_num) 101 | fmt.Println(pre, ".name:", string(xx.name[:])) 102 | fmt.Println(pre, ".filename:", string(xx.filename[:])) 103 | } 104 | 105 | func (this *Server) Handler(conn net.Conn) { 106 | defer conn.Close() 107 | user := NewUser(conn) 108 | this.mapLock.Lock() 109 | this.OnlineMap[user.Addr] = user 110 | this.mapLock.Unlock() 111 | 112 | this.UpdateConnectNum() 113 | for { 114 | buf := make([]byte, unsafe.Sizeof(message_head{})) 115 | n, err := conn.Read(buf) 116 | if n == 0 { 117 | this.numLock.Lock() 118 | this.connnect_num-- 119 | this.UpdateConnectNum() 120 | this.numLock.Unlock() 121 | this.mapLock.Lock() 122 | delete(this.OnlineMap, user.Name) 123 | this.mapLock.Unlock() 124 | return 125 | } 126 | if err != nil && err != io.EOF { 127 | fmt.Println("conn Read ERROR:", err) 128 | return 129 | } 130 | var curr_head *message_head = *(**message_head)(unsafe.Pointer(&buf)) 131 | if curr_head.message_type == g_message { 132 | data := make([]byte, curr_head.size) 133 | n, err = conn.Read(data) 134 | if err != nil && err != io.EOF { 135 | fmt.Println("conn Read ERROR:", err) 136 | return 137 | } 138 | this.numLock.RLock() 139 | xy := &message_head{ 140 | message_type: g_message, 141 | connect_num: int32(this.connnect_num), 142 | size: curr_head.size, 143 | name: curr_head.name, 144 | } 145 | this.numLock.RUnlock() 146 | midleBytes := &SliceMock{ 147 | addr: uintptr(unsafe.Pointer(xy)), 148 | len: int(unsafe.Sizeof(*xy)), 149 | cap: int(unsafe.Sizeof(*xy)), 150 | } 151 | re_head := *(*[]byte)(unsafe.Pointer(midleBytes)) 152 | this.BroadCast(re_head) 153 | this.BroadCast(data) 154 | } else if curr_head.message_type == g_picture { 155 | data := make([]byte, 8192) 156 | fmt.Println("filename: ", Bytes64String(curr_head.filename)) 157 | file, err := os.OpenFile(Bytes64String(curr_head.filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755) 158 | if err != nil { 159 | fmt.Println("OpenFile ERROR:", err) 160 | return 161 | } 162 | for size := curr_head.size; size > 0; { 163 | n, _ = conn.Read(data) 164 | size -= int32(n) 165 | file.Write(data[:n]) 166 | } 167 | file.Close() 168 | 169 | xy := &message_head{ 170 | message_type: g_picture, 171 | connect_num: int32(this.connnect_num), 172 | size: curr_head.size, 173 | name: curr_head.name, 174 | filename: curr_head.filename, 175 | } 176 | midleBytes := &SliceMock{ 177 | addr: uintptr(unsafe.Pointer(xy)), 178 | len: int(unsafe.Sizeof(*xy)), 179 | cap: int(unsafe.Sizeof(*xy)), 180 | } 181 | re_head := *(*[]byte)(unsafe.Pointer(midleBytes)) 182 | re_data, _ := ioutil.ReadFile(Bytes64String(curr_head.filename)) 183 | this.BroadCast(re_head) 184 | this.BroadCast(re_data) 185 | 186 | } else if curr_head.message_type == g_file { 187 | data := make([]byte, 8192) 188 | filename := Bytes64String(curr_head.filename) 189 | f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755) 190 | if err != nil { 191 | fmt.Println("OpenFile ERROR:", err) 192 | return 193 | } 194 | for size := curr_head.size; size > 0; { 195 | n, _ = conn.Read(data) 196 | size -= int32(n) 197 | f.Write(data[:n]) 198 | } 199 | f.Close() 200 | 201 | xy := &message_head{ 202 | message_type: g_file, 203 | connect_num: int32(this.connnect_num), 204 | size: curr_head.size, 205 | name: curr_head.name, 206 | filename: curr_head.filename, 207 | } 208 | midleBytes := &SliceMock{ 209 | addr: uintptr(unsafe.Pointer(xy)), 210 | len: int(unsafe.Sizeof(*xy)), 211 | cap: int(unsafe.Sizeof(*xy)), 212 | } 213 | re_head := *(*[]byte)(unsafe.Pointer(midleBytes)) 214 | re_data, _ := ioutil.ReadFile(filename) 215 | this.BroadCast(re_head) 216 | this.BroadCast(re_data) 217 | } else if curr_head.message_type == g_status { 218 | name := Bytes64String(curr_head.name) 219 | xy := &message_head{ 220 | message_type: g_status, 221 | connect_num: int32(this.connnect_num), 222 | size: -1, 223 | name: [64]byte{'r', 'e', 'n', 'a', 'm', 'e'}, 224 | filename: curr_head.filename, 225 | } 226 | xx := &message_head{ 227 | message_type: g_status, 228 | connect_num: int32(this.connnect_num), 229 | size: -1, 230 | name: [64]byte{'n', 'a', 'm', 'e'}, 231 | filename: curr_head.filename, 232 | } 233 | midleBytes_xy := &SliceMock{ 234 | addr: uintptr(unsafe.Pointer(xy)), 235 | len: int(unsafe.Sizeof(*xy)), 236 | cap: int(unsafe.Sizeof(*xy)), 237 | } 238 | midleBytes_xx := &SliceMock{ 239 | addr: uintptr(unsafe.Pointer(xx)), 240 | len: int(unsafe.Sizeof(*xx)), 241 | cap: int(unsafe.Sizeof(*xx)), 242 | } 243 | re_xy := *(*[]byte)(unsafe.Pointer(midleBytes_xy)) 244 | re_xx := *(*[]byte)(unsafe.Pointer(midleBytes_xx)) 245 | this.mapLock.Lock() 246 | _, ok := this.OnlineMap[name] 247 | if ok { 248 | this.BroadCast(re_xy) 249 | } else { 250 | _, oo := this.OnlineMap[user.Addr] 251 | if oo { 252 | delete(this.OnlineMap, user.Addr) 253 | } 254 | this.OnlineMap[name] = user 255 | user.Name = name 256 | fmt.Println("Accept name: ", name) 257 | this.BroadCast(re_xx) 258 | } 259 | this.mapLock.Unlock() 260 | } 261 | 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /server_Go/server/user.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type User struct { 8 | Name string 9 | Message chan []byte 10 | conn net.Conn 11 | Addr string 12 | } 13 | 14 | func NewUser(conn net.Conn) *User { 15 | user := &User{ 16 | Message: make(chan []byte), 17 | conn: conn, 18 | Addr: conn.RemoteAddr().String(), 19 | } 20 | 21 | go user.ListenMessage() 22 | 23 | return user 24 | } 25 | 26 | func (this *User) ListenMessage() { 27 | for { 28 | msg := <-this.Message 29 | this.conn.Write(msg) 30 | } 31 | } 32 | --------------------------------------------------------------------------------