网络基础——套接字

套接字

应用层通过传输层进行通信时,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中会延时建立连接.

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;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

发布了34 篇原创文章 · 获赞 4 · 访问量 1739

猜你喜欢

转载自blog.csdn.net/qq_41181857/article/details/104783536
今日推荐