3. socket编程简介
关于socket的扩展阅读:https://mp.weixin.qq.com/s/Syee1T7JcnqACs5TJf-cLA
3.1. socket概述
socket(套接字)是一种独立于协议的网络编程接口。socket接口定义了许多函数或例程,我们可以使用它们来开发TCP/IP网络上的应用程序。
socket的三种类型:
流式socket(SOCK_STREAM):使用TCP协议提供可靠的、面向连接的通信流,保证了数据传输的正确性和顺序。
数据报socket(SOCK_DGRAM):使用UDP协议提供无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。
原始socket(SOCK_RAW):允许在MAC层上收发原始数据帧,功能强大但使用较为不便,主要用于一些协议开发。
在邮件客户端实验中使用的SMTP和POP3协议,都是运行在TCP协议的基础上,因此,我们下面主要介绍流式socket(SOCK_STREAM)的用法,如果想了解socket的另外两种类型的用法,可以自行百度。
3.2. 流式socket(SOCK_STREAM)
网络应用的标准模型是客户端/服务器模型(Client/Server,简称C/S)。客户端一般是发起通信请求的进程,服务器则一般是等待并处理客户请求的进程。服务器通常由系统执行,在系统生成期间一直存在。流式socket中服务器和客户端的交互过程可参考下图。
服务器的处理过程如下:
调用scoket初始化套接字;
调用bind将套接字与服务器的IP绑定在一起,客户端可以使用这个IP地址进行连接;
调用listen开始侦听客户端建立连接的请求;
调用accept处理收到的客户连接请求;
调用recv和send接收和发送数据,与客户端通信;
最后调用close关闭并释放socket。注意,当服务器关闭socket后,客户端将无法连接。
客户端的处理过程如下:
调用socket初始化套接字;
调用connect连接指定的服务器(IP地址)和端口;
连接建立后,调用recv和send与服务器通信;
最后调用close关闭并释放socket。
在邮件客户端实验中,只需完成客户端的处理,故以下只介绍客户端使用到的socket套接字编程接口。
3.2.1. socket函数
socket函数用于创建并初始化一个socket套接字接口。
1//使用socket编程接口所需的头文件
2#include <sys/socket.h>
3#include <netinet/in.h>
4
5int socket(int domain, int type, int protocol);
6// 返回:非负描述字 —— 成功,-1 —— 出错
domain参数指明协议族,在使用IPv4协议时,该参数为AF_INET;
type参数指明 socket的类型 ,在邮件客户端实验中该参数为SOCK_STREAM;
protocol参数指名协议类型,建议设为0,以选择所给定family和type组合的系统缺省值。
3.2.2. connect函数
connect函数用于向指定的服务器和端口发送连接请求。
1int connect(int sockfd, struct sockaddr * servaddr, int addrlen);
2// 返回:0 —— 成功,-1 —— 出错
1) sockfd参数是socket函数返回的套接字描述符;
2) servaddr参数是服务器的地址和端口,它由struct sockaddr_in结构体来表示(在connect函数传参时需要强制转换成struct sockaddr * ),其定义如下:
1struct sockaddr_in{
2 short in sin_family; //地址族
3 unsigned short int sin_port; //端口号
4 struct in_addr sin_addr; //IP地址
5 unsigned char sin_zero[8]; //填充
6}
sin_family表示协议的地址族,IP协议必须为AF_INET;
sin_port表示TCP/UDP的端口号,可别忘记大小端转换了:)
sin_zero用于使各种协议族保持结构大小一致的填充字段,IP协议中,此字段填充为8位的零(建议使用bzero函数);
sin_addr表示IP地址,它也是一个结构体。
1struct in_addr{
2 unsigned long s_addr;
3}
与常见的IP地址(如“192.168.1.0”)不同,struct in_addr结构体定义的IP地址是一个无符号长整形的数,所以在填充地址时,需要将字符串表示的IP地址与32位二进制形式的IP地址进行转换。在邮件客户端代码框架中,我们调用gethostbyname函数根据DNS域名获取到了char类型的dest_ip,接着可以使用inet_addr函数把它转换成32位二进制网络字节序的IPv4地址。
1 #include <arpa/inet.h>
2
3 unsigned long inet_addr(const char* strptr);
3) addrlen参数是套接字地址结构(sockaddr)的长度。
3.2.3. send函数
send函数实现网络数据的发送。
1int send(int sockfd, void * buf, int len, int flags);
2// 返回:表示实际发送了多少数据,-1 —— 出错
buf参数是发送数据的缓冲区指针;
len参数是缓冲区的大小;
flags参数一般取0。
3.2.4. recv函数
recv函数实现网络数据的接收。
1int recv(int sockfd, void * buf, int len, int flags);
2// 返回:表示实际接收了多少数据,-1 —— 出错
buf参数是接收数据的缓冲区指针;
len参数是缓冲区的大小;
flags参数一般取0,在接收缓冲区有数据时立即返回。
备注
recv函数和send函数是阻塞的。当调用recv函数时,程序会一直阻塞知道接收到数据(或者发生了中断或者错误)才会返回。