socket 套接字(server端)
一、OSI七层模型
OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层),即ISO开放互连系统参考模型。如下图所示。
每一层实现各自的功能和协议,并完成与相邻层的接口通信。OSI的服务定义详细说明了各层所提供的服务。某一层的服务就是该层及其下各层的一种能力,它通过接口提供给
更高一层。各层所提供的服务与这些服务是怎么实现的无关。
每层常见的物理设备如下:
虽然OSI模型是一种接近完美的理论(注意这种模型只出现在教课书里)这种模型是在TCP/IP协议已经成熟之后提出的,可以理解为升级版。但是并没有流行出来。所以,网
络数据传输是TCP/IP的天下。
二、TCP/IP五层协议
每一层都呼叫它的下一层提供的网络来完成自己的需求。(如果是四层模型数据链路层和物理层在一层)
1、物理层:负责光电信号传递方式。集线器工作在物理层。以太网协议。
2、数据链路层:负责设备之间的数据帧的传输和识别。交换机工作在数据链路层。例如网卡设备的驱动,帧同步,冲突检测,数据差错校验等工作。
3、网络层:负责地址管理和路由选择。路由器工作在网络层。
4、传输层:负责两台主机之间的数据传输。
5、应用层:负责应用程序之间的沟通。网络编程主要针对的就是应用层。
关系如下:
ARP协议可实现通过IP地址获得对应主机的物理地址(MAC地址)。
RARP协议是将MAC物理地址转换成IP地址
ICMP协议确认IP包是否成功到达目标地址以及通知在发送过程中IP包被丢弃的原因。
IGMP协议用来在IP 主机和与其直接相邻的组播路由器之间建立、维护组播组成员关系。
三、什么是socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,
对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循TCP/UDP标准的。
另一方面,我们可以抽象地将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port
是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序。
四、socket工作流程
生活中的场景,你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。
五、socket服务器端(server)
//ssocket.h #ifndef SSOCKET_H #define SSOCKET_H #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h>
#define port 7788
#define backlog 5
namespace ssocket { class clientSocket; class newSocket { private: int fd; sockaddr_in mAddr; public: newSocket(); void bind(); void listen(); void accept(clientSocket &client); ~newSocket(); }; class clientSocket { public: int fd; //套接字 socklen_t mLen; //地址长度 sockaddr_in mAddr; //地址 ~clientSocket(); }; } // namespace ssocket #endif
AF_INET是一个地址系列,用于指定套接字可以与之通信的地址类型(在本例中为Ipv4地址)。创建套接字时,必须指定其地址族,然后只能使用该类型的地址与套
接字。
套接字的特征在于它们的域,类型和传输协议。常见域名是:
AF_UNIX:地址格式为UNIX路径名
AF_INET:地址格式为主机和端口号
ssocket::newSocket::newSocket() { bzero(&mAddr, sizeof(mAddr)); mAddr.sin_family = AF_INET; mAddr.sin_port = htons(port); //INADDR_ANY含义是让服务器端计算机上的所有网卡的IP地址都可以作为服务器IP地址 //也即监听外部客户端程序发送到服务器端所有网卡的网络请求。 mAddr.sin_addr.s_addr = INADDR_ANY; this->fd = socket(AF_INET, SOCK_STREAM, 0); int val = 1; //closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); if (ret < 0) { perror("setsocketopt"); exit(-1); } if (this->fd < 0) { perror("socket"); exit(-1); } std::cout << "成功创建socket:" << this->fd << std::endl; }
sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:
struct sockaddr {
sa_family_t sin_family; //地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:
void ssocket::newSocket::bind() { std::cout << "bind" << std::endl; int ret = ::bind(this->fd, (struct sockaddr *)&mAddr, sizeof(mAddr)); if (ret < 0) { perror("bind"); exit(-1); } }
其中,backlog参数决定了未完成队列和已完成队列中连接数目之和的最大值
void ssocket::newSocket::listen() { printf("listen\n"); int ret = ::listen(this->fd, backlog); if (ret < 0) { perror("listen"); exit(-1); } }
其中,client.mAddr和client.mLen是输出参数。
void ssocket::newSocket::accept(clientSocket &client) { client.fd = ::accept(this->fd, (struct sockaddr *)&client.mAddr, &client.mLen); if (client.fd < 0) { perror("accept"); exit(-1); } }
close为释放套接字。
ssocket::newSocket::~newSocket() { close(this->fd); } ssocket::clientSocket::~clientSocket() { close(this->fd); }