linux下基于socket的聊天软件

近期学习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

时间: 2024-07-31 06:11:58

linux下基于socket的聊天软件的相关文章

Linux下基于Socket网络通信的多人聊天室

服务端 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <arpa/inet.h> #include <netinet/in.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> cha

Linux 下基于多线程服务器/客服端聊天程序源码

Linux 下基于多线程服务器/客服端聊天程序,采用阻塞的socket技术,和多线程技术实现. 客服端程序:client.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h>

C语言 linux环境基于socket的简易即时通信程序

转载请注明出处:http://www.cnblogs.com/kevince/p/3891033.html   By Kevince 最近在看linux网络编程相关,现学现卖,就写了一个简易的C/S即时通信程序,代码如下: head.h 1 /*头文件,client和server编译时都需要使用*/ 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <sys/types.h> 5 #include <sys

Linux下基于nw音乐电台

Linux下基于nw音乐电台,希望大神改进 百度云:下载

Linux下非root用户安装软件

下面简要说一下Linux下非root用户安装软件的一般流程:1. 获取源代码,一般是wget方式,ubuntu可以使用apt-get source来获取源代码.2. 解压源代码,一般使用tar -zxvf xxx.tar.gz即可3. 切换到解压后的目录,运行 ./configure.其选项可以通过 ./configure –help来获取,非root用户下最重要的应该是定义安装目录,即应该定义 ./configure –prefix=/path/to/bin, 对于一些依赖库,可能还需要使用

linux下的APK反编译软件及过程介绍

需要工具: 1.apktool apk打包工具 下载地址:http://android-apktool.googlecode.com/files/apktool1.5.2.tar.bz2 安装:直接解压即可,是一个apktool.jar文件,通过 $java -jar apktool.jar 来运行,依赖于java运行环境 2.dex2jar dex转化jar工具 下载地址:http://dex2jar.googlecode.com/files/dex2jar-0.0.9.15.zip 安装:直

Windows 和 Linux下使用socket下载网页页面内容(可设置接收/发送超时)的代码

主要难点在于设置recv()与send()的超时时间,具体要注意的事项,请看代码注释部分,下面是代码: [cpp] view plaincopyprint? #include <stdio.h> #include <sys/types.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <string.h> #ifdef _WIN32   ///

Linux下基于源码方式安装MySQL 5.6

MySQL为开源数据库,因此可以基于源码实现安装.基于源码安装有更多的灵活性.也就是说我们可以针对自己的硬件平台选用合适的编译器来优化编译后的二进制代码,根据不同的软件平台环境调整相关的编译参数,选择自身需要选择不同的安装组件,设定需要的字符集等等一些可以根据特定应用场景所作的各种调整.本文描述了如何在源码方式下安装MySQL. 1.安装环境及介质#安装环境SZDB:~ # cat /etc/issueWelcome to SUSE Linux Enterprise Server 10 SP3

Linux 下configure 参数配置与软件的安装与卸载

Linux环境下的软件安装,并不是一件容易的事情:如果通过源代码编译后在安装,当然事情就更为复杂一些:现在安装各种软件的教程都非常普遍:但万变不离其中,对基础知识的扎实掌握,安装各种软件的问题就迎刃而解了.Configure脚本配置工具就是基础之一,它是autoconf的工具的基本应用. 'configure'脚本有大量的命令行选项.对不同的软件包来说,这些选项可能会有变化,但是许多基本的选项是不会改变的.带上'--help'选项执行'configure'脚本可以看到可用的所有选项.尽管许多选项