socket 基础知识
概述
socket 是 TCP/IP 协议的最流行的一种网络编程接口。它与 TCP/IP 一起最早实现于 4.1BSD UNIX 系统中,主要用于传送级( TCP,UDP )编程。
socket 往往称为套接口,套接口用于网络中两个通信实体间的通信,两个实体可以存在于同一机器的不同进程中或不同机器的进程中。
套接口就好像 UNIX 中 pipe (管道),通信双方进程通过它来与对方发送或接受数据。如同 pipe 用文件描述字表示一样, socket 也用文件描述字表示,也称为套接口描述字,或简称套接字。在网络编程时要用套接字表示通信的对方。但两者不同的是, pipe 的通信双方在一台机器上,共用一个 pipe ,双方使用不同的文件描述字;而 socket 通信双方一般在不同机器上,因而通信双方均有一 socket 和对应的套接字负责通信,当然他们之间必须连接起来。
网络中每个通信实体的 socket 是用一个三元组标识的。三元组指的是:协议族(地址族),网络地址、和传输层端口 (本文目前只介绍 Ipv4 )。通信双方的一个连接是用网络五元组来标识的,它是由双方相同协议族的两个本地三元组合成的。网络五元组指的是:协议族(地址族)、本地网络地址、本地端口、远程网络地址和远程端口。 上述五元组往往称为全相关。而三元组往往称为半相关
套接口分为若干类型,常用的是 SOCK_STREAM 和 SOCK_DGRAM 。 SOCK_STREAM 是面向连接的套接字,使用的协议是 TCP ,通信的双方通过三次握手建立起虚拟的连接线路,通信的过程是可靠的。而 SOCK_DGRAM 是面向非连接的套接字,使用的协议是 UDP ,通信双方以数据包的方式进行通信,通信的可靠性就不能得到保证。本项目要求采用 SOCK_STREAM 类型。
客户机 / 服务器计算模式
在 TCP/IP 网络应用中 , 通信双方相互作用采用客户 / 服务器模式 , 即客户端向服务器发出请求 , 服务器接收到请求后提供相应的服务。一种常见的面向连接的运行方式如下:
服务器方(首先启动):
1. 打开一通信通道并告知本地主机,它愿意在某一公认地址端口上 ( 周知口,如 http 为 80) 接受客户请求。
2. 等待客户请求到达该端口。
3. 接收并处理该请求,发送处理结果。如果是并发服务器,则要建立( fork )一新的子进程来处理这个客户请求。服务完成后,关闭这个新进程与客户的通信连接,并终止该进程。
4. 返回第二步,等待下一个客户请求。
客户方 :
1. 打开一通信通道,并连接到服务器所在主机的特定端口。
2 .如服务器接受了这个连接,则向它发出服务请求报文,等待并接收应答;
3. 请求结束后关闭通信通道。
从上面的运行过程可知:
1 .客户与服务器进程的作用是非对称的。
2. 服务进程一般是先于客户请求启动的。只要系统运行,该进程一直存在,直到正常终止或者强迫终止。
五元组格式(协议,本地 IP ,本地端口,远方 IP ,远方端口)的建立过程
服务器一般都有两个功能:监听 和 处理
在监听的时候,协议 / 本地 IP/ 本地端口(监听端口)都是确定的,当收到客户端的报文时,远方 IP 就是报文的源 IP 地址,远方端口就是报文的源端口,这样一来五元组就确定了。
然后服务器进入处理阶段,需要开启一个新的线程与客户端交互,当然就需要确定一个新的五元组,这时候协议 / 本地 IP/ 远方 IP/ 远方端口都来自监听阶段确定的五元组,而本地端口会在 1024 以上随机选取 (不再使用监听端口,以便监听其他客户端的请求)。
客户端的话正好相反,在发送请求时采用随机的本地端口 ,而接受响应时采用服务器的源端口作为远方端口。
系统调用
1. 创建套接字
#include <sys/types.h>;
#include <sys/socket.h>;
int socket(int family, int type, int protocol);
这是进行套接字通信的第一步,就是创建套接口。参数 family 表示所使用的协议族。参数 type 可以是 SOCK_STREAM 或 SOCK_DGRAM 。 SOCK_STREAM 是指面向连接的通信,而 SOCK_DGRAM 是面向非连接的数据报方式。参数 protocol 描述的是协议种类,一般设为 0 。
Socket 函数用于建立三元组中的协议族部分 。
此系统调用如果成功就返回的是套接字,以后的所有的对此套接口的操作都需引用这个描述字。如果调用失败,就返回 -1 ,并在全局变量 errno 保存错误的类型。
2. 绑定套接字
#include <sys/types.h>;
#include <sys/socket.h>;
int bind(int sockfd, cost struct sockaddr * saddr, socklen_t addrlen);
bind( ) 系统调用的作用是将由 socket( ) 所创建的套接字和地址结构 sockaddr 的一个实例相关联,其中含有本地的信息如 IP 地址、端口号。
bind 函数用于建立三元组中的本地 IP 地址和本地端口号部分。
参数 sockfd 是在此前用 socket 系统调用创建的套接字, sa 是指向结构 sockaddr 一个实例的指针。这个实例的类型一般是 sockaddr_in ,在调用时需要进行强制转换( struct sockaddr * ) &saddr (其中 saddr 是 sockaddr_in 类型)。参数 addrlen 是结构 sockaddr 的大小即 sizeof ( struct sockaddr )。其中套接字的地址和端口可以在程序中指定,也可以由系统分配。
3. 监听
#include <sys/socket.h>;
int listen( int sockfd, int backlog ) ;
用 TCP 进行网络通信,服务器端要经历几个状态,当调用 socket( ) 和 bind( ) 后,虽然套接字已经建立起来,但是其状态是 CLOSED 即关闭状态。要进行通信就必须将套接字打开。对于服务器端来说要被动打开,这就是 listen( ) 的功能。
下面就可以用 accept( ) 和 connect() 建立连接。这个过程其实就是 TCP 的“三次握手”。
4. 接受连接
#include <sys/socket.h>;
int accept( int sockfd, struct sockaddr * client_addr, socklen_t *addrlen);
服务器调用 accept( ) 来接受连接。当客户与服务器连接( connect )并且连接成功(三次握手)后 accept 则返回另一个套接字。这个新创建的套接字称为“连接套接字 ”。服务器利用这个套接字来传输数据。而前面用 socket( ) 调用产生的套接字则称之为“监听套接字”,这个套接字专门监听来自客户端的请求。
参数 sockfd 是由 socket( ) 创建的连接套接字。 client_addr 是指向结构 sockaddr 的一个实例的指针,在返回时用来存放请求连接的客户端的 IP 地址、端口信息。参数 addrlen 存放结构 * client_addr 的大小。
access 完成后通信双方两个三元组组成的五元组就建立起来了。
5. 连接服务器
#include <sys/types.h>;
#include <sys/socket.h>;
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
客户端使用这个系统调用来连接目的主机(服务器)的指定端口,以便和目的主机 ( 在 access 中等待 ) 进行通信。
sockfd 是客户机的系统调用 socket() 返回的套接字。 serv_addr 指向目的地 ( 服务器)端口和 IP 地址的数据结构 struct sockaddr 。 addrlen 设置为 sizeof(struct sockaddr) 。
connect 完成后通信双方两个三元组组成的五元组就建立起来了。