近期学习linux socket编程。看看unp那本书。顺便写了个类似最简单聊天功能的软件。界面是用qt写的。写下来总结总结吧,假设有问题。欢迎大家和我交流。
模式是C/S模式,server端等待请求。client发送后建立请求。连接用的是tcp不是udp,事实上udp实现更为简单。
一. 环境搭建
我用docker搭了几个虚拟机,详细搭建方式能够參考网上的或我之前写docker的总结:点击打开链接
docker还是非常轻量级的,比一般虚拟机起的快多了。
搭建好docker以后就是主要的先看网络是否联通,假设不连通看看链路。防火墙设置一下。
二.没有界面的代码实现:
这部分代码主要參考unp上的简单样例,当中肯定会有bug,由于没有考虑复杂的tcp环境,仅仅是单纯的先实现再说:
server端:
#include "unp.h" #include <stdio.h> #include <unistd.h> void Answer(FILE *fop, FILE *fip, int sockfd) { int maxfdp1, stdineof; fd_set rset; char buf[MAXLINE]; int n; stdineof = 0; FD_ZERO(&rset); for ( ; ; ) { if (stdineof == 0) FD_SET(fileno(fip), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fip), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { if (stdineof == 1) return; /* normal termination */ else err_quit("client quit!"); } Write(fileno(fop), buf, n); } if (FD_ISSET(fileno(fip), &rset)) { /* input is readable */ if ( (n = Read(fileno(fip), buf, MAXLINE)) == 0) { stdineof = 1; Shutdown(sockfd, SHUT_WR); /* send FIN */ FD_CLR(fileno(fip), &rset); continue; } Writen(sockfd, buf, n); } } } int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //servaddr.sin_port = htons(SERV_PORT); servaddr.sin_port = htons(50001); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for ( ; ; ) { clilen = sizeof(cliaddr); if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) { if (errno == EINTR) continue; /* back to for() */ else err_sys("accept error"); } if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ Answer(stdout, stdin, connfd); printf("end of one connect!\n"); exit(0); } Close(connfd); /* parent closes connected socket */ } }
client代码:
#include "unp.h" const int SIZE = 1024; void Input(FILE *fp, int sockfd) { int maxfdp1, stdineof; fd_set rset; char buf[MAXLINE]; int n; stdineof = 0; FD_ZERO(&rset); for ( ; ; ) { if (stdineof == 0) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fp), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { if (stdineof == 1) return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } Write(fileno(stdout), buf, n); } if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) { stdineof = 1; Shutdown(sockfd, SHUT_WR); /* send FIN */ FD_CLR(fileno(fp), &rset); continue; } Writen(sockfd, buf, n); } } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 3) err_quit("usage: tcpcli <IPaddress>::<Port>"); sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(atoi(argv[2])); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); struct sockaddr_in ss; socklen_t len = sizeof(ss); if(getsockname(sockfd, (SA *)&ss, &len) < 0) return 0; int clientPort = ntohs(ss.sin_port); //pay attention the translate n<->pstr_cli(stdin, sockfd); /* do it all */ printf("%d\n", clientPort); Input(stdin, sockfd); exit(0); }
vim一粘贴,这代码格式我也是无奈了……
要想跑通这个代码还得配置unp的环境。
好吧。我以下有界面的代码能够不用配置unp环境
三.加界面
我抽象了几个基类。主要界面就4个,先来看看界面吧:
server端和client的聊天界面是一样的。server端我做了点小处理,拒绝多个客户的连接。当一个客户聊天时,监听的socket就被关闭了。
刚学qt就開始搞界面,真是太恶心了。
主要代码:
client登录:
#include "clientstart.h" #include "chatclient.h" #include <iostream> #include <fstream> #include <sstream> using namespace std; ClientStart::ClientStart(QWidget *parent) : StartBase(parent) { this->className = "ClientStart"; /*belong to first layout*/ QLabel *labelOppositeIP = new QLabel("请输入服务器IP:"); oppositeIP = new QLineEdit; QLabel *labelOppositePort = new QLabel("请输入服务器端口:"); oppositePort = new QLineEdit; firstLayout->addWidget(labelOppositeIP, 2, 0, 1, 1); firstLayout->addWidget(oppositeIP, 2, 1, 1, 4); firstLayout->addWidget(labelOppositePort, 3, 0, 1, 1); firstLayout->addWidget(oppositePort, 3, 1, 1, 4); /*second layout*/ secondLayout = new QHBoxLayout; secondLayout->setSpacing(60); secondLayout->addWidget(buttonSure); secondLayout->addWidget(buttonCancel); topLayout->addLayout(firstLayout); topLayout->addLayout(secondLayout); QWidget* widget = new QWidget(this); widget->setLayout(topLayout); this->setCentralWidget(widget); //add the topLayout in current dialog } ClientStart::~ClientStart() { //delete this; } void ClientStart::doOpen() { //QMessageBox::information(this, "success", QObject::tr("xxx"), QMessageBox::Ok); serverIP = (oppositeIP->text()).toStdString(); serverPort = (oppositePort->text()).toStdString(); /*start connect now*/ bool isConnect = tryConnect(); if(!isConnect) { errorCall(this, "连接失败"); return ; } ChatClient cc(clientIP, clientPort, serverIP, serverPort, servaddr, clientSocketFd); cc.exec(); //cc.show(); this->close(); } void ClientStart::doCancel() { QMessageBox::information(this, "failed", QObject::tr("请又一次设置"), QMessageBox::Ok); oppositeIP->setText(""); oppositePort->setText(""); } bool ClientStart::tryConnect() { clientSocketFd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(atoi(serverPort.c_str())); inet_pton(AF_INET, serverIP.c_str(), &servaddr.sin_addr); if(::connect(clientSocketFd, (SA *) &servaddr, sizeof(servaddr)) < 0) return 0; /*get client port, ip has already got*/ struct sockaddr_in ss; socklen_t len = sizeof(ss); if(getsockname(clientSocketFd, (SA *)&ss, &len) < 0) return 0; stringstream stmp; //change int to string stmp << ntohs(ss.sin_port); //pay attention the translate n<->p clientPort = stmp.str(); return 1; }
client聊天:
#include "chatclient.h" #include <iostream> #include <fstream> using namespace std; ChatClient::ChatClient(std::string clientIP, std::string clientPort, std::string serverIP, std::string serverPort, struct sockaddr_in servaddr, int clientSocketFd) : ChatBase(NULL) { this->clientIP = clientIP; this->clientPort = clientPort; this->serverIP = serverIP; this->serverPort = serverPort; this->servaddr = servaddr; this->clientSocketFd = clientSocketFd; this->lineLocalIP->setText(QString(clientIP.c_str())); this->lineLocalPort->setText(QString(clientPort.c_str())); this->lineOppositeIP->setText(QString(serverIP.c_str())); this->lineOppositePort->setText(QString(serverPort.c_str())); this->secondLayout->setReadOnly(1); this->secondLayout->setText("start chatting now!\n"); this->secondLayout->append(showTime()); this->secondLayout->show(); /*connect the socket and function*/ stdineof = 0; FD_ZERO(&rset); revSN = new QSocketNotifier(clientSocketFd, QSocketNotifier::Read); QObject::connect(revSN, SIGNAL(activated(int)), this, SLOT(doDataReceived())); } ChatClient::~ChatClient() { stdineof = 1; shutdown(clientSocketFd, SHUT_WR); /* send FIN */ FD_ZERO(&rset); this->close(); } void ChatClient::doSendMsg() { std::string sent = (forthLayout->toPlainText()).toStdString(); sent += ‘\n‘; if(write(clientSocketFd, sent.c_str(), sent.size()) < 0) { errorCall(this, "send message failed!"); return ; } appendSecondLayout(sent); forthLayout->clear(); } void ChatClient::doCancel() { forthLayout->clear(); } void ChatClient::doSendFile() {} void ChatClient::doVedio() {} void ChatClient::doDataReceived() { int revLen; if ( (revLen = read(clientSocketFd, buf, MAXLINE)) == 0) { if (stdineof == 1) return; /* normal termination */ else { errorCall(this, "server terminated prematurely"); this->close(); } } buf[revLen] = ‘\0‘; appendSecondLayout(string(buf)); return ; }
server端代码就不放了。基本都是类似的。
我把所有代码上传到csdn了,链接:点击打开链接
安装时须要配置qt5,视频和传文件功能暂未实现
四.总结和未来展望
这个软件也就写着玩。肯定有Bug。
事实上之前的计划是作NAT转发,使得两台局域网之间的主机能够直接聊天,后来发现行不通,没有权限改动入网的路由NAT规则。
以后的计划例如以下:
1.是不用qt界面。直接命令行。
2.实现文件传输。
3.抛弃C/S架构,搞一台有公网ip的server,两台聊天的主机同一时候连到这个服务,server进行转发
假设大家有什么问题欢迎交流,希望能有小伙伴和我一起来搞~~。
代码虽不难,但也是自己倒腾的。转载请注明地址:http://blog.csdn.net/u011353822/article/details/41246277