Linux下socket入门介绍

以下内容引述至《Linux/Unix系统编程手册》

概述

应用程序使用socket进行通信的方式如下

  • 各个应用程序创建一个socket,socket是一个允许通信的“设备”,两个应用程序都需要用到它;
  • 服务器将自己的socket绑定到一个总所周知的地址上使得客户端能够定位到它的位置
    使用socket系统调用能够创建一个socket,它返回一个用来在后续系统调用中引用该socket的文件描述符
fd = socket(domain, type, protocol);

通信domain

socket存在于一个通信domain中,它确定

  • 识别出一个socket的方法
  • 通信范围(主机IPC通信 or 网络通信)
Domain执行的通信应用程序间的通信地址格式地址结构
AF_UNIX内核中同一主机路径名socketaddr_un
AF_INET通过IPv4通过IPv4网络连接起来的主机32位IPv4地址+16位端口号socketaddr_in
AF_INET6通过IPv6通过IPv6网络连接起来的主机128位IPv6地址+16位端口号sockaddr_in6

socket类型

每个socket实现都至少提供了两种socket:流和数据报
流 socket(SOCKET_STREAM)提供了一个可靠的双向的字节流通信信道。在这段描述中的属于的含义:

  • 可靠的:表示可以保证发送者传输到数据会完整无缺地到达接收应用程序或收到一个传输失败的通知;
  • 双向的:表示数据可以在两个socket之间的任意方向上传输;
  • 字节流:表示与管道一样不存在消息边界的概念

数据报socket(SOCK_DGRAM)允许数据以被称为数据报的消息的形式进行交换。在数据报socket中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或者根本就无法到达的。

socket系统调用

  1. socket()系统调用创建一个新socket;
  2. bind()系统调用将一个socket绑定到一个地址上。通常,服务器需要使用这个调用来将其socket绑定到一个众所周知的地址上使得客户端能够定位到该socket上;
  3. listen()系统调用允许一个流socket接收来自其他socket的接入连接;
  4. accept()系统调用在一个监听流socket上接收来自一个对等应用程序的连接,并可选地返回对等socket的地址;
  5. connect()系统调用建立与另一个socket之间的连接

在大多数Linux架构上,所有这些socket系统调用实际上被实现成了通过单个系统调用socketcall()进行多路复用的库函数

socket I/O可使用传统的read()和write()系统调用或使用一组socket特有的系统调用(如read()、recv()、sendto()以及recvfrom())来完成。在默认情况下,这些系统调用在I/O操作无法被理解完成时会阻塞,通过使用fcntl() F_SETFL操作来启用O_NONBLOCK打开文件状态标记可以执行非阻塞I/O

创建socket

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

绑定地址

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

除了将一个服务器的socket绑定到一个众所周知的地址之外还存在其他做法。如对于一个Internet domain socket来讲,服务器可以不调用bind()而直接调用listern(),这将会导致内核为该socket选择一个临时端口,之后服务器可以使用getsockname()来获取soket的地址。在这种场景中,服务器必须要发布其地址使得客户端能够知道如何定位到服务器的socket。这种发布可以通过向一个中心目录服务应用程序注册服务器的地址来完成,之后客户端可以通过这个服务来获取服务器的地址。

通用socket地址结构 struct sockaddr

struct sockaddr {
	sa_family sa_family;
	char      sa_data[14];
};

流 socket

流socket的运作与电话系统类似

  1. socket()系统调用将会创建一个socket,这等价于安装一个电话。为使两个应用程序能够通信,每个应用程序都必须要创建一个socket
  2. 通过一个流socket通信类似于一个电话呼叫。一个应用程序在进行通信之前必须要将其socket连接到另一个应用程序的socket上
  3. 一旦建立了一个连接之后就可以在应用程序之间进行双向数据传输直到其中一个使用close()关闭连接为止。通信是通过传统的read()和write()系统调用或通过一些提供了额外功能的socket调用的系统调用(send()和recv())来完成的

主动和被动socket

流socket通常可以分为主动和被动两种

  • 在默认情况下,使用socket创建的socket是主动的。一个主动的socket可用在connect()调用中来建立一个到一个被动socket的连接。这种行为被称为执行一个主动的打开;
  • 一个被动socket是一个通过调用listen()以标记成允许接入连接的socket,接受一个接入连接通常被称为执行一个被动的打开。

监听接入连接

listen()系统调用将文件描述符sockfd引用的流socket标记为被动。这个socket后面会被用来接受来自其他(主动的)socket的连接

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

无法在一个已连接的socket上执行listen()

未决的连接:
客户端可能会在服务器调用accept()之前调用connect,这种情况是有可能发生的,如服务器可能正忙于处理其他客户端,这将会产生一个未决的连接
内核必须记录所有未决的连接请求的相关信息,这样后续的accept()就能够处理这些请求
backlog参数用于限制这些未决连接的数量

接收连接 accept()

accept()系统调用在文件描述符sockfd引用的监听流socket上接收一个接入连接。如果在调用accept()时不存在未决的连接,那么调用就会阻塞知道有连接请求到达为止

#include <sys/socket.h>
int accept(int sockfd, struct socketaddr *addr, socklen_t *addrlen);

accept会创建一个新的socket,并且正式这个新的socket会与执行connect()的对等socket进行连接。

连接到对等socket: connect()

connect()系统调用将文件描述符socketfd引用的主动socket连接到地址通过addr和addrlen指定的监听socket上

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

流 socket I/O

一对连接的流socket在两个端点之间提供了一个双向通信信道
连接流socket上I/O的语义与管道上I/O的语义类似

  • 要执行I/O需要使用read()和write()系统调用,由于socket是双向的,因此在连接的两端都可以使用这两个调用;
  • 一个socket可以使用close()系统调用来关闭或在应用程序终止后关闭。之后当对等应用程序试图从连接的另一端读取数据时将会收到文件结束。如果对等应用程序试图向其socket写入数据,那么它就会收到一个SIGPIPE信号,并且系统调用会返回EPIPE错误。

终止连接 close()

终止一个流的socket连接的常见方式是调用close(),如果多个文件描述符引用同一个socket,那么当所有的描述符被关闭之后连接就会终止。

数据报

数据报socket的运作类似于邮政系统

  1. socket()系统调用等价于创建一个邮箱,所有需要发送和接收数据报的应用程序都需要使用socket创建一个数据报socket;
  2. 为允许另一个应用程序发送其数据报,一个应用程序需要使用bind()将其socket绑定到一个总所周知的地址上;
  3. 要发送一个数据报,一个应用程序需要调用sendto(),它接收的其中一个参数是数据报发送到的socket的地址;
  4. 为接收一个数据报,一个应用程序需要调用recvfrom(),它在没有数据报到达的时会阻塞,由于recvfrom()允许获取发送者的地址,因为可以在需要的时候发送一个响应;
  5. 当不再需要socket时,应用程序需要使用close()关闭socket

交换数据报: recvfrom()和sendto()

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, \
	struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags, \
	const strcut sockaddr *dest_addr, socklen_t addrlen);

数据报socket上使用connect()

尽管数据报是无连接的,但是数据报socket上应用connect()系统调用仍然是起作用的。在数据报socket上调用connect()会导致内核记录这个socket对等socket的地址。
术语:已连接的数据报socket就是调用了connect的数据报;非连接的数据报socket是指那些没有调用connect()的数据报socket
当数据报socket连接后:

  • 数据报的发送可在socket上使用write()来完成并且会自动发送到同样对等的socket上;
  • 在这个socket上只能读取自由对等socket发送的数据报

为一个数据报socket设置一个对等的socket,这种做法的一个明显优势是在该socket上传输数据时,可以使用更简单的I/O系统调用,即无需使用指定了dest_addr和addrlen参数的sendto(),只需要调用write()即可。
设置一个对等socket往往能够带来性能上提升。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值