网络基础——套接字

套接字

应用层通过传输层进行通信时,TCP和UDP会同时遇到要为多个应用进程提供并发服务的问题,多个TCP链接或者应用程序可能需要通过一个TCP协议端口传输数据,为了将不同的应用程序区分开来,操作系统为应用程序与TCP/UDP交互提供了接口,成为套接字。

套接字(Socket):(IP地址:端口号)
端口:计算机通信的终点是应用进程,只要把所传送的报文交到目的主机合适的端口,不需要具体实现这个功能的进程,剩下的工作由TCP/UDP完成。

端口号:

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析;
  • IP地址 + 端口号能标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用。

源端口号 & 目的端口号

传输层协议(TCP/IP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁的?发给谁?"

Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。

Host A上的程序A将一段信息写入Socket中,Socket的内容被Host A的网络管理软件访问,并将这段信息通过Host A的网络接口卡发送到Host B,Host B的网络接口卡接收到这段信息后,传送给Host B的网络管理软件,网络管理软件将这段信息保存在Host B的Socket中,然后程序B才能在Socket中阅读这段信息。

要通过互联网进行通信,至少需要一对套接字,一个运行于客户机端,称之为ClientSocket,另一个运行于服务器端,称之为serverSocket。

在这里插入图片描述
套接字链接步骤

  1. 服务器监听(Lisen)
  2. 客户端请求(accept)
  3. 连接确认

bind()函数

//绑定端口号  (TCP/IP,服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(address_len):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。
Lisen函数

#include <sys/types.h>          
#include <sys/socket.h>
int listen(int sockfd, int backlog);

在网络通信中,客户端往往处于主动的一方,服务器处于被动的一方,服务器是被链接的,所以它时时刻刻准备着被连接,所以调用Lisen()函数来监听

lisen()函数的作用就是将socket()函数得到的sockfd变成一个被动的套接字,用来被动等待客户端,而参数backlog的作用就是设置连接队列的长度,
三次握手不是lisen()函数完成的,而是内核完成的,lisen()监听到就把sockfd和backlog告诉内核就直接返回了,一切工作都是由内核来完成的。
这里还需要再分析一下 listen() 函数的第二个参数 backlog,

connect()函数

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

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

客户端通过connect()函数主动向服务器发起连接,
但建立这个连接也不是connect函数完成的,它也只是告诉内核,通知内核进行三次握手后,将结果返回给这个函数,

这个函数会一直阻塞,直到内核建立连接成功或者超时失败后才返回;

所以客户端通过connect()函数通知内核建立连接,服务器通过lisen()函数通知内核建立连接,

因此lisen()之后连接就已建立好了,建立好的连接储存在已连接队列中

之后, 如果有客户端通过 connect() 发起连接请求, 内核就会通过三次握手建立连接, 然后将建立好的连接放到一个队列中, 这个队列称为: 已完成连接队列
这里还需要再分析一下 listen() 函数的第二个参数 backlog,实际上,内核为每一个套接字维护两个队列:
未连接队列:其中存储着尚未建立连接的套接字
**已连接队列:**存储着已建立连接的套接字

一般backlog的大小是这两个队列之和,收到客户端的请求之后,内核创建一个套接字存在未连接队列之中,然后进行三次握手建立连接,
当这个连接建立成功之后,内核就把这个套接字放置在已连接队列的队尾,服务器从已完成队列中取走一个就空出一个位置,然后已经完成连接的套接字由补充进来, 就这样实现动态平衡。
所以说, 如果在 listen 之后不进行 accept , connect 也是会成功返回的, 其实此时连接就已经建立好了。

accept()函数

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

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

所以accept()函数作用就是在已完成队列中取一个连接好的套接字
如果服务器来不及调用 accept() 取走队列中已经建立好的连接, 导致队列中的连接满了, 会怎么样呢 ?

这取决于内核的具体实现, 在Linux中会延时建立连接.

函数的第一个参数用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字),第二个参数是用来保存客户端套接字对应的“地方”(包括客户端IP和端口信息等), 第三个参数是“地方”的占地大小。返回值对应客户端套接字标识。

    实际上是这样的: accept函数指定服务端去接受客户端的连接,接收后,返回了客户端套接字的标识,且获得了客户端套接字的“地方”(包括客户端IP和端口信息等)。

    accept函数非常地痴情,痴心不改:如果没有客户端套接字去请求,它便会在那里一直痴痴地等下去,直到永远(注意, 此处讨论的是阻塞式的socket.  如果是非阻塞式的socket, 那么accept函数就没那么痴情了, 而是会立即返回, 并意犹未尽地对未来的客户端扔下一句话: 我等了你, 你不来, 那就算了, 我懒得鸟你)。

Socket()函数

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

domain

函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义
type

函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等

protocol
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值