Socket 入门

Socket 英文有插座的意思,可以想象端口就是插座上的口,端口不能被其他进程占用。想象 Socket 类似于操作某个 IP 地址上的某个端口以达到点对点通信的目的,需要绑定到某个具体的进程和端口。

Socket 原本代表的是 Unix 上的原始套接字(RawSocket)用于描述文件的内存镜像,但因为 Unix 的设计哲学是“一切都是文件”,所以后来的网络版的进程间通信也就被冠名为文件描述符。本质上讲 Socket 是 Unix 系统设计的一种思想。

Socket 没有层的概念,可以把它看成是对 TCP/IP 协议簇的一个门面模式的封装。可以让编程变得更加简单。

基础

文件描述符

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,操作系统为每个运行的进程维护一张单独的文件描述符表。当程序打开一个现有文件或者创建一个新文件时,系统把一个指向此该文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者 。应用程序只需记住这个描述符,并在以后操作该文件时使用它。操作系统把该描述符作为索引,用来访问进程描述符表,通过指针找到保存该文件所有的信息的数据结构。

系统创建一个 socket 连接,也是会打开一个 Socket 文件描述符。

函数

Socket 通信双方的函数如下:

socket

1
int socket(int domain, int type, int protocol);

socket() 函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而 socket() 用于创建一个 socket 描述符(socket descriptor),它唯一标识一个socket。

打开一个网络通讯端口,如果成功的话,返回一个文件描述符,应用程序可以像读写文件一样用 read/write 在网络上收发数据,如果 socket() 调用出错则返回-1。

bind

1
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

服务器需要调用 bind 绑定一个固定的网络地址和端口号。服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接。

bind() 的作用是将参数 sockfd 和 myaddr 绑定在一起,使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号。

listen

1
int listen(int sockfd, int backlog);// backlog取值0~5.

listen() 声明 sockfd 处于监听状态,并且最多允许有 backlog 个客户端处于连接待状态,如果接收到更多的连接请求就忽略。 listen() 成功返回 0,失败返回 -1。

accept

1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

三方握手完成后,服务器调用 accept() 接受连接,如果服务器调用 accept() 时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。