linux tcp工程源码

2016-01-19 17:49:01 KayChanGEEK 阅读数 5500
  • H12-711_HCSCA101_TCPIP协议基础

    大家通过学习这门课程,可以进入Linux源码的大门,通过阅读源码来学习Linux,可以让你真正的理解Linux。同时,也希望大家有阅读源码的好习惯,包括各种开源项目,都会有很大的收获。

    633人学习 马青
    免费试看

我们拿到一个Linux内核源码后,必先熟悉其目录大概的结构。其目录结构

图如下所示:


arch:这个为目录是架构相关,里面存放了许多CPU架构,比如arm,x86,MIPS,PPC等,

block:在linux中block表示块设备(以块(多个字节组成的整体,类似于扇

区)为单位来整体访问),譬如说SD卡、iNand、Nand、硬盘等都是块设备。你几乎可以

认为块设备就是存储设备。block目录下放的是一些linux存储体系中关于块设备管理的代码。

crypto:英文意思是加密。这个目录下放了一些各种常见的加密算法的C语言代码实现。譬

如crc32、md5、sha1等。

Documentation:里面放了一些文档。

drivers:驱动目录,里面分门别类的列出了linux内核支持的所有硬件设备的驱动源代码。

firmware:固件。什么是固件?固件其实是软件,不过这个软件是固话到IC里面运行的

叫固件:就像S5PV210里的iROM代码。

fs:fs就是file system,文件系统,里面列出了linux支持的各种文件系统的实现。

include:头文件目录,公共的(各种CPU架构共用的)头文件都在这里。每种CPU架构

特有的一些头文件在arch/arm/include目录及其子目录下。

init:init是初始化的意思,这个目录下的代码就是linux内核启动时初始化内核的代码。

ipc:ipc就是inter process commuication,进程间通信,里面都是linux支持的IPC的

代码实现。

kernel:kernel就是内核,就是linux内核,所以这个文件夹下放的就是内核本身需要的

一些代码文件。

lib:lib是库的意思,这里面都是一些公用的有用的库函数,注意这里的库函数和C语言

的库函数不一样的。在内核编程中是不能用C语言标准库函数,这里的lib目录下的库函

数就是用来替代那些标准库函数的。譬如在内核中要把字符串转成数字用atoi,但是内

核编程中只能用lib目录下的atoi函数,不能用标准C语言库中的atoi。譬如在内核中要打

印信息时不能用printf,而要用printk,这个printk就是我们这个lib目录下的。

mm:mm是memory management,内存管理,linux的内存管理代码都在这里。

net:该目录下是网络相关的代码,譬如TCP/IP协议栈等都在这里。

scripts:脚本,这个目录下全部是脚本文件,这些脚本文件不是linux内核工作时使用的,

而是用来辅助对linux内核进行配置编译生产的。我们并不会详细进入分析这个目录下的

脚本,而是通过外围来重点学会配置和编译linux内核即可。

security:安全相关的代码。不用去管。

sound:音频处理相关的。

tools:linux中用到的一些有用工具

usr:目录下是initramfs相关的,和linux内核的启动有关,暂时不用去管。

virt:内核虚拟机相关的,暂时不用管。

Kbuild:Kbuild是kernel build的意思,就是内核编译的意思。这个文件就是linux内核特有

的内核编译体系需要用到的文件。

Makefile:这个是linux内核的总makefile,整个内核工程用这个Makefile来管理的。


2018-10-23 10:31:52 linuxweiyh 阅读数 1905
  • H12-711_HCSCA101_TCPIP协议基础

    大家通过学习这门课程,可以进入Linux源码的大门,通过阅读源码来学习Linux,可以让你真正的理解Linux。同时,也希望大家有阅读源码的好习惯,包括各种开源项目,都会有很大的收获。

    633人学习 马青
    免费试看

linux内核源码目录结构如下所示:

(1)Kbuild

Kbuild是Kernel build的意思,就是内核编译的意思,这个文件就是linux内核特有的内核编译体系需要用到的文件。

(2)Makefile

这个是linux内核的总Makefile,整个内核工程是用这个Makefile来管理的。

(3)mk

这个文件是九鼎在移植时自己添加的,不是linux内核本身的东西。九鼎添加这个文件的作用是用这个文件来整个管理kernel目录的配置和编译。

(4)arch

arch是architecture的缩写,意思是架构。arch目录下是好多个不同架构的CPU的子目录,例如ARM这种CPU的所有文件都在arch/arm目录下,X86的CPU的所有文件都在arch/x86目录下。

(5)block

英文是块的意思,在linux中block表示块设备(以块(多个字节组成一个整体,类似于扇区)为单位进行访问),例如SD卡、iNand、Nand、硬盘等都是块设备。几乎可以认为块设备就是存储设备。block目录下放的是一些linux存储体系中关于块设备管理的代码。

(6)crypto

英文意思是加密。这个目录下放了一些各种常见的加密算法的C语言代码实现,例如crc32、md5、sha1等。

(7)Documentation

这个目录下放了一些文档。

(8)drivers

驱动目录,里面分门别类地列出了linux内核支持的所有硬件设备的驱动源代码。

(9)firmware

固件。什么叫固件?固件其实就是软件,不过这个软件是固化到IC里面运行的,就像S5PV210里的iROM代码。

(10)fs

fs就是file system,文件系统,里面列出了linux支持的各种文件系统的实现。

(11)include

头文件目录,公共的(各种CPU架构共用的)头文件都在这里。各种CPU架构特有的一些头文件在arch/arm/include目录及其子目录下。

(12)init

init是初始化的意思,这个目录下的代码就是linux内核启动时初始化内核的代码。

(13)ipc

ipc就是inter process communication,进程间通信,里面都是linux支持的IPC的代码实现。

(14)kernel

kernel就是内核,就是linux内核,所以这个文件夹下放的就是内核本身需要的一些代码文件。

(15)lib

lib是库的意思,这里面都是一些公用的有用的库函数,注意这里的库函数和C语言的库函数是不一样的。在内核编程中是不能用C语言标准库函数,这里的lib目录下的库函数就是用来替代那些标准库函数的。例如在内核中要把字符串转成数字用atoi,但是在内核编程中只能用lib目录下的atoi函数,不能用标准C语言库中的atoi。还比如在内核中要打印信息时不能用printf,而要用printk,这个printk就是我们这个lib目录下的。

(16)mm

mm就是memory management,内存管理,linux的内存管理代码都在这里。

(17)net

该目录下是网络相关的代码,例如TCP/IP协议栈等。

(18)scripts

脚本,这个目录下全部是脚本文件,这些脚本文件不是linux内核工作时使用的,而是用来辅助linux内核的配置和编译。这个目录下的脚本不需要详细的分析,只需要通过外围来重点学会配置和编译linux内核即可。

(19)security

安全相关的代码,不需要去管。

(20)sound

音频处理相关的。

(21)tools

linux中用到的一些有用工具。

(22)usr

目录下是initramfs相关的,和linux内核的启动有关,暂时可以不管。

(23)virt

内核虚拟机相关的,暂时可以不管。

注:与内核移植紧密的目录是arch和drivers,有点相关的是include、block、mm、net、lib等目录。

 

2019-05-24 19:52:55 wenjs0620 阅读数 540
  • H12-711_HCSCA101_TCPIP协议基础

    大家通过学习这门课程,可以进入Linux源码的大门,通过阅读源码来学习Linux,可以让你真正的理解Linux。同时,也希望大家有阅读源码的好习惯,包括各种开源项目,都会有很大的收获。

    633人学习 马青
    免费试看

基于TCP/IP的网络通信应用程序(TCP-Server)

上一篇文章讲述了在i.MX6UL开发板中,以客户端的角色,使用TCP/IP协议进行网络通信。

嵌入式Linux应用程序开发-(7)TCP-IP网络通信应用程序(TCP-Client)

本章节,将以服务端的角色进行讲解,如何开发一个TCP服务端(TCP-Server)。

目标:使用QT提供的TCP/IP网络通信类,实现一个简单的TCP服务端(TCP-Server)

功能:

(1)开发板界面显示开发板服务端的网络IP地址。

(2)可手动输入需要监听的网络端口。

(3)提供按钮,可手动开启/停止服务端监听。

(4)界面显示TCP客户端的收发数据,并提供清屏按钮。

(5)提供服务端手动发送按钮和自动发送按钮。

开发板运行TCP服务端(TCP-Server)后,界面如下图所示:

服务端界面描述:

1、服务端程序启动后,先获取本机IP地址作为服务器的IP地址。程序默认监听4418端口,用户可自定义修改需要监听的端口

2、点击[LISTEN]按钮,开始服务端监听,等待客户端连接。

3、客户端连接成功后,会在数据显示窗口提示客户端上线,并在客户端列表显示每个客户端的IP地址和连接端口。

4、用户点击[START_AUTO_SEND]按钮,服务端以1秒的频率,自动对所有客户端发送固定数据。再次点击该按钮,停止自动发送数据。

5、用户每点击一次[tcp_send_data]按钮,服务端对指定的客户端发送一帧固定数据。

6、用户点击[CLEAR]按钮,清空数据显示窗口的内容。

对于TCP/IP的服务端(TCP-Server)角色,在进行数据通信之前,一般会经历以下过程:

(1)调用socket套接字函数,创建套接字的文件描述符(这个套接字是用来监听客户端连接请求的)。

(2)调用bind()绑定函数,将创建成功的套接字与需要监听的IP地址和Port绑定。

(3)绑定成功后,就可以调用listen()函数进行监听,等待客户端的连接请求。(服务端需要成功调用listen()函数,客户端才可以发起连接请求,否则,客户端的连接会出错)

(4)调用listen()函数成功后,若此时有客户端申请建立连接,服务端则调用accept()函数,接收客户端的连接,并自动产生用于网络I/O通信的套接字,作为accept()函数的返回值。(accept()函数自动产生的套接字是用来进行网络I/O数据收发的,与socket()套接字不同)

(5)当客户端连接成功后,服务端就可以基于accept()返回的套接字,使用系统调用的读写函数read()/write()进行数据收发了。

使用嵌入式QT进行TCP/IP的网络通信应用程序开发,对于TCP服务端,QT的network类库提供的QTcpServer类,这个类继提供了一系列的服务端网络操作接口函数,如:监听函数listen(),阻塞等待客户端连接waitForNewConnection(),虚函数incomingConnection()用来处理客户端的连接请求。更多接口函数,具体可以参阅 QtNetwork/qtcpserver.h 文件的内容。在服务端应用程序里面,我们通常建立一个继承于QTcpSocket的类,来描述每一个连接成功的客户端,每一个客户端的具体信息,可以通过这个类来获取。关于这个类的使用方法,请参考上一章节的内容。

 

以下是TCP服务端(TCP-Server)应用程序的开发过程。

1、先用Qt Creator构建一个工程,命名为:008_tcp_server,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

2、创建工程后,修改008_tcp_server.pro里面的内容,添加QT里面的网络通信模块network,使工程支持QT网络类的调用,如下图所示。

3、双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:

服务端界面描述如上面内容所示,这里不作重复。

4、对于一个合格的TCP服务端,由于要给多个TCP客户端提供服务,因此,难以避免处理多个客户端连接,以及对指定的客户端进行数据收发。因此,TCP服务端需要一个TcpClient类来描述每个客户端的信息,这个TcpClient类继承于QTcpSocket,注意,这个TcpClient类与上一章节的TcpClient类有些差异。它是用来描述客户端,而非创建一个客户端,如下图所示:

//定义一个TcpClient类,这个类用于TcpServer的基础通信
class TcpClient : public QTcpSocket
{
    Q_OBJECT
public:
    explicit TcpClient(QObject *parent = 0);

private:
    QString client_ip;     //连接上服务器的客户端IP
    int client_port;       //连接上服务器的客户端端口

private slots:
    void slot_read_data();     //读取每个客户端发送上来的数据

signals:
    //数据发送信号,用于通知ui线程,让ui显示哪个客户端发送了哪些数据
    void signals_send_data(const QString &ip, int port, const QString &data);
    //数据接收信号,用于通知ui线程,让ui显示服务端接收的数据来自哪个客户端
    void signals_receive_data(const QString &ip, int port, const QString &data);

public slots:
    void slot_set_client_ip(const QString &ip);   //用于设置client_ip这个变量的值
    void slot_set_client_port(int port);   //用于设置client_port这个变量的值
    void slot_send_data(const QString &data);   //用于服务端向指定的客户端发送数据
};

5、创建好TcpClient类之后,对于服务端,我们还需要创建一个TcpServer类,用来描述整个服务端的操作,这个TcpServer类包含了服务端的数据收发函数,连接成功的客户端列表QList<TcpClient *> tcp_clients,启动和停止服务端的监听,重载void incomingConnection(int handle);虚函数,等等。TcpServer类的具体定义,如下图所示:

//定义一个TcpServer类
class TcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit TcpServer(QObject *parent = 0,int port = 0);

private:
    QList<TcpClient *> tcp_clients;    //客户端列表,用于保存已经连接成功的客户端

    int listen_port;    //服务器监听的端口
protected:
    //重载此函数,这个函数主要用来通知TcpServer有新的连接准备建立
    void incomingConnection(int handle);

private slots:
    void slot_disconnected();   //关闭服务器,断开与客户端的所有连接

signals:
    //数据发送信号,用于通知ui线程,让ui显示哪个客户端发送了哪些数据
    void signal_send_data(const QString &ip, int port, const QString &data);
    //数据接收信号,用于通知ui线程,让ui显示服务端接收的数据来自哪个客户端
    void signal_receive_data(const QString &ip, int port, const QString &data);
    //连接成功信号,用于通知连接服务器成功
    void signal_client_connected(const QString &ip, int port);
    //断开成功信号,用于通知断开服务器成功
    void signal_client_disconnected(const QString &ip, int port);

public slots:
    bool slot_server_start();//启动服务器监听
    void slot_server_stop();//停止服务器监听
    void slot_send_data(const QString &ip, int port, const QString &data);//指定连接的客户端发送数据
    void slot_send_data(const QString &data);//对所有连接的客户端发送数据
    QString get_local_ipaddr(void); //获取本机的IP地址
};

6、以上的两个类创建完成后,我们创建一个tcp_server.cpp文件,这个文件主要是用来编写Tcp-Client类和Tcp-Server类里面各个函数的具体实现,关于整个tcp_server.cpp文件的具体内容,请参阅源码,里面有详细的注释。以下列出关键的函数进行讲解

7、Tcp-Client类是用来描述每个连接服务端成功的客户端的,这个类的构造函数,如下图所示:

//TcpServer的基础通信类Tcp_Client的构造函数
TcpClient::TcpClient(QObject *parent) :  QTcpSocket(parent)
{
    client_ip = "127.0.0.1";
    client_port = 4418;

    //绑定通信出错的槽函数
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(deleteLater()));
    //绑定通信断开的槽函数
    connect(this, SIGNAL(disconnected()), this, SLOT(deleteLater()));
    //绑定数据读取的槽函数
    connect(this, SIGNAL(readyRead()), this, SLOT(slot_read_data()));
}

构造函数主要绑定了错误相关的槽函数,通信断开槽函数,以及数据接收的槽函数。当服务端底层I/O接收到客户端发送上来的数据的时候,就会调用slot_read_data()进行处理。

8、对于每个连接服务端成功的客户端,服务端可以使用以下的函数,对其进行数据的接收和发送,函数实现如下图所示:

//用于服务端向指定的客户端发送数据
void TcpClient::slot_send_data(const QString &data)
{
    QByteArray buffer;

    buffer = data.toLatin1();

    this->write(buffer); //发送数据

    //通过此信号,告诉ui是发送给哪个客户端,以及数据的具体内容
    emit signals_send_data(client_ip, client_port, data);
}

//读取客户端数据的槽函数
void TcpClient::slot_read_data()
{
    QByteArray data = this->readAll();   //读取所有接收到的数据

    if (data.length() <= 0)return;   //如果数据长度为0,返回

    QString buffer;
    buffer = QString(data);   //把接收到的数据转化为QString类型

    //通过信号,把接收到的数据,以及来自哪个客户端的IP和端口,都发送出去
    emit signals_receive_data(client_ip, client_port, buffer);
}

对于数据发送函数void TcpClient::slot_send_data(const QString &data),主要是把需要发送的数据转为QByteArray,然后调用QIODevice::write()函数进行发送,发送完成后,调用信号函数signals_send_data(client_ip, client_port, data); 通知widget类,是向哪个客户端发送了哪些数据。

对于数据接收函数void TcpClient::slot_read_data(),主要是跟底层I/O的readyRead()信号进行绑定,当接收到服务端的数据时,会调用QIODevice::readAll()函数,把缓冲区的数据全部读出,然后通过signals_receive_data(client_ip, client_port, buffer);信号,把客户端的IP地址,端口,数据内容都发送出去。

9、Tcp-Server类主要用来描述Tcp服务端的,里面实现了服务端的开始监听与停止监听的函数,如下图所示:

//启动服务器监听
bool TcpServer::slot_server_start()
{
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
    bool ok = listen(QHostAddress::AnyIPv4, listen_port);
#else
    bool ok = listen(QHostAddress::Any, listen_port);
#endif

    return ok;
}

//停止服务器监听
void TcpServer::slot_server_stop()
{
    foreach (TcpClient *client, tcp_clients) {
        client->disconnectFromHost();
    }

    this->close();
}

主要调用listen函数,启动服务器监听所有地址的客户端连接。对于停止服务器监听,则先断开所有的客户端连接后,调用close()函数关闭服务端监听。

10、服务器的监听函数成功启动后,对于客户端发起的连接请求,服务端会调用一个虚函数void TcpServer::incomingConnection(int handle)进行处理,这个虚函数的具体实现,如下图所示:

//重载此函数,这个函数主要用来通知TcpServer有新的连接准备建立
void TcpServer::incomingConnection(int handle)
{
    //构建一个TcpClient对象,用来描述连接上的Tcp客户端
    TcpClient *client = new TcpClient(this);

    client->setSocketDescriptor(handle);  //保存客户端的设备描述符

    //绑定客户端断开连接的槽函数
    connect(client, SIGNAL(disconnected()), this, SLOT(slot_disconnected()));
    //绑定客户端发送数据的信号,与服务端的发送数据信号绑定
    connect(client, SIGNAL(signals_send_data(QString, int, QString)), this, SIGNAL(signal_send_data(QString, int, QString)));
    //绑定客户端接收数据的信号,与服务端的接收数据信号绑定
    connect(client, SIGNAL(signals_receive_data(QString, int, QString)), this, SIGNAL(signal_receive_data(QString, int, QString)));

    QString ip = client->peerAddress().toString();//获取连接成功的客户端的IP地址
    int port = client->peerPort();  //获取连接成功的客户端的端口
    client->slot_set_client_ip(ip);
    client->slot_set_client_port(port);

    emit signal_client_connected(ip, port);  //发送信号,通知ui客户端连接成功
    emit signal_send_data(ip, port, trUtf8("客户端上线"));

    tcp_clients.append(client);   //把这个客户端加入客户端列表
}

当有客户端的连接请求,这个函数就会被调用,在这个函数里面,保存客户端的设备描述符,这个描述符是服务端和客户端通信的基础。然后绑定各个槽函数,分别是断开连接槽函数,数据收发槽函数,然后把连接上来的客户端IP和端口保存下来。在发送信号通知ui界面,有客户端上线了。最后,把这个客户端对象保存在服务端的tcp_clients列表中。

11、对于客户端主动断开连接,调用以下函数进行处理:

//客户端断开槽函数
void TcpServer::slot_disconnected()
{
    //断开连接后从链表中移除
    TcpClient *client = (TcpClient *)sender();
    QString ip = client->peerAddress().toString();

    int port = client->peerPort();

    emit signal_client_disconnected(ip, port);
    emit signal_send_data(ip, port, trUtf8("客户端下线"));

    tcp_clients.removeOne(client); //把这个客户端从客户端列表中移除
}

这个槽函数是在void TcpServer::incomingConnection(int handle)里面,与TcpClient类的disconnected()进行绑定的,当客户端连接断开后,函数会发送信号给ui界面,告知客户端下线,然后把客户端从tcp_clients列表中移除。

12、服务端可以对指定的客户端发送数据,也可以对所有的客户端发送数据,具体的函数实现,如下图所示:

//对指定连接的客户端发送数据
void TcpServer::slot_send_data(const QString &ip, int port, const QString &data)
{
    foreach (TcpClient *client, tcp_clients)   //从客户端列表中取出客户端
    {
        //如果IP地址和端口匹配
        if (client->peerAddress().toString() == ip && client->peerPort() == port)
        {
            client->slot_send_data(data);   //发送数据
            break;
        }
    }
}

//对所有连接的客户端发送数据
void TcpServer::slot_send_data(const QString &data)
{
    foreach (TcpClient *client, tcp_clients)
    {
        client->slot_send_data(data);
    }
}

对指定的客户端发送数据,则先从客户端列表中获取匹配的IP地址和端口,获取成功后,调用TcpClient::slot_send_data()函数发送数据。

对所有的客户端发送数据,则不用匹配IP地址和端口,直接对列表中所有的客户端发送数据。

13、TcpClient类和TcpServer类的具体实现已经介绍完毕,在这里,我们就可以在界面的构造类Class Widget里面,基于TcpServer构建服务端应用。Widget类是ui的界面类,关于ui界面的操作,都在该类实现,Widget类的构造函数,如下图所示:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    bool ok;
    int listen_port = ui->lineEdit_port->text().toInt(&ok,10);

    tcp_server = new TcpServer(this,listen_port);   //定义一个tcp_server对象

    //绑定服务端数据发送的信号槽,用于显示服务端的数据发送
    connect(tcp_server, SIGNAL(signal_send_data(QString, int, QString)), this, SLOT(slot_send_data(QString, int, QString)));
    //绑定服务端数据接收的信号槽,用于显示服务端的数据接收
    connect(tcp_server, SIGNAL(signal_receive_data(QString, int, QString)), this, SLOT(slot_receive_data(QString, int, QString)));
    //绑定客户端连接成功的信号槽,用来显示客户端上线
    connect(tcp_server, SIGNAL(signal_client_connected(QString, int)), this, SLOT(slot_client_connected(QString, int)));
    //绑定客户端断开连接的信号槽,用来显示客户端下线
    connect(tcp_server, SIGNAL(signal_client_disconnected(QString, int)), this, SLOT(slot_client_disconnected(QString, int)));

    ui->pushButton_listen->setCheckable(false);
    ui->pushButton_send_data->setEnabled(false);   //发送数据按钮不可用
    ui->pushButton_auto_send_data->setEnabled(false); //自动发送数据按钮不可用

    auto_send_timer = new QTimer();     //构建一个TCP自动发送数据的定时器
    connect( auto_send_timer, SIGNAL(timeout()), this, SLOT(slot_auto_send_timer_handler()));  //关联定时器超时槽函数

    ui->label_local_ip_display->setText(tcp_server->get_local_ipaddr());   //显示本机的IP地址
}

在这个构造函数里面,主要定义了一个tcp_server对象,然后把这个tcp_server对象里面的各种信号与相关的槽函数进行绑定,对界面的按钮控件进行初始化,定义一个定时器,用来自动发送数据,最后,在界面显示本机的IP地址。

14、widget.cpp还实现了按钮控件和显示控件的相关功能,具体的实现函数,请参考widget.cpp的源码文件,部分源码如下图所示:

/*************************************************
>>>>>>>>>>>>  源码篇幅太长,此处适当省略  >>>>>>>>>>
*************************************************/

//服务端发送数据的槽函数,用于ui显示
void Widget::slot_send_data(const QString &ip, int port, const QString &data)
{
    QString str = QString("[%1:%2] %3").arg(ip).arg(port).arg(data);
    text_browser_append(0, str);
}

//服务端接收数据的槽函数,用于ui显示
void Widget::slot_receive_data(const QString &ip, int port, const QString &data)
{
    QString str = QString("[%1:%2] %3").arg(ip).arg(port).arg(data);
    text_browser_append(1, str);
}

//客户端上线的槽函数,加到客户端列表进行显示
void Widget::slot_client_connected(const QString &ip, int port)
{
    QString str = QString("%1:%2").arg(ip).arg(port);
    ui->listWidget_client_list->addItem(str);
}

//客户端下线的槽函数,从客户端列表进行移除
void Widget::slot_client_disconnected(const QString &ip, int port)
{
    int row = -1;
    QString str = QString("%1:%2").arg(ip).arg(port);

    for (int i = 0; i < ui->listWidget_client_list->count(); i++)
    {
        if (ui->listWidget_client_list->item(i)->text() == str)
        {
            row = i;
            break;
        }
    }

    ui->listWidget_client_list->takeItem(row);
}

/*************************************************
>>>>>>>>>>>>  源码篇幅太长,此处适当省略  >>>>>>>>>>
*************************************************/

15、至此,整个TCP服务端已经开发完毕,编译成功后,下载到开发板运行,实验现象如下图所示:

16、以上只是实现了一个简单的TCP服务端应用程序,并在单个线程里面处理了少量的TCP客户端连接,对于大规模的TCP服务端应用程序,还需要考虑高并发,数据低延迟,如何管理大规模的客户端数量,保证服务端7*24小时运行不宕机,等等,这些大规模的服务端程序,都是运行在性能较高的硬件上。在硬件性能满足的前提下,如果嵌入式设备需要管理比较多的客户端连接,建议采用线程池的管理方式,对客户端分批采用线程池管理,即可达到多客户端管理,更多的网络服务应用技术,需要开发者不断在工程应用中不断优化,不断积累经验,才能不断进步。

 

                                                   欢迎关注公众号 【微联智控】

 

2016-12-21 22:04:14 wanna_wsl 阅读数 8074
  • H12-711_HCSCA101_TCPIP协议基础

    大家通过学习这门课程,可以进入Linux源码的大门,通过阅读源码来学习Linux,可以让你真正的理解Linux。同时,也希望大家有阅读源码的好习惯,包括各种开源项目,都会有很大的收获。

    633人学习 马青
    免费试看

在Linux下使用C语言TCPSocket实现简单文件传输,包括客户端和服务器端,其中,服务器端使用多线程实现同时接收多个客户端发送的文件。

发送文件内容之前,首先需要将文件名和长度信息发送到服务器,为了便于区分,采用发送结构体的方式,设置标志位,1标识数据域为文件名,2标识数据域为文件内容,3标识发送结束,4标识发送文件长度。这样便可区分发送的内容。

服务器端代码如下:

/*多线程实现接收多个客户端的文件*/
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8887
#define BUFF_SIZE 1024

typedef struct{
    char type;
    char data[BUFF_SIZE];
}m_package;

void* process_client();

int main(){
    int ss = create_tcp_server(PORT);
    if(-1 == ss)
        exit(-1);
    while(1){
        //接受客户端连接
        socklen_t addrlen = sizeof(struct sockaddr);
        struct sockaddr_in client_addr; //客户端地址结构
        int client_sock = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
        if(client_sock < 0){
            printf("accept error\n");
        }
        printf("accept success\n");

        pthread_t pid;
        if(pthread_create(&pid, NULL, process_client, &client_sock) < 0){
            printf("pthread_create error\n");
        }
    }
}

//处理客户端程序
void *process_client(void *arg){
    int size = 0, fd, count = 0, sockid = *(int*)arg;
    m_package pac;
    long total = 0, cur = 0;
    //循环接收文件
    while(1) {
        memset(&pac, 0, sizeof(pac));
        size = read(sockid, &pac, sizeof(pac));
        if(size > 0){
            if (pac.type == 1){
                fd = open(pac.data, O_CREAT|O_WRONLY, 0777);
                if(-1 == fd){
                    printf("open file error!\n");
                    continue;
                }
                count = total = cur = 0;
            }
            else if (pac.type == 2){
                cur += write(fd, pac.data, strlen(pac.data));
                if(count++ % 5000 == 0){
                    printf("recv from client < %d > : %.01lf\%\n", sockid, cur * 100.0 / total);
                    count = 0;
                }
            }
            else if (pac.type == 3){
                printf("recv from client < %d > : 100.0\%\n", sockid);
                printf("recv success\n");
                close(fd);
            }
            else if(pac.type == 4){//文件长度
                total = strtol(pac.data, NULL, 10);
                printf("%ld\n", total);
            }
        }else{
            printf("client disconnected\n");
            close(sockid);
            break;
        }
    }
    return 0;
}

服务器端对创建服务器代码进行了简单封装,需要在工程下添加相关的头文件和源文件,添加的代码见博客:http://blog.csdn.net/wanna_wsl/article/details/53712066

客户端对创建连接同样进行了简单封装,需引进封装的代码,同样参见博客
http://blog.csdn.net/wanna_wsl/article/details/53712066
客户端代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>

#define PORT 8888
#define BUFF_SIZE 1024

typedef struct{
    char type;
    char data[BUFF_SIZE];
}m_package;

int main(){
    //创建连接
    int sock_fd = connect_tcp("192.168.134.188", PORT);
    if(-1 == sock_fd)
        return -1;

    m_package pac;
    int fd, cur = 0, count = 0;
    long filesize = 0;
    while(1){
        //打开文件
        memset(&pac, 0, sizeof(pac));
        pac.type = 1;
        // strcpy(pac.data, "/home/SKZH/a.txt");
        scanf("%s", pac.data);
        //获取文件信息
        struct stat sfile;
        stat(pac.data, &sfile );
        filesize = sfile.st_size;
        time_t t;
        long begin = time(&t);
        cur = count = 0;

        fd = open(pac.data, O_RDONLY);
        if(-1 == fd){
            printf("file open error\n");
            continue;
        }
        //读取文件并发送
        //发送文件名
        strcpy(pac.data, strrchr(pac.data, '/') + 1);
        write(sock_fd, &pac, sizeof(pac));
        memset(&pac, 0, sizeof(pac));

        //发送文件长度
        pac.type = 4;
        sprintf(pac.data,"%ld",filesize);
        write(sock_fd, &pac, sizeof(pac));
        memset(&pac, 0, sizeof(pac));

        int read_len = 0;
        while((read_len = read(fd, pac.data, BUFF_SIZE)) > 0){
            pac.type = 2;
            write(sock_fd, &pac, sizeof(pac));
            memset(&pac, 0, sizeof(pac));
            cur += read_len;
            if(count++ % 5000 == 0){
                count = 0;
                printf("send to server : %.1lf\%\n", cur * 100.0 / filesize);
            }
        }

        //发送结束标记
        memset(&pac, 3, sizeof(pac));
        write(sock_fd, &pac, BUFF_SIZE + 1);
        close(fd);

        printf("send to server : 100.0\%\n");
        printf("file size : %d B\n", filesize);
        printf("time : %ld ms\n", time(&t) - begin);
        printf("send file success\n");
        printf("------------------------\n");
    }
    close(sock_fd);
}

源码下载地址:
https://github.com/Wushaoling/Linux-C-Socket/tree/master/TCP/TCP%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93

2018-03-14 18:03:34 qq_38892883 阅读数 5942
  • H12-711_HCSCA101_TCPIP协议基础

    大家通过学习这门课程,可以进入Linux源码的大门,通过阅读源码来学习Linux,可以让你真正的理解Linux。同时,也希望大家有阅读源码的好习惯,包括各种开源项目,都会有很大的收获。

    633人学习 马青
    免费试看

学习Linux也有一段时间了,具体来整理一下Linux源码的目录结构和文件系统的目录结构,以便加深记忆。

一、Linux源码的目录结构

首先上一张截图,如下所示:


再看各个文件的介绍,借用一下其他资源。

arch:这个为目录是架构相关,里面存放了许多CPU架构,比如arm,x86,MIPS,PPC等,

处理器原厂提供一套Linux内核的源码,那么在这个目录下都有一套针对具体处理器CPU的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录,分别控制系统引导,内存管理,系统调用,动态调频,主频率设置部分等。
在arch目录中有关键的平台文件。任何一款支持Linux的处理器,都有一部分内核代码是针对特定的处理器来提供的,具体的实现就是通过平台文件。
迅为4412的平台文件,是arch→arm→mach-exynos→mach-itop4412.c。
arch→arm→boot目录,默认编译生成的内核镜像是在这个目录下。
在arch→arm→kernel目录中,有针对具体CPU处理器的代码,有相关内核特性实现方式,如信号处理等。这一部分当然是芯片厂商做好了,4412的这部分就是三星已经做好的部分。
在arch→arm→lib目录中,有一些和硬件相关库函数,后面学习驱动的时候会使用到。

在arch→arm→tools目录中,包含了生成镜像的工具。


block:在linux中block表示块设备(以块(多个字节组成的整体,类似于扇区)为单位来整体访问),譬如说SD卡、iNand、Nand、硬盘等都是块设备。你几乎可以认为块设备就是存储设备。block目录下放的是一些linux存储体系中关于块设备管理的代码。

crypto:英文意思是加密。这个目录下放了一些各种常见的加密算法的C语言代码实现。譬如crc32、md5、sha1等。

Documentation:里面放了一些文档。

drivers:驱动目录,里面分门别类的列出了linux内核支持的所有硬件设备的驱动源代码。就是需要重点学习的部分。

firmware:固件。什么是固件?固件其实是软件,不过这个软件是固话到IC里面运行的叫固件:就像S5PV210里的iROM代码。

fs:fs就是file system,文件系统,里面列出了linux支持的各种文件系统的实现。

include:头文件目录,公共的(各种CPU架构共用的)头文件都在这里。每种CPU架构特有的一些头文件在arch/arm/include目录及其子目录下。

init:init是初始化的意思,这个目录下的代码就是linux内核启动时初始化内核的代码。


如下图所示,部分目录如下。下面的这些目录,几乎不需要我们去动其中任何一个文件。

ipc:ipc就是inter process commuication,进程间通信,里面都是linux支持的IPC的代码实现。

kernel:kernel就是内核,就是linux内核,所以这个文件夹下放的就是内核本身需要的一些代码文件。

lib:lib是库的意思,这里面都是一些公用的有用的库函数,注意这里的库函数和C语言的库函数不一样的。在内核编程中是不能用C语言标准库函数,这里的lib目录下的库函数就是用来替代那些标准库函数的。譬如在内核中要把字符串转成数字用atoi,但是内核编程中只能用lib目录下的atoi函数,不能用标准C语言库中的atoi。譬如在内核中要打印信息时不能用printf,而要用printk,这个printk就是我们这个lib目录下的。
mm:mm是memory management,内存管理,linux的内存管理代码都在这里。
net:该目录下是网络相关的代码,譬如TCP/IP协议栈等都在这里。


scripts:脚本,这个目录下全部是脚本文件,这些脚本文件不是linux内核工作时使用的,而是用来辅助对linux内核进行配置编译生产的。我们并不会详细进入分析这个目录下的脚本,而是通过外围来重点学会配置和编译linux内核即可。
security:安全相关的代码。不用去管。
sound:音频处理相关的。
tools:linux中用到的一些有用工具
usr:目录下是initramfs相关的,和linux内核的启动有关,暂时不用去管。


virt:内核虚拟机相关的,暂时不用管。
Kbuild:Kbuild是kernel build的意思,就是内核编译的意思。这个文件就是linux内核特有的内核编译体系需要用到的文件。
Makefile:这个是linux内核的总makefile,整个内核工程用这个Makefile来管理的。

二、文件系统目录结构

先上图,如下图所示:


1、/-
每一个文件和目录从根目录开始。

只有root用户具有该目录下的写权限。请注意,/root是root用户的主目录,这与/.不一样

2、/bin中 - 用户二进制文件

包含二进制可执行文件。
在单用户模式下,你需要使用的常见Linux命令都位于此目录下。系统的所有用户使用的命令都设在这里。

例如:ps、ls、ping、grep、cp

3、/sbin目录 - 系统二进制文件

就像/bin,/sbin同样也包含二进制可执行文件。

但是,在这个目录下的linux命令通常由系统管理员使用,对系统进行维护。例如:iptables、reboot、fdisk、ifconfig、swapon命令

4、/etc - 配置文件

包含所有程序所需的配置文件。
也包含了用于启动/停止单个程序的启动和关闭shell脚本。例如:/etc/resolv.conf、/etc/logrotate.conf

hosts:设备名称(或域名)到ip地址的解析,相当于本地存在的dns功能。

5、/dev - 设备文件

包含设备文件。

这些包括终端设备、USB或连接到系统的任何设备。例如:/dev/tty1、/dev/usbmon0

6、/proc - 进程信息

包含系统进程的相关信息。
这是一个虚拟的文件系统,包含有关正在运行的进程的信息。例如:/proc/{pid}目录中包含的与特定pid相关的信息。

这是一个虚拟的文件系统,系统资源以文本信息形式存在。例如:/proc/uptime

7、/var - 变量文件

var代表变量文件。
这个目录下可以找到内容可能增长的文件。

这包括 - 系统日志文件(/var/log);包和数据库文件(/var/lib);电子邮件(/var/mail);打印队列(/var/spool);锁文件(/var/lock);多次重新启动需要的临时文件(/var/tmp);

8、/tmp - 临时文件

包含系统和用户创建的临时文件。

当系统重新启动时,这个目录下的文件都将被删除。

9、/usr - 用户程序

包含二进制文件、库文件、文档和二级程序的源代码。
/usr/bin中包含用户程序的二进制文件。如果你在/bin中找不到用户二进制文件,到/usr/bin目录看看。例如:at、awk、cc、less、scp。
/usr/sbin中包含系统管理员的二进制文件。如果你在/sbin中找不到系统二进制文件,到/usr/sbin目录看看。例如:atd、cron、sshd、useradd、userdel。
/usr/lib中包含了/usr/bin和/usr/sbin用到的库。

/usr/local中包含了从源安装的用户程序。例如,当你从源安装Apache,它会在/usr/local/apache2中

10、/home - HOME目录

所有用户用home目录来存储他们的个人档案。

例如:/home/john、/home/nikita

11、/boot - 引导加载程序文件

包含引导加载程序相关的文件。
内核的initrd、vmlinux、grub文件位于/boot下。

例如:initrd.img-2.6.32-24-generic、vmlinuz-2.6.32-24-generic

12、/lib - 系统库

包含支持位于/bin和/sbin下的二进制文件的库文件.
库文件名为 ld*或lib*.so.*

例如:ld-2.11.1.so,libncurses.so.5.7

13、/opt - 可选的附加应用程序

opt代表可选的。
包含从个别厂商的附加应用程序。

附加应用程序应该安装在/opt/或者/opt/的子目录下。

14、/mnt - 挂载目录

临时安装目录,系统管理员可以挂载文件系统。15、/media - 可移动媒体设备
用于挂载可移动设备的临时目录。
举例来说,挂载CD-ROM的/media/cdrom,挂载软盘驱动器的/media/floppy;16、/srv - 服务数据
srv代表服务。
包含服务器特定服务相关的数据。
例如,/srv/cvs包含cvs相关的数据。