【华清远见嵌入式培训】网络编程(更新中)

Pre-learning:网络编程

  • 不借助第三方软件实现不同主机、不同操作系统之间的通信。搭建服务器与客户端。

Chapter 1 网络发展史与背景

1.1 网络发展简史

ARPnet(阿帕网)因为没有纠错和实现不同计算机/操作系统之间的互联,所以经后来的进化成为了TCP/IP协议( TCP专门用于纠错检错的传输控制协议;IP专门用于对不同网络进行互联的互联网协议 )(了解这么多就够)

1.2 局域网和广域网

  • 局域网(local area network),顾名思义,是个本地的网络,只能实现小范围短距离的网络通信。我们的家庭网络是典型的局域网。电脑、手机、电视、智能音箱、智能插座都连在路由器上,可以互相通信。

  • 广域网(Wide Area Network),是相对局域网来讲的,局域网的传输距离比较近,只能是一个小范围的。如果需要长距离的传输,比如某大型企业,总部在北京,分公司在长沙,局域网是无法架设的。为了数据安全,不能连接因特网,需要用一条自己的专用线路来传输数据,这条线路上只有自己人,不会有其他人接入,且距离很远,这个网络就叫 “广域网”。

1.3 光猫

光猫是一种类似于基带modem(数字调制解调器)的设备,和基带modem不同的是接入的是光纤专线,是光信号。用于广域网中光电信号的转换和接口协议的转换,接入路由器,是广域网接入。

1.4 路由器

Chapter 2 IP地址与子网掩码

2.1 IP地址基本概念

IP地址是Internet中主机的唯一标识,Internet中的主机与别的机器通信必须具有一个IP地址;分为IPv4(32位IP地址)和IPv6(128位IP地址),常用点分十进制表示法表示,目前主流的I地址由4个byte组成,最后都会转化成一个32位unsigned int型整数

2.2 IP地址的划分

IP协议定义了四种主要的地址类:A、B、C和D,其中前三类细划为网络号+主机号

  1. A类地址:第1位固定为0,前8位为网络号(网络标识符),其余三个字节为主机号,用于标识网络中的主机。最多有127个A类网络,每个A类地址可以容纳1700万台主机。1.0.0.1~126.255.255.254

0

网络号(7bit)

主机号(24bit)

  1. B类地址:前2位固定为10,前两个字节(16位)为网络号,其余两个字节为主机号。最多有16000个B类网络,每个B类网络可以容纳65000台主机。128.0.0.1~~191.255.255.254

1

0

网络号(14bit)

主机号(16bit)

  1. C类地址:前3位固定为110,前三个字节(24位)为网络号,最后一个字节为主机号。最多有200万个C类网络,每个C类网络可以容纳254台主机。192.0.0.1~~223.255.255.254

1

1

0

网络号(21bit)

主机号(8bit)

  1. D类地址:前4位固定为1110,D类地址为多目地址,标识在网络上运行分布式应用的一群主机,D类地址并不标识一个在线的主机。224.0.0.1~~239.255.255.254

  1. 特殊地址:

  1. 0.0.0.0:在服务器中,该地址指的是本机上所有的IPv4地址,如果一个主机有两个IP地址,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个IP地址都能够访问该服务。

  1. 127.0.0.1:主机回环地址,主机环回指的是,地址为127.0.0.1的数据包不应离开计算机(主机)发送,而不是发送到本地网络或Internet,它只是在自身上“环回”,发送数据包的计算机成为收件人。

  1. 每一个网段主机号为0的地址是网络地址,设置网关主机号为1的地址,主机号最大的地址是该网段的广播地址。

2.3 子网掩码基本概念

子网掩码是一个32位的整数,作用是将某一个IP划分成网络地址和主机地址。子网掩码不能单独存在,它必须结合IP地址一起使用。用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上。

  • 通过子网掩码,就可以判断两个IP在不在一个局域网内部

  • 子网掩码可以看出有多少位是网络号,有多少位是主机号

  • 网络号全为1,主机号全为0

2.4 IP地址和子网掩码

  • 将IP地址和子网掩码转为二进制,子网掩码连续全1的是网络地址,后面的是主机地址,子网掩码0和1分界点位置对应的IP地址用虚线分开,虚线前为网络地址,虚线后为主机地址。

  • IP地址和子网掩码进行与运算,结果是网络地址

  • 将运算结果中的网络地址不变,主机地址变为1,结果就是广播地址

网络地址+1即为第一个主机地址,广播地址-1即为最后一个主机地址,由此可以看出地址范围是: 网络地址+1 至 广播地址-1

Chapter 3 网络模型与协议

  • 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。

  • 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务

  • 网络体系结构即指网络的层次结构和每层所使用协议的集合

  • 两类非常重要的体系结构:OSI 与 TCP/IP

3.1 OSI模型

OSI 模型是一个理想化的模型,尚未有完整的实现

OSI中的层级

功能

TCP/IP协议族

应用层

文件传输、电子邮件、文件服务、虚拟终端

TFTP、HTTP、FTP、DNS…

表示层

数据格式化、代码转换、数据加密

会话层

解除或建立与别的结点的联系

传输层

提供端对端的接口

TCP、UDP

网络层

为数据包选择路由

IP、ICMP、RIP…

数据链路层

传输有地址的帧以及错误检测功能

SLIP、CSLIP、PPP…

物理层

以二进制数据形式在物理媒体上传输数据

ISO2110、IEEE802.1、IEEE802.2

  • 物理层:传输的是bit流(0与1一样的数据),物理信号,没有格式

  • 链路层:格式变为帧(把数据分成包,一帧一帧的数据进行发送)

  • 网络层:路由器中是有算法的,IP,(主机到主机)(路由的转发)

  • 传输层:端口号,数据传输到具体那个进程程序(端到端)

  • 会话层:通信管理,负责建立或者断开通信连接

  • 表示层:确保一个系统应用层发送的消息可以被另一个系统的应用层读取,编码转换,数据解析,管理数据加密,解密;

  • 应用层:指定特定应用的协议,文件传输,文件管理,电子邮件等。

3.2 TCP/IP模型

TCP/IP中的层级

TCP/IP协议族

应用层

Talent、FTP、HTTP、DNS、SMTP…

传输层

TCP、UDP

网络层

IP、ICMP、IGMP

网络接口和物理层

以太网、令牌环网、FDDI

  • 网络接口和物理层(对应OSI中的数据链路层和物理层):屏蔽硬件差异(驱动),向上层提供统一的操作接口。

  • 网络层:提供端对端的传输,可以理解为通过IP寻址机器。

  • 传输层:决定数据交给机器的哪个任务(进程)去处理,通过端口寻址

  • 应用层(对应OSI中的应用层+表示层+会话层):应用协议和应用程序的集合

3.3 常见的协议

  • 网络接口和物理层:

ppp:拨号协议(老式电话线上网方式)

ARP:地址解析协议 IP-->MAC

RARP:反向地址转换协议 MAC-->IP

  • 网络层:

IP(IPV4/IPV6):网间互连的协议

ICMP:网络控制管理协议,ping命令使用

IGMP:网络分组管理协议,广播和组播使用

  • 传输层:

TCP:传输控制协议

UDP:用户数据报协议

  • 应用层:

SSH:加密协议

telnet:远程登录协议

FTP:文件传输协议

HTTP:超文本传输协议

DNS:地址解析协议

SMTP/POP3:邮件传输协议

3.4 TCP&UDP

  • 相同点:都存在传输层

  • TCP是一种面向有连接的传输层协议,可以提供高可靠性通信,适用于:

  • 对传输质量要求较高以及大量数据的通信

  • 需要可靠传输的场合

  • MSN/QQ等即时通信软件的用户登录账户管理相关的功能通常采用TCP

  • UDP是一种无连接用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。适用于:

  • 发送小尺寸数据(如对DNS服务器进行IP地址查询时)

  • 在接收到数据,给出应答较困难的网络中使用UDP

  • 适合于广播/组播式通信中

  • MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议

  • 流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输

Chapter 4 Socket套接字

4.1 Socket概念

Socket并非互联网协议,它只是基于互联网协议,连接应用层和传输层之间的套件,翻译为“套接字”,是为了实现通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议。

  • 是一个编程接口

  • 是一种特殊文件描述符

  • 并不限于TCP/IP协议

为什么需要Socket?

  • 是一种区别于普通的I/O操作过程(open/options/close)

  • 可以使运行在不同机器不同操作系统的机器上的进程进行通信

Linux BSD(Berkeley Software Distributions)规定的套接字类型:

  1. Stream(数据流套接字):提供两个方向的序列字节流,可以保证数据不丢失、破坏或重复。数据流套接字由Internet地址族的TCP协议所支持。

  1. Datagram(数据报套接字):也提供两个方向上的数据传送,但不保证消息到达,即使到达也不保证顺序无误。数据报套接字由Internet地址族的UDP协议所支持。

  1. Raw(原式套接字)

  1. Reliable Delivered Message(可靠传递消息)

  1. Sequenced Packets(顺序数据报)

  1. Packet(包)

4.2 端口号

端口号(port number)为了区分一台主机接收到的数据包应该转交给哪,传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地将数据传输,TCP端口号与UDP端口号独立,并用2Byte来表示。

在实际通信中,要事先确定端口号,有两种方法:

  • 标准既定的端口号(静态方法),它是指每个应用程序都有其指定的端口号

  • 时序分配法(动态分配法),此时,服务端有必要确定监听端口号,但是接受服务的客户端没必要确定端口号

4.2 字节序

小端序(little-endian) - 低序字节存储在低地址 (主机字节序)

大端序(big-endian)- 高序字节存储在低地址 (网络字节序)

网络中传输的数据必须使用网络字节序,即大端字节序

eg.写一个函数来判断当前主机的字节序?(共用体、指针强转)

#include <stdio.h>

union un {
    int a;
    short b;
    char c;
};

int main(int argc, char const *argv[])
{
    union un st;
    st.a=0x12345678;
    printf("%#x %#x\n",st.b,st.c);
#if 0
    int a=0x12345678;
    //printf("%#x %#x\n",(char)a,(short)a);
 
    char *p=(char *)&a;
    printf("%#x %#x\n",*p,*(p+3));
#endif
    return 0;
}

主机字节序到网络字节序

u_long htonl (u_long hostlong);
u_short htons (u_short short);  //掌握这个  

网络字节序到主机字节序

u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);

IP地址的转换

typedef uint32_t in_addr_t;
struct in_addr {
    in_addr_t s_addr;
};
in_addr_t inet_addr(const char *cp);  //从人看的ip地址转为机器使用的32位无符号整数
char *inet_ntoa(struct in_addr in);  //从机器到人

Chapter 5 TCP编程

5.1 TCP网络模型

  • C/S模型:client(客户端)和server(服务器)模型

  • B/S模型:browser(浏览器)和server(服务器)模型

5.2 C/S模型通信流程

5.3 TCP相关函数

5.3.1 socket创建

所需头文件:

#include <sys/types.h>
#include <sys/socket.h>

首先创建socket套接字,我们使用socket系统调用创建一个socket文件,函数原型为:

int socket(int domin,int type,int protocol);
  • domain参数告诉系统使用哪个底层协议族,我们在上面列出了协议族和对应的地址族。

  • 如果为 TCP/IP协议,参数设置为AF_INET(用于IPv4)或AF_INET6(用于IPv6);

  • 对于UNIX协议族,设置为AF_UNIX;

  • 对于本地通信,设置为AF_LOCAL。

  • type参数指定服务器类型。服务器类型主要有:

  • SOCK_STREAM表示数据流服务,用于TCP。

  • SOCK_DGRAM表示数据报服务,用于UDP。

  • SOCK_RAW表示原式套接字服务。

  • protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议,一般都设置为0,标识默认协议

调用成功返回一个socket文件描述符,失败返回-1,并设置errno


创建socket时,指定了它的地址族,但是并未指定使用该地址族中的哪个具体socket地址,所以需要下一步命名绑定

5.3.2 bind命名绑定

所需头文件:

#include <sys/types.h>
#include <sys/socket.h>

将一个socket与socket地址绑定称为给socket命名。在服务器程序中,我们通常要命名绑定socket,因为只有命名后客户端才能知道该如何连接它,而客户端通常不需要命名socket,会采取匿名方式,即使用操作系统自动分配的socket地址。

命名socket的系统调用是bind,函数原型为:

int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
  • sockfd:为socket文件描述符,socket系统调用函数的返回值。

  • my_addr:表示将所指的socket地址分配给未命名的sockfd文件描述符,我们上面说过通用地址使用不方便,所以在这我们使用TCP/IP专用socket地址即sockaddr_in。使用前先定义结构体,再将参数传入,参数包含地址族,端口号,IPv4地址,需要用到上面说的函数进行一定的转换,具体如下

  • struct sockaddr_in serveraddr; //定义结构体 memset(&ser_addr,0,sizeof(ser_addr));//清空结构体,全为0 serveraddr.sin_family=AF_INET;//设置地址族,因为当前所用为TCP/IPv4,对应的地址族为AF_INET serveraddr.sin_port=htons(6000);//端口号,需要将short类型的主机字节序转化为网络字节序 serveraddr.sin_addr.s_addr=inet_addr("127.0.0.1");//将点分十进制IPv4转换为网络字节序整数标的IPv4地址,此地址为回环测试地址,也可以输入本机IP // 自动获取ip serveraddr.sin_addr.s_addr=htonl(INADDR_ANY); serveraddr.sin_addr.s_addr=INADDR_ANY; //0.0.0.0

  • 在bind中使用时,需要强制转换为sockaddr类型的,即

(struct sockaddr *)&serveraddr
  • addrlen参数指出该socket地址的长度,用sizeof即可得知。

成功返回0,失败返回-1,并设置errno。

常见bind失败返回-1并设置errno的值和原因为:

  • EACCES:被绑定的地址是受保护的地址,仅超级用户才可以,如将socket绑定到知名服务端口(0~1023)上时,就会返回这个。

  • EADDRINUSE:被绑定的地址正在使用中。


一般使用方式就是:

if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
	perror("bind error.");
    return -1;
}
printf("bind ok.\n");

5.3.3 listen启动监听

所需的头文件:

# include<sys/socket.h>

socket命名后,还不能马上接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接

int listen(int sockfd,int backlog);

注意:listen不会阻塞,因为它只是启动监听

  • sockfd: 指定被监听的socket,socket系统调用函数的返回值。

  • backlog参数: 表示内核监听队列的最大长度,监听队列的长度如果超过backLog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。backlog参数的典型值是5

内核版本2.2之前的Linux中,backlog参数是指所有处于半连接状态(未完成三次握手的SYN_RCVD),和完全连接状态(完成三次握手的ESTABLISHED)的socket的上限,但自内核版本2.2之后,它只表示处于完全连接状态的socket的上限,处于半连接状态的socket的上限由/pro/sys/net/ipv4/tcp_max_syn_backlog内核参数定义

成功返回0,失败返回-1,并设置errno

一般使用方法为:

if(listen(sockfd, 5) < 0){
	perror("listen error.");
    return -1;
}

5.3.4 connect建立连接

所需头文件:

# include<sys/types.h>
# include<sys/socket.h>

服务器通过listen调用被动接受连接,那么客户端需要通过如下系统调用来主动与服务器建立连接:

int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen);
  • sockfd:由socket系统调用返回的sockfd。

  • serv_addr: 是服务器监听的socket地址。注意定义socketaddr_in结构体存储的是服务器的socket信息,所以应该和服务器的初始化一样,而不是客户端的。

  • addrlen: 指定这个地址的长度。

成功返回0,一旦成功建立连接,sockfd就唯一标识了这个连接,客户端就可以通过读写sockfd来与服务器通信

失败返回-1,设置errno

一般使用方法为:

struct sockaddr_in ser;//定义socket地址,存储服务器socket地址
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;//地址族
ser.sin_port=htons(6000);//端口号
ser.sin_addr.s_addr=inet_addr("127.0.0.1");//IP地址
int res=connect(sockfd,(strcut sockaddr*)&ser,sizeof(ser));

connect的执行在listen之后,accept之前

5.3.5 accept接受连接

所需头文件:

#include <sys/types.h>
#include <sys/socket.h>

从listen监听队列中接受一个连接

int accept(int sockfd,struct sockaddr * addr,socklen_t* addrlen);
  • sockfd:执行过listen系统调用的监听socket。即socket系统调用返回的sockfd经过listen系统调用。

  • addr:用来获取被接受连接的远端socket地址(即客户端),所以我们需要再定义一个socketaddr_in结构体来存储客户端的socket地址,在使用参数时,记得强转。

  • addrlen:为socket地址的长度,用sizeof即可

成功时返回一个新的连接socket,该socket唯一的标识了被接受的这个连接,服务器可以通过读写该socket来与被接受连接对应客户端通信;失败返回-1,设置errno

一般使用方法为:

struct sockaddr_in cli_addr;//定义保存客户端socket地址的结构体
socklen_t len = sizeof(cli_addr);//得到长度
int clifd = accept(sockfd, (struct sockaddr*)&cli_addr, &len);//传入,执行成功后,cli_addr保存客户端的socket地址信息

int acceptfd = accept(sockfd, NULL, NULL); // 本地
if (acceptfd < 0){
	perror("accept error.");
    return -1;
}
printf("acceptfd=%d\n", acceptfd);

accept只是从监听队列中取出连接,而不论连接处于何种状态(如ESYABLOSHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化,就算客户端断网了,它还是会正常返回

5.3.6 recv读取、send发送数据

所需头文件:

# include<sys/types.h>
# include<sys/socket.h>

对文件的读写操作read和write同样适用于socket,到那时socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制,其中用于TCP流数据读写的系统调用是

ssize_t recv(int sockfd,void* buf,size_t len,int flags);//读取数据
ssize_t send(int sockfd,const void*buf,size_t len,int flags);//发送数据
  • sockfd:为accept函数返回的sockfd,表示被接受的客户端,可以通过socfkd对他进行读写,注意不是socket函数返回的sockfd。

  • buf:缓冲区位置

  • len:缓冲区大小

  • flags:为数据收发提供了额外的控制,一般设置为0

recv函数成功返回 实际读取到的数据的长度,它可能小于我们期望的长度len,因此我们可能要多次调用recv,才能读取到完成的数据。recv可能返回0,这意味着通信对方已经关闭连接,没有读到数据。send函数往sockfd表示的客户端写入数据。

5.3.7 close关闭连接

所需头文件:

# include<unistd.h>

关闭连接实际上就是关闭连接对应的socket,和关闭普通文件描述符方法一样,系统调用如下:

int close(int fd);
  • fd: 是待关闭的socket。

但是close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用技术为0时,才能真正的关闭。这个概念我们在父子进程共享文件时也说过,即一次fork系统调用默认将使父进程中打开的socket的引用计数加一,所以必须在父、子进程中都对该socket执行close调用才能将连接关闭。

close这种关闭方法是专门为了网络编程设计的,如果要立即终止连接,而不是将socket的引用计数减一,可以使用shutdown调用,它可以关闭读、写或全部关闭。

5.4 编程流程

  1. server端:

int socket();//创建一个用于监听客户端连接的网络套接字《----》买手机
int bind();  //将创建的套接字与本端的地址信息进行绑定IP+端口《----》给手机插卡,绑定电话号码,不然别人无法拨号联系我
int listen();//启动监听,不会阻塞《----》开机,不用一直等着别人给你打电话
int accept()://接受一个客户端的连接,返回的是一个客户端连接套接字《----》如果有人打电话,我就接听电话
int recv()/send();//读取数据或者发送数据《----》交谈
int close(); //关闭文件描述符《----》挂电话
  1. client端:

int socket();//创建一个用于整个通讯的套接字《----》买手机
int connect();//与服务器程序建立连接《----》拨号
int recv()/send();//读取或发送数据《----》交谈
int close();//关闭连接《----》挂电话

以下代码最好背过:

// server.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    // 1.创建socket套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error!");
    }
    // printf("sockfd=%d\n", sockfd);

    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    //serveraddr.sin_port = htons(8888);
    serveraddr.sin_port = htons(atoi(argv[1]));// 改进版,在终端输入端口号
    serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 自动获取IP 
	
    // 2.绑定套接子ipv4
    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind error.");
        return -1;
    }
    printf("bind ok.\n");

    // 3.监听套接子,将主动套接子转为被动套接子
    if (listen(sockfd, 5) < 0)
    {
        perror("listen error.");
        return -1;
    }
    
    // 4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
    int acceptfd = accept(sockfd, NULL, NULL);
    if (acceptfd < 0)
    {
        perror("accept error.");
        return -1;
    }
    printf("acceptfd=%d\n", acceptfd);

    // 5.循环接收消息 通信
    char buf[128]; // 定义一个缓冲区用于存放数据

    while (1)
    {
        int recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
        if (recvbyte < 0){
            perror("recv error.");
            return -1;
        }
        else if (recvbyte == 0){
            printf("client exit.\n");
            break;
        }
        else
        {   
            buf[recvbyte] = '\0';
            printf("buf:%s\n", buf);
        }
    }
    
    close(acceptfd);
    close(sockfd);

    return 0;
}
// client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //1.创建套接字
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error.");
        return -1;
    }
    printf("sockfd=%d\n", sockfd);
    //填充ipv4的通信结构体  服务器的ip和端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2])); // 改进版,在终端输入端口号
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]); // 改进版,在终端输入IP地址

    if(connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0)
    {
        perror("connect error");
        return -1;
    }
    printf("connect ok\n");
	// 循环发送消息 通信
    char buf[128];
    
    while (1)
    {
        printf("send:");
        scanf("%s",buf);
        int sendbyte = send(sockfd, buf, sizeof(buf), 0);
        if(sendbyte < 0)
        {
            perror("send error");
            return -1;
        }       
    }
    return 0;
}

Chapter 6 UDP编程

与TCP相比较,UDP是面向无连接的通信方式不需要connect、listen、accept等函数操作,不用维护TCP的连接、断开等状态

6.1 UDP流程

server:

创建数据报套接字(socket(,SOCK_DGRAM,))----->有手机

绑定网络信息(bind())---------------------->绑定号码(发短信知道发给谁)

接收信息(recvfrom())--------------------->接收短信

关闭套接字(close())----------------------->接收完毕

client:

创建数据报套接字(socket())----------------------->有手机

指定服务器的网络信息------------------------------>有对方号码

发送信息(sendto())---------------------------->发送短信

关闭套接字(close())--------------------------->发送完

6.2 UDP相关函数

一般涉及到的函数为:

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
int close(int fd);

6.2.1 sendto发送数据

所需头文件:

#include <sys/types.h>
#include <sys/socket.h>

客户端和服务端之间相互发送数据,函数原型如下:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd:套接字描述符

  • buf:发送缓存区的首地址

  • len:发送缓存区的大小

  • flags:0

  • src_addr:接收端的网络信息结构体的指针

  • addrlen:接收端的网络信息结构体的大小

成功返回发送的字节个数;失败返回-1。

一般使用方法为:

sendto(sockfd,buff,sizeof(buff),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));

6.2.2 recvfrom接收数据

所需头文件:

#include <sys/types.h>
#include <sys/socket.h>

客户端和服务端之间相互接收数据,函数原型如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
					struct sockaddr *src_addr, socklen_t
  • sockfd:套接字描述符

  • buf:接收缓存区的首地址

  • len:接收缓存区的大小

  • flags:0

  • src_addr:发送端的网络信息结构体的指针

  • addrlen:发送端的网络信息结构体的大小的指针

成功返回接收的字节个数;失败返回-1;返回值为0则客户端退出。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Listening to you

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值