socket随笔

前言

socket(套接字),socket的英文翻译为插座。在通信过程中,套接字必须是成对(指客户端和服务器端都要创建套接字)出现的,就想插头与插座的关系一样。

0

在Linux环境下,socket用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

套接字通信原理如下图所示:

0

一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)。在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区

socket模型创建流程图

0

相关函数

备注:本篇博客介绍函数相关参数只介绍我们平时最常用的参数

socket函数

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
作用:创建一个套接字
参数:
domain:
    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    AF_INET6 与上面类似,不过是来用IPv6的地址
  
type:
    SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
    SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
protocol:
    传0 表示使用默认协议。
返回值:
    成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno

bind函数

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:给socket绑定一个地址结构(IP+PORT),用于绑定服务器的地址结构
参数:
sockfd:
    socket文件描述符,即socket函数的返回值
addr:
    构造出IP地址加端口号
addrlen:
    sizeof(addr)
返回值:
    成功返回0,失败返回-1, 设置errno

注意bind函数的第2参数addr就是服务器的相关地址结构,因此需要我们手动给出。一般服务器端addr(这里写作serv)的构造方式一般如下:

struct sockaddr_in serv;
memset(&serv,0,sizeof(serv));
serv.sin_family=AF_INET;
serv.sin_port=htons(9876);
serv.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY表示取出系统中有效的任意IP地址

客户端addr(这里写作serv)的构造方式一般如下:

struct sockaddr_in serv;
memset(&serv,0,sizeof(serv));
serv.sin_family=AF_INET;
serv.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&serv.sin_addr.s_addr);//转换字符串到网络地址

另外注意到addr定义时,其类型为struct sockaddr_in;而bind函数的第2参数是struct sockaddr类型,所以我们在传参的时候要强制转换一下。至于为什么要设计这两个结构,其实原因就在于这些网络编程函数诞生早于IPv4协议,早期这些函数使用的都是sockaddr结构体,而sockaddr_in是基于IPv4协议的,所以为了兼容,我们需要进行强制转换。我们可以发现sockaddr_in和sockaddr其实大小一样,都是16字节。其实,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6(IPv6,这个结构体大小28字节),由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

0

listen函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
作用:设置同时与服务器连接的上限数。(同时进行3次握手的客户端数量)
参数:
sockfd:
    socket文件描述符,即socket函数的返回值
backlog:
    设置同时与服务器连接的上限数。最大值为128
返回值:
    成功返回0,失败返回-1,设置errno  

注意:listen函数并不负责监听客户端的连接请求,其作用只是设置同时与服务器连接的客户端的数量,因此其是不阻塞的,真正的阻塞发生在accept阶段,它才负责监听客户端的连接请求,所以会发生阻塞

accept函数

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:阻塞等待客户端建立连接
参数:
sockfd:
    socket文件描述符,即socket函数的返回值
addr:
    传出参数,返回成功与服务器建立连接的客户端地址信息,含IP地址和端口号
addrlen:
    传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
    成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

注意,在服务器端共建立了两个socket文件描述符。分别是使用socket函数创建的一个socket文件描述符(监听描述符listenfd)和使用accept创建的文件描述符(已连接描述符connfd)。监听描述符listenfd的作用是在accept函数中等待来自客户端的连接请求到达监听描述符。而已连接描述符connfd就专门用来与客户端建立通信。形象的比喻就是listenfd就像酒店的迎宾小姐,客户端就相当于客人,connfd就是酒店为每个客人配备的服务人员,当迎宾小姐迎接到客人之后,她就不管这个客人了,转而去迎接其他客人,进入酒店的这个客人转而与酒店为其配备的服务人员进行交流(类比客户端与服务器的通信)

connect函数

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:使用现有的socket与服务器建立连接
参数:
sockfd:
    socket文件描述符,即socket函数的返回值
addr:
    传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
    传入参数,传入sizeof(addr)大小
返回值:
    成功返回0,失败返回-1,设置errno

注意:客户端自己并没有像服务器一样使用bind函数绑定客户端地址结构,其是系统采用的"隐式绑定",也就是操作系统自动帮我们绑定好了

TCP通信流程分析

server:

  1. socket() 创建socket
  2. bind() 绑定服务器地址结构
  3. listen() 设置监听上限
  4. accept() 阻塞监听客户端连接
  5. read(fd) 读socket获取客户端数据
  6. 根据读到的数据进行相应操作
  7. write(fd) 将数据发送给客户端
  8. close() 关闭连接

client:

  1. socket() 创建socket
  2. connect() 与服务器建立连接
  3. write() 写数据到socket
  4. read() 读取服务器发来的响应
  5. 显示读取结果
  6. close 关闭连接
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值