前言

原文:https://www.cnblogs.com/lizhuming/p/14309823.html

11. 套接字

  • 前面介绍的管道、信号量、消息队列共享内存等等都是用于单个计算机的进程间通信
  • 基于套接字的进程间通信机制,可实现跨主机的进程间通信

11.1 Socket简介

  • 套接字(socket)是一种通信机制,凭借这种机制, 客户端<->服务器 模型的通信方式既可以在本地设备上进行,也可以跨网络进行。
  • 套接字机制可实现 多客户端到一个服务器
  • 在Socket中,它使用一个套接字来记录网络的一个连接,套接字是一个整数。
  • 在网络中,可以对socket进行网络连接、读取数据、发送数据和终止连接等操作。
  • 相关头文件
#include <sys/types.h>
#include <sys/socket.h>
  • 套接字本身只是用户程序与内核交互信息的枢纽,没有网络协议地址和端口号等信息,所以在使用时需要用bind()绑定一下
  • 概念上注意套接字与端口号的区别。套接字可以理解为IP+端口号
  • 个人辅助理解:一个端口号对应一个应用程序,一个应用程序内,理论上可以多个套接字绑定一个端口号,但是大多数系统不允许这么干。 肚脐
  • 一个服务器一般只创建一个监听套接字,它在该服务器的生命周期内是一直存在的。同时,服务器也会为每个已连接的客户端创建一个已连接套接字在accept()连接成功后,内核会自动生成一个全新的套接字)。

11.2 socket()

  • socket()函数用于创建一个socket描述符,用于标识唯一一个socket。
  • 函数原型:int socket(int domain, int type, int protocol);
    • domain:表示该套接字使用的协议族(对于TCP/IP,一般选择AF_INET就可以了

      • AF_UNIX, AF_LOCAL: 本地通信
      • AF_INET : IPv4
      • AF_INET6 : IPv6
      • AF_IPX : IPX - Novell 协议
      • AF_NETLINK : 内核用户界面设备
      • AF_X25 : ITU-T X.25 / ISO-8208 协议
      • AF_AX25 : 业余无线电 AX.25 协议
      • AF_ATMPVC : 访问原始ATM PVC
      • AF_APPLETALK : AppleTalk
      • AF_PACKET : 底层数据包接口
      • AF_ALG : 内核加密API的AF_ALG接口
    • type:服务类型
      • SOCK_STREAM:提供可靠的(即能保证数据正确传送到对方)面向连接的Socket服务,多用于资料(如文件)传输,如TCP协议。
      • SOCK_DGRAM:是提供无保障的面向消息的Socket 服务,主要用于在网络上发广播信息,如UDP协议,提供无连接不可靠的数据报交付服务。
      • SOCK_SEQPACKET:为固定最大长度的数据报提供有序的,可靠的,基于双向连接的数据传输路径。
      • SOCK_RAW:表示原始套接字,它允许应用程序访问网络层的原始数据包,这个套接字用得比较少,暂时可忽略。
      • SOCK_RDM:提供不保证排序的可靠数据报层。
    • protocol:套接字使用的协议。当protocol为0时,会自动选择type类型对应的默认协议。
      • 如:当在IPv4,只有TCP协议提供SOCK_STREAM这种可靠的服务,此时,protocol为0即可。
    • 返回
      • 成功:返回一个大于 0 的套接字描述符
      • 失败:返回-1

11.3 bind()

  • bind()函数用于将一个 IP 地址或端口号与一个套接字进行绑定,许多时候内核会帮我们自动绑定一个IP地址与端口号,但是也可以手动绑定。
  • 函数原型:int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
    • sockfd:sockfd是由socket()函数返回的套接字描述符。
    • my_addr:my_addr是一个指向套接字地址结构的指针。
    • addrlen:addrlen指定了以addr所指向的地址结构体的字节长度。
    • 返回:
      • 成功:返回 0
      • 失败:返回-1
  • sockaddr结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14]; // 填入IP、端口号等信息
};
  • 一般不使用 sockaddr 结构体,因为操作不方便,而是使用 sockaddr_in。因为两者占用相同的空间。可以代替。(赋参数时进行强制类型转换即可)
  • sockaddr_in结构体:
struct sockaddr_in {
short int sin_family; /* 协议族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
};

11.4 connect()

  • connect()函数用于客户端,将socket与远端的IP地址、端口号绑定。如在TCP客户端中调用该函数,将会发生握手过程。
  • 函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    • 参数参考 bind() 函数
    • 返回:
      • 成功:返回0
      • 失败:返回-1,错误存在于errno中

11.5 listen()

  • listen()函数用于服务器端,使服务器端进入监听状态,等待客户端请求连接。
  • 函数原型:int listen(int sockfd, int backlog);
    • sockfd:sockfd是套接字描述符。
    • backlog:表示sockfd的等待连接队列能够达到的最大值。
      • 当多个客户端同时尝试连接本服务器时,内核会在自己的进程空间中维护一个队列来保存这些请求,当队列满时,后面进来的请求,服务器端会将其丢弃,客户端会收到连接失败的错误。

11.6 accept()

  • accept()函数用于服务器端,主要是处理来自客户端的连接请求。
  • 函数原型:int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
    • 参数参考 bind() 函数
    • 返回:
      • 成功:如果连接成功,会建立一个和参数sockfd相同属性的连接套接字,并为该连接套接字分配一个文件描述符。最终返回一个socket描述符(非负值)。
      • 失败:返回-1
  • 当套接字标记为阻塞模式,队列中没有未完成处理的连接请求时,调用 accept() 函数会一直阻塞,直至与远端建立连接。
  • 当套接字标记为非阻塞模式,队列中没有未完成处理的连接请求时,调用 accept() 函数会立即返回EAGAIN。

11.7 read()

  • 当客户端与服务器端建立好TCP连接之后,我们就可以通过sockfd套接字描述符(已连接套接字描述符,由sccept()产生)来收发数据。
  • 接收网络中的数据可以使用read()、recv()、recvfrom()等。
  • 函数原型:ssize_t read(int fd, void *buf, size_t count);
    • fd:可以是文件描述符,也可以是套接字描述符。在本章节中为套接字描述符。
    • buf:接收数据的缓冲区。
    • count:需要读取的字节数。
    • 返回:
      • 成功:实际读取到的字节数。(情况一:文件剩余字节数小于count,则返回的字节数是小于count的)
      • 失败:返回-1,错误存在于errno中:
        • EINTR:在读取到数据前被信号所中断。
        • EAGAIN:使用 O_NONBLOCK 标志指定了非阻塞式输入输出,但当前没有数据可读。
        • EIO:输入输出错误,可能是正处于后台进程组进程试图读取其控制终端,但读操作无效,或者被信号SIGTTIN所阻塞, 或者其进程组是孤儿进程组,也可能执行的是读磁盘或者磁带机这样的底层输入输出错误。
        • EISDIR:fd 指向一个目录。
        • EBADF:fd 不是一个合法的套接字描述符,或者不是为读操作而打开。
        • EINVAL:fd 所连接的对象不可读。
        • EFAULT:buf 超出用户可访问的地址空间。

11.8 recv()

  • recv()函数功能和read()函数功能差不多,客户端和服务器端都可以使用该函数来接收另一端的数据。
  • recv()实际上是拷贝数据,接收数据是由协议完成的。recv()函数会先检查套接字的接收缓冲区,若缓冲区中没有数据或正在接收数据,recv()函数会一直等待,直至协议接收数据完毕,recv()才能把缓冲区的数据读走。
  • 函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    • sockfd:指定接收端套接字描述符。
    • buf:指定一个接收数据的缓冲区,该缓冲区用来存放recv()函数接收到的数据。
    • len:指定recv()函数拷贝的数据长度。
    • flags
      • 0:一般置为0即可
      • MSG_OOB:接收以out-of-band送出的数据。
      • MSG_PEEK:保持原有数据,就是说接收到的数据在缓冲区中并不会被删除, 如果再调用recv()函数还会拷贝相同的数据到buf中。
      • MSG_WAITALL:强迫接收到指定len大小的数据后才能返回, 除非有错误或信号产生。
      • MSG_NOSIGNAL:recv()函数不会被SIGPIPE信号中断。
    • 返回:
      • 成功:实际读取到的字节数。
      • 失败:返回-1,错误存在于errno中:
        • EBADF:fd 不是一个合法的套接字描述符,或者不是为读操作而打开。
        • EFAULT:buf 超出用户可访问的地址空间。
        • ENOTSOCK:参数 s 为一文件描述词, 非socket。
        • EINTR:在读取到数据前被信号所中断。
        • EAGAIN:此动作会令进程阻塞, 但参数s的 socket 为不可阻塞。
        • ENOBUFS:buf内存空间不足。
        • ENOMEM:内存不足。
        • EINVAL:传入的参数不正确。

11.9 write()

  • write()函数一般用于处于稳定的TCP连接中传输数据。UDP协议也可以使用。
  • write()函数在写入数据完成后并不是立即发送的,至于什么时候发送则由TCP/IP协议栈决定。
  • 函数原型:ssize_t write(int fd, void *buf, size_t count);
    • fd:可以是文件描述符,也可以是套接字描述符。在本章节中为套接字描述符。
    • buf:发送数据的缓冲区。
    • count:需要发送字节数。
    • 返回:
      • 成功:返回实际写入的字节数
      • 失败:返回-1,错误存在于errno
  • 注意,网络编程中write()是不负责将全部数据写完之后再返回的,或许中途就返回了。若想保证数据全部写入,必须循环运行write()函数,可自行封装。代码如下:(copy野火
/* Write "n" bytes to a descriptor. */
ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft; //剩余要写的字节数
ssize_t nwritten; //已经写的字节数
const char *ptr; //write的缓冲区 ptr = vptr; //把传参进来的write要写的缓冲区备份一份
nleft = n; //还剩余需要写的字节数初始化为总共需要写的字节数 //检查传参进来的需要写的字节数的有效性
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) { //把ptr写入fd
if (nwritten < 0 && errno == EINTR) //当write返回值小于0且因为是被信号打断
nwritten = 0; /* and call write() again */
else
return(-1); /* error 其他小于0的情况为错误*/
} nleft -= nwritten; //还剩余需要写的字节数=现在还剩余需要写的字节数-这次已经写的字节数
ptr += nwritten; //下次开始写的缓冲区位置=缓冲区现在的位置右移已经写了的字节数大小
}
return(n); //返回已经写了的字节数
}

11.10 send()

  • 无论是客户端还是服务器应用程序都可以用write()函数来向TCP连接的另一端发送数据。
  • 当使用send()发送数据时,send()会先比较需要发送的长度len和套接字sockfd的发送缓冲区长度,若len大,则返回SOCKET_ERROR。若len小于等于,send()函数会先检查协议是否正在发送套接字sockfd的发送缓冲区的数据,如果是,就等待发送完毕,如果还没有开始发送,则send()函数会继续比较len和发送缓冲区中剩余空间长度,若len大,则等待发送缓冲区发送完毕,若len小于等于,则把数据copy到发送缓冲区。
  • copy成功后,send()函数就马上返回,但是不一定马上发送,什么时候发送取决于TCP/IP协议。
  • 函数原型:int send(int sockfd, const void *msg, size_t len, int flags);
    • sockfd:指定发送端套接字描述符。
    • msg:指定要发送数据的缓冲区。
    • len:指定recv()函数拷贝的数据长度。
    • flags:一般为0即可。
    • 返回:
      • 成功:返回实际copy的字节数
      • 失败:返回SOCKET_ERROR

11.11 sendto()

  • sendto()函数与send()函数相似,但是它会通过 struct sockaddr 指向的 to 结构体指定要发送给哪个远端主机,在to参数中需要指定远端主机的IP地址、端口号等,而tolen参数则是指定to 结构体的字节长度。
  • sendto()适用于已连接的数据报或流式套接口发送数据。
  • 函数原型:int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
    • s:一个标识套接口的描述字。
    • buf:包含待发送数据的缓冲区。
    • len:buf缓冲区中数据的长度。
    • flags:调用方式标志位。
    • to:(可选)指针,指向目的套接口的地址。
    • tolen:to所指地址的长度。

11.12 close()

  • close()函数是用于关闭一个指定的套接字。
  • 函数原型:int close(int fd);

11.13 ioctlsocket()

  • ioctlsocket()函数用于获取与设置套接字相关的操作参数。
  • 函数原型int ioctlsocket( int s, long cmd, u_long *argp);
    • s:指定要操作的套接字描述符。
    • cmd:对套接字s的操作命令。
      • FIONBIO:允许或禁止套接口s的非阻塞模式。
      • FIONREAD:确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。
      • SIOCATMARK:确实是否所有的带外数据都已被读入。
    • argp:指向cmd命令所带参数的指针。argp指向一个无符号长整型。如允许非阻塞模式则非零,如禁止非阻塞模式则为零。
    • 返回:
      • 成功:ioctlsocket()返回0。
      • 失败:返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码:
        • WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。   
        • WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。   
        • WSAEINVAL:cmd为非法命令,或者argp所指参数不适用于该cmd命令,或者该命令不适用于此种类型的套接口。
        • WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。   
        • WSAENOTSOCK:描述字不是一个套接口。

11.14 getsockopt()、setsockopt()

  • getsockopt()、setsockopt()分别是获取和设置套接字。
  • 函数原型:
    • int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
    • int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
      • sockfd:指定要操作的套接字描述符。
      • level
        • SOL_SOCKET:表示在Socket层。
        • IPPROTO_TCP:表示在TCP层。
        • IPPROTO_IP: 表示在IP层。
      • optname:该层的具体选项,如:
        • 对于SOL_SOCKET选项

          • SO_REUSEADDR(允许重用本地地址和端口)
          • SO_SNDTIMEO(设置发送数据超时时间)
          • SO_SNDTIMEO(设置接收数据超时时间)
          • SO_RCVBUF(设置发送数据缓冲区大小)等等。
        • 对于IPPROTO_TCP选项
          • TCP_NODELAY(不使用Nagle算法)
          • TCP_KEEPALIVE(设置TCP保活时间)等等。
        • 对于IPPROTO_IP选项
          • IP_TTL(设置生存时间)
          • IP_TOS(设置服务类型)等等。

参考:

* 野火

【linux】系统编程-8-Socket的更多相关文章

  1. Linux系统编程:socket网络编程(操作篇)

    一.问题思考 问1.网络通信应用在什么场合?通信的前提是什么? 答1.主要应用在不同主机进程间的互相通信,同一主机的进程也可以使用网络进行通信.通信的前提是如何标识通信进程的唯一,由于不同主机的进程极 ...

  2. Linux 系统编程 学习:06-基于socket的网络编程1:有关概念

    Linux 系统编程 学习:006-基于socket的网络编程1:有关概念 背景 上一讲 进程间通信:System V IPC(2)中,我们介绍了System IPC中关于信号量的概念,以及如何使用. ...

  3. Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信

    Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信 背景 上一讲我们介绍了网络编程的一些概念.socket的网络编程的有关概念 这一讲我们来看UDP 通信. 知识 U ...

  4. Linux系统编程温故知新系列 --- 01

    1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...

  5. linux系统编程之错误处理

    在linux系统编程中,当系统调用出现错误时,有一个整型变量会被设置,这个整型变量就是errno,这个变量的定义在/usr/include/errno.h文件中 #ifndef _ERRNO_H /* ...

  6. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  7. linux系统编程(一)概述

    glibc库封装了linux系统调用,并提供c语言接口 所以学习linux系统编程,主要参考glibc库系统调用相关api 一.进程控制: fork 创建一个新进程 clone 按指定条件创建子进程 ...

  8. Linux 系统编程 学习 总结

    背景 整理了Liunx 关于 进程间通信的 很常见的知识. 目录 与 说明 Linux 系统编程 学习:000-有关概念 介绍了有关的基础概念,为以后的学习打下基础. Linux 系统编程 学习:00 ...

  9. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  10. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

随机推荐

  1. React Hooks: useImperativeHandle All In One

    React Hooks: useImperativeHandle All In One useImperativeHandle https://reactjs.org/docs/hooks-refer ...

  2. Windows 10 滚动截图工具

    Windows 10 滚动截图工具 Edge & Note & Clip https://www.runoob.com/docker/docker-architecture.html ...

  3. WEB 使用lazysizes延迟加载图像

    原文 Native lazy-loading for the web Example <style> div { height: 3000px; } </style> < ...

  4. Flutter: ValueListenableBuilder 内容与ValueListenable保持"同步"的窗口小部件

    API 使用这个修改状态可以不用setState(). class _MyHomeState extends State<MyHome> { final ValueNotifier< ...

  5. Vue为何采用异步渲染

    Vue为何采用异步渲染 Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入到队列中一次 ...

  6. Project facet Java version 1.7 is not supported.解决方法

    最近遇到这个问题,在网上查到的解决方案基本都是下面几个: 1.右击项目,properties,project facets,改动java的version为1.7. 2.window,propertie ...

  7. JVM相关 - 深入理解 System.gc()

    本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...

  8. Java基础语法:包机制

    为了更好地组织类,Java 提供了包(package)机制. 这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class).接口(interface).枚举(enumerations)和注释( ...

  9. 逆向基础 C++ Primer Plus 第二章 开始学习C++

    C++ Primer Plus 第二章 开始学习C++ 知识点梳理 本章从一个简单的C++例子出发,主要介绍了创建C++程序的步骤,以及其所包含的预处理器编译指令.函数头.编译指令.函数体.注释等组成 ...

  10. SpringBoot(十):SpringBoot的简单事务管理

    SpringBoot集成Mybatis之后,进行事务管理.SpringBoot使用事务非常简单,底层依然采用的是Spring本身提供的事务. 1.在入口类中使用注解@EnableTransaction ...