【linux】系统编程-8-Socket

李柱明 2021-01-21 19:24:17
编程 linux 系统 统编 8-socket



前言

原文: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,错误存在于error中

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,错误存在于error中:
        • 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,错误存在于error中:
        • 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,错误存在于error
  • 注意,网络编程中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(设置服务类型)等等。

参考:

* 野火
版权声明
本文为[李柱明]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/lizhuming/p/14309823.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云