【Linux基础】socket编程详解

引入

在讲Socket编程之前,先来回顾一下TCP的三握手与四挥手过程,这样就更易理解Socket编程了。

1.TCP三握手建立连接过程

【Linux基础】socket编程详解

2.TCP四挥手释放连接过程

【Linux基础】socket编程详解

Socket编程

先来张图,看看其过程如何:

【Linux基础】socket编程详解

针对上图出现的函数或其它重要的函数,我在此解释下:

1.socket():申请了一个描述符(本地可见),但不能对外提供通讯

(1)函数

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

(2)参数

  • domain 需要被设置为“AF_INET”(或者其他值)

  • type 告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”;

  • protocol 设置为0

2.bind():组合IP和端口,对外提供连接

(1)函数

int bind (int sockfd , struct sockaddr *my_addr , int addrlen)

(2)参数

  • sockfd 是由socket()函数返回的套接字描述符。

  • my_addr 是一个指向struct sockaddr 的指针,包含有关你的地址的信息:名称、端口和IP 地址。

  • addrlen 可以设置为sizeof(struct sockaddr)。

3.listen():监听绑定的IP端口上的数据,将半连接转到全连接中

(1)函数

int listen(int sockfd, int backlog)

(2)参数

  • sockfd 是一个套接字描述符,由socket()系统调用获得。

  • backlog 是未经过处理的连接请求队列可以容纳的最大数目。每一个连入请求都要进入一个连入请求队列,即请求队列的最大值。

4.accept():从listen监听的全连接队列中取连接,申请文件描述符,

(1)函数

int accept(int sockfd, void *addr, int *addrlen)

(2)参数

  • sockfd 是正在listen() 的一个套接字描述符。

  • addr 一个指向struct sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息的IP 地址和端口

  • addrlen 是一个本地的整型数值,在它的地址传给accept() 前它的值应该是sizeof(struct sockaddr_in);accept()不会在addr 中存储多余addrlen bytes 大小的数据。如果accept()函数在addr 中存储的数据量不足addrlen,则accept()函数会改变addrlen 的值来反应这个情况。

5.connect():连接到指定的IP

(1)函数

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)

(2)参数

  • sockfd :套接字文件描述符,由socket()函数返回的。

  • serv_addr 是一个存储远程计算机的IP 地址和端口信息的结构。

  • addrlen 应该是sizeof(struct sockaddr)。

6.write():写入数据

(1)函数

ssize_t write(int fd, const void*buf,size_t nbytes)

(2)参数

  • fd 为要写入的文件的描述符
  • buf 为要写入的数据的缓冲区地址,
  • nbytes 为要写入的数据的字节数。

(3)返回值

  • ssize_t 是通过 typedef 声明的 signed int 类型,即size_t 是通过 typedef 声明的 unsigned int 类型;ssize_t 在 “size_t” 前面加了一个”s”,代表 signed。

  • write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -1。

7.read():读取数据

(1)函数

ssize_t read(int fd,void *buf,size_t nbyte)

(2)参数

  • fd 为要读取的文件的描述符,
  • buf 为要接收数据的缓冲区地址,
  • nbytes 为要读取的数据的字节数。

(3)返回值

read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。

8.send():发送数据(通过TCP)

(1)函数

int send(int sockfd, const void *msg, int len, int flags)

(2)参数

  • sockfd 是代表你与远程程序连接的套接字描述符。

  • msg 是一个指针,指向你想发送的信息的地址。

  • len 是你想发送信息的长度。

  • flags 发送标记。一般都设为0。

9.recv():接收数据(通过TCP)

(1)函数

int recv(int sockfd, void *buf, int len, unsigned int flags)

(2)参数

  • sockfd 是你要读取数据的套接字描述符。

  • buf 是一个指针,指向你能存储数据的内存缓存区域。

  • len 是缓存区的最大尺寸。

  • flags 是recv() 函数的一个标志,一般都为0 。

10.close() :关闭连接

(1)使用方法

close(sockfd)

(2)功能

执行close()之后,套接字将不会在允许进行读操作和写操作

(3)底层实现:引用计数

11.shutdown()

(1)函数

int shutdown(int sockfd, int how)

(2)参数

  • sockfd 是一个你所想关闭的套接字描述符.
  • how 可以取:0 表示不允许以后数据的接收操;1 表示不允许以后数据的发送操作;2 表示和close()一样,不允许以后的任何操作(包括接收,发送数据)

(3)how的方式

  • SHUT_RD:关闭连接的读端。
  • SHUT_WR:关闭连接的写端。
  • SHUT_RDWR:相当于调用shutdown两次:首先是SHUT_RD,然后SHUT_WR

12.sendto():发送数据(通过UDP)

(1)函数

int sendto(int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr *to, int tolen);

(2)参数

  • sockfd 是代表你与远程程序连接的套接字描述符。
  • msg 是一个指针,指向你想发送的信息的地址。
  • len 是你想发送信息的长度。
  • flags 发送标记。一般都设为0。

13.recvfrom():接收数据(通过UDP)

(1)函数

int recvfrom(int sockfd, void *buf, int len, unsigned int flags,struct sockaddr *from, int *fromlen)

(2)参数

  • sockfd 是你要读取数据的套接字描述符。
  • buf 是一个指针,指向你能存储数据的内存缓存区域。
  • len 是缓存区的最大尺寸。
  • flags 是recv() 函数的一个标志,一般都为0
  • from 是一个本地指针,指向一个struct sockaddr 的结构(里面存有源IP 地址和端口数).
  • fromlen 是一个指向一个int 型数据的指针,它的大小应该是sizeof ( struct sockaddr).当函数返回的时候,formlen 指向的数据是form 指向的struct sockaddr 的实际大小.

小疑问

1.一个客户端对外最多提供多少连接(一台服务器最多接收多少端口)?

答:由内核内存上限(listen所能监听的两个队列大小)和文件描述符大小(进程所能打开的文件个数上限)决定。取两个值中较小的值

注:使用ulimit -n 命令可以设置文件描述符的上限

2.服务器怎么处理百万连接(c10k问题)?

答:一种方法是采用一个进程/线程一个连接,但是这样太消耗性能,另一种方法则是一个进程/线程创建多个连接(io多路复用),即使用epoll技术

3.为什么要设置TCP缓冲区?

答:① 减少系统调用次数(减少内核读取次数);② 保证网络带宽利用率

4.send/recv函数分别会在什么时候阻塞?

答:send函数(发送缓冲区)在buffer的允许发送但未发送区已满时阻塞;而recv函数(接受缓冲区)会在已确认未读取的数据为空时阻塞。

5.五元组指的是什么?

答:五元分别指:网络协议、源IP、源端口、目的IP、目的端口

例:通过命令 netstat –an | grep tcp 获取五元组

【Linux基础】socket编程详解

分析:从上图可以看出,每列分别表示Protocol、Recv-Q 、Send-Q、源IP:源端口、目的IP:目的端口 State,从而可以清晰的看出tcp的五元组,

6.怎样获取内核缓冲区的大小?

【Linux基础】socket编程详解

分析:从上图可以看出,内核缓冲区为163840、tcp接收缓冲区为87380、 tcp 发送缓冲区为16384

7.什么是带外数据

答:带外数据拥有比一般数据高的优先级,但也是通过连接传输数据的,TCP 是由一种叫做“紧急模式”的方法来传输带外数据的,当TCP 收到一个包含URG 标志的数据段时,,TCP 会检查“紧急指针”来验证是否紧急指针所指的数据已经到达本地,然后通过PSH标志立即发送数据。

8.listen(fd,backlog)函数的作用

答:用于获取两个队列(半连接、全连接),前三次连接是在半连接队列,之后转入全连接,

注:文件位置在:/proc/syn/net/ipv4/

9.为什么不将listen和accept组合为一个函数?

答:因为listen是非阻塞的。处理三次握手,accept负责listen处理过的连接,如果组合为一个函数,那么就只能是顺序的三握手四挥手,没有并发的功能了。

10. listen为什么是非阻塞的?为什么维护两个连接?

答:非阻塞是因为编程需要,阻塞时数据有交互;两个连接是因为要维护数据转换过程,accept需要的时候就可以从listen中拿到,不用循环遍历

11.accept为什么是阻塞的?

答:若是非阻塞的,就要等待listen的全连接队列、轮询会消耗资源。所以设置accept函数为阻塞式函数。



本人才疏学浅,若有错,请指出,谢谢!
如果你有更好的建议,可以留言我们一起讨论,共同进步!
衷心的感谢您能耐心的读完本篇博文!