6、TCPIP协议分析与编程(之二)

项目任务:

①.理解socket原理与编程基础

②.详细的查一下socket的一些基本操作,函数,结构体等等

③.学会写makefile

④.在linux下完成socket编程实验

具体安排:这部分会有一天半到两天的时间(做的快的可以提前自己做下去),在理解了socket原理和编程基础后,结合给出的部分的代码完成TCP和UDP在linux下客户端与服务器之间的通信。接下去基本就进入编程了,培训我们采用C++,所以以后的内容,将都采用C++来编程,之后将不再强调。

考核内容:

①.理解并掌握socket的原理以及框架(要理解好socket编程框架)

②.自己在linux下用socket函数编写一些通信的小实验

③.根据所给的代码,和实验步骤(给出的代码为不完整版,需要你在理解的基础上填入相应的参数,以及编写相应的函数模块)

④.完成本章最后的作业提交任务和反馈内容(请在实验前看,以免影响你提交作业)

任务、步骤:

任务一:

在了解TCP/IP协议基础上学习socket网络通信编程,完成socket编程实验;

步骤:

socket编程实验:

一、 实验目的

  1. 通过实验了解嵌入式 Linux 的编程
  2. 掌握 Makefile 的使用
  3. 了解 c++类的相关概念
  4. 了解 Socket 通信编程 二、 实验条件
  5. 使用虚拟机装有 Linux 系统的 PC 机一台 三、 实验原理 3.1 网络中进程之间如何通信 本地的进程间通信( IPC)有很多种方式,但可以总结为下面 4 类:

 消息传递(管道、 FIFO、消息队列)

 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)

 共享内存(匿名的和具名的)

 远程过程调用( Solaris 门和 Sun RPC)

在本地可以通过进程 PID 来唯一标识一个进程,但是在网络中这是行不通的。 其实 TCP/IP 协议族已经帮我们解决了这个问题, 网络层的“ip 地址”可以唯一标 识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进 程)。这样利用三元组( ip 地址,协议,端口)就可以标识网络的进程了,网络 中的进程通信就可以利用这个标志与其它进程进行交互。

使用 TCP/IP 协议的应用程序通常采用应用编程接口: UNIX BSD 的套接字 ( socket)和 UNIX System V 的 TLI(已经被淘汰),来实现网络进程之间的通信。

就目前而言,几乎所有的应用程序都是采用 socket,而现在又是网络时代,网络 中进程通信是无处不在,这就是我为什么说“一切皆 socket”。

3.2 Socket 4. 多个 TCP 连接或多个应用程序进程可能需要通过同一个 TCP 协议端口传输 数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与 TCP/IP 协议交互提供了称为套接字(Socket)的接口。

常用的 Socket 类型:

有两种:流式 Socket( SOCK_STREAM)和数据报式 Socket( SOCK_DGRAM)。

流套接字( SOCK_STREAM):流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即 TCP ( The Transmission Control Protocol)协议。

数据报套接字( SOCK_DGRAM):数据报套接字提供了一种无连接的服务。 该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据 重复,且无法保证顺序地接收到数据。

数据报套接字使用 UDP( User DatagramProtocol) 协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

1.连接过程:

根据连接启动的方式以及本地套接字要连接的目标, 套接字之间的连接过程 可以分为三个步骤: 服务器监听, 客户端请求,连接确认。

服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等 待连接的状态,实时监控网络状态。

客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器 端的套接字。为此, 客户端的套接字必须首先描述它要连接的服务器的套接字, 指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

2.连接确认:

是指当服务器端套接字监听到或者说接收到客户端套接字的连接 请求,它就响应客户端套接字的请求,把服务器端套接字的描述发给客户端,一 旦客户端确认了此描述,连接就建立好了。

3.常用的 Socket 操作:

socket()

bind()

listen()

connect()

accept()

read()、 write()

close()

4.函数具体介绍如下: 5. int socket(int domain, int type, int protocol);

domain:即协议域,又称为协议族( family)。常用的协议族有, AF_INET、 AF_INET6、AF_LOCAL(或称 AF_UNIX, Unix 域 socket)、 AF_ROUTE 等等。协议族决定了 socket 的地址类型,在通信中必须采用对应的地址,如 AF_INET 决定了要用 ipv4 地址( 32 位的)与端口号( 16 位的)的组合、 AF_UNIX 决定了要用一个绝对路径名作为地址。

type:指定 socket 类型。常用的 socket 类型有, SOCK_STREAM、 SOCK_DGRAM、 SOCK_RAW、SOCK_PACKET、 SOCK_SEQPACKET 等等( socket 的类型有哪些?)。

protocol:故名思意,就是指定协议。常用的协议有, IPPROTO_TCP、 IPPTOTO_UDP、IPPROTO_SCTP、 IPPROTO_TIPC 等,它们分别对应 TCP 传输协议、 UDP 传输协议、 STCP 传输协议、TIPC 传输协议 该 socket 函数返回一个 socket 描述符,它标识唯一的一个 socket, 可以把它作为参数,用来进行一些读写操作。但创建的 socket 并没有一个具体的地址, 需要使用 bind()完成端口分配等操作。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即 socket 描述字,它是通过 socket()函数创建了,唯一标识一个 socket。 bind()函数就是将给这个描述字绑定一个名字

addr:一个 const struct sockaddr *指针,指向要绑定给 sockfd 的协议地址。这个地址结构根据地址创建 socket 时的地址协议族的不同而不同,如 ipv4 对应的是:

struct sockaddr_in

{

sa_family_t sin_family; / address family: AF_INET /

in_port_t sin_port; / port in network byte order /

struct in_addr sin_addr; / internet address /

};

/ Internet address. /

struct in_addr

{

uint32_t s_addr; / address in network byte order /

};

• > addrlen:对应的是地址的长度

如果作为一个服务器,在调用 socket()、 bind()之后就会调用 listen()来监听这个 socket,

如果客户端这时调用 connect()发出连接请求,服务器端就会接收到这个请求。

• int listen(int sockfd, int backlog);

• int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen 函数的第一个参数即为要监听的 socket 描述字,第二个参数为相应 socket 可以排队的最大连接个数。 socket()函数创建的 socket 默认是一个主动类型的, listen 函数将 socket变为被动类型的,等待客户的连接请求。

connect 函数的第一个参数即为客户端的 socket 描述字,第二参数为服务器的 socket 地址,第三个参数为 socket 地址的长度。客户端通过调用 connect 函数来建立与 TCP 服务器的连接

int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);

accept 函数的第一个参数为服务器的 socket 描述字,第二个参数为指向 struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果 accpet 成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的 TCP 连接。

3.3 TCP/UDP

TCP (Transmission Control Protocol)和 UDP(User Datagram Protocol)协议属于传输层协议。其中 TCP 提供 IP 环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后 再进行数据发送;而 UDP 则不为 IP 提供可靠性、流控或差错恢复功能。一般来 说, TCP 对应的是可靠性要求高的应用,而 UDP 对应的则是可靠性要求低、传输 经济的应用。

1.基于 TCP 客户-服务器模型的程序设计基本框架

2.基本 UDP 客户-服务器程序设计基本框架流程图 3.TCP和UDP的区别: 4.实验内容

根据服务端-客户模型、常用 socket 操作函数等实现 Socket 通信

Socket 基类:

class BaseSock { protected: bool m_bUDP; bool m_bConnected; char m_cHost[BUFFER_SIZE]; unsigned short m_nPort; int m_nSockfd; public: BaseSock(); virtual ~BaseSock(); bool Create(bool bUDP=false); virtual bool Bind(unsigned short nPort); virtual void Close(); virtual bool Send(const char buf, long buflen); virtual long Recv(char buf, long buflen); virtual bool Sendto(const char buf, int len, const struct sockaddr_in toaddr); virtual long RecvFrom(char buf, int len, struct sockaddr_in fromaddr); };

class ClientSock :public BaseSock {

public:

ClientSock();

virtual ~ClientSock();

virtual bool Connect(const char *host, unsigned short port);

virtual long Recv(char *buf, long buflen);

friend class ServerSock;

};

BaseSock 为 Socket 的基类, 完成客户端、 服务端 Socket 都涉及到的函数部分, 主要根据上述原理中提到的服务端、客户端模型。

protected 关键字为受保护的,其针对类以外的外界( ) ,与private关键字效果 一样,均无法访问,但对于派生类, 其与基类的访问权限一致。

Public 关键字为公有的, 为类对外界的接口

BaseSock()为构造函数,创建对象的时候调用

~BaseSock()为析构函数, 销毁对象时自动调用

virtual 声明函数为虚函数(虚函数可在派生类中重新定义)

class B:public A B 类为 A 的派生类

B 为派生类, A 为基类 基类中的公有成员,将成为派生类中的公有成员 基类中的私有成员,只能通过基类的公有或者保护方法访问 派生类需要有自己的构造函数 派生类中可以添加自身额外的成员

这两个成员函数:

virtual bool Connect(const char host, unsigned short port); virtual long Recv(char buf, long buflen); 就是派生类中新增的公有成员函数。

friend 关键字表示友元 友元类或者友元函数能够访问该类的私有部分

需要完成的实验内容:

1). 实现 ClientSock 的成员函数 Connect

2). 实现一个 ServerSock 类,该类基于 BaseSock, 添加额外的成员函数 Listen 与 Aceept

声明如下:

virtual bool Listen();

virtual bool Accept(ClientSock &client);

在 mySocket.h 中,在注释下方完成该类声明,可仿 ClientSock

3). ServerSock 类对 RecvFrom、 Sendto 进行重定义。 使其最后的结果为效果 为:

4). 基本掌握 TCP/UDP 服务器-客户端模型

  1. Connect 函数需要完成的工作: 1) 设置 sockaddr_in 中服务器地址的端口号,注意网络字节序与本机字节序 的问题 2) 连接请求函数 connect()的使用,可以通过 man connect 查看具体的参数 说明
  2. ServerSock 派生类需要完成的工作 1) 在 mySocket.h 中,加入 ServerSock 类的声明,其要求如下: i. 构造函数、析构函数 ii. 加入额外的 Accept、 Listen 成员函数 iii. 对 Sendto 与 RecvFrom 重定义 2) 在 mySocket.cpp 中 i. 构造函数、析构函数

ii. 对 Aceept、 Listen 成员函数实现 Accept 为接收连接请求,通过 accpet 函数完成功能, 具体 accept 函数的使用方法可以通过 man accept 查阅 int accept(int s, struct sockaddr addr, socklen_t addrlen);

Listen 函数为监听, 通过 listen 实现监听 int listen(int s, int backlog); backlog 是侦听队列的长度,在内核函数中,首先对 backlog 作检查,如果大于 128, 则强制使其等于 128

iii. Sendto、 RecvFrom 重新定义

实验效果: 1).服务器:

2).客户端:

作业提交:

1.完成上面socket编程实验,并提交最终的结果,截图提交。

2.提交你所完成的代码;

内容反馈:

请提交您在这一小阶段项目完成的过程中遇到的问题和您的意见及建议提交至内容反馈中,以便我们改进,衷心的感谢。

results matching ""

    No results matching ""

    results matching ""

      No results matching ""