浅析linux服务端socket编程

记得大一第一次接触linux服务端编程时,当时看的linuxc编程实战中有如下一副示意图

上图虽然简单,但是却包含了我们编写服务器客户端程序时所有的最基本的接口。我们平时所用的第三方网络库例如libevent等,都是将上图中的函数接口进一步封装。 图中的接口使用简单,但是控制起来未必容易,接下来,我会为大家一一介绍各个接口的使用方法,以及调用该接口可能遇到的问题

1.Socket接口

socket接口用来创建一个套接字描述符,函数定义如下

int socket(int domain,int type,int protocol);//成功返回文件描述符,失败返回-1

参数介绍: .domain指定使用哪个底层协议,PF_INET(ipv4) PF_INET6(ipv6) .type指定协议的服务类型,例如TCP服务类型为SOCK_STREAM(流协议),udp为SOCK_UGRAM(数据报协议) .protocol为对具体协议的控制,一般使用默认控制设为0

2.bind接口

bind接口用来绑定一个由1中返回的socket套接字,所谓绑定是指将某个具体的address(包含IP和port)指定给某个套接字,其接口如下

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

参数介绍: .socketfd为地址所绑定套接字对应的套接字描述符 .sockaddr为所绑定的具体地址 .addrlen为具体大小 关于bind接口,一般服务器程序会调用这个接口,因为作为服务器你的连接套接字绑定了地址,客户端才知道咋么连你。如果作为客户端则这个接口是非必须的,如果你自己给客户端调用了该接口,则连接socket对应的地址就是你所绑定的地址,如果你没调用该接口,则内核会帮你匿名调用该接口,所以最后所绑定的地址也是不确定的

3.listen接口

使用该接口可以使我们2中所绑定的套接字变为监听socket,接听socket可以说是一种特殊的socket,之所以说他特殊是因为它的可读事件为有新的连接到来,并前获取新连接用accept调用(下面会介绍)其接口如下

int listen(int sockfd,int backlog);//成功返回0,失败-1

参数说明:

.sockfd,需要设置为监听套接字的套接字描述符.backlog,监听队列的长度

需要注意的是当listen调用完成之后,内核就为我们维护好了俩个队列,如下图所示

图片来自网络

如上图,内核维护的俩个队列分别为syn队列和accept队列。其中syn队列中保存的是还处于TCP三次握手的连接,当完成三次握手则把连接放入accept中供accept读取,该队列的大小为参数backlog的大小,那么,我们平时应该把backlog设为多大才合适呢?设的太小如果accept队列满,服务端会不会把新来的连接拒绝掉呢?其实一般情况下我们的backlog不必设的太大,一般设为5-8就可以了,以为前面说过syn队列会把已建好的连接给accept队列,当accept队列满时,此连接仍然会暂存在syn队列中,所以新连接并不会被丢弃,那么什么时候会拒绝新连接呢?当syn队列满时,内核才会拒绝新来的连接。关于syn队列的大小上限由内核给出,可在/pro/sys/net/ipv4/tcp_max_syn_backlog中查看我的ubuntu默认为256个。

既然存放已建立好的连接的队列accept和存放半连接(正在三次握手)的队列syn队列都有大小限制,那么我们在编写我们的服务器程序时就因该在监听套接字可读时,尽快的去读取它,如果我们在监听套接字可读时,而由于忙其他事来不及处理可读事件,那么可能会使accept队列堆满,accept堆满之后,新的完成三次握手的连接就会继续堆积在syn队列中,当sys队列也满时,那么新的客户端请求就连不上我们的服务器了,后果很严重,所以我们因该在服务端编程中尽可能的在监听队列可读时,就马上将其读走,最通用的方法是给读监听套接字单开一个线程。关于监听队列可读时的读取方法,我想在补充一点,由于accept可以保存多个可读的连接,所以当我们使用I/O复用可读事件发生时可能我们的一般做法是,调用一次accept接口,然后设置新得的套接字,当高并发情况下(同时有好多个连接)其实其实我们完全可以使用一个循环来accept将accept上的连接都读完4.accept接口

accept用来接受3中监听套接字的新连接,函数详细接口如下

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);//成功返回新建的套接字描述符,失败返回-1

参数详解:

.sockfd为监听套接字描述符.addr保存新来的连接的地址信息.addrlen保存地址的大小5.send接口

经过上述步骤我们已经可以获得与客户端建立的socket,那么我们接下来就可以通过send接口来给客户端发送数据了,send的接口如下

ssize_t send(int sockfd,const void *buf,size_t len,int flags);//成功返回写入的数据长度,失败返回-1*参数详解:*.sockfd为send具体写入的套接字描述符.buf为保存待写入数据的用户态缓冲区.len为待写入数据的大小.flags为对数据收发提供的一些额外控制

简单的介绍几个我常用的flags标志,及作用

flags 具体作用

SO_SNDBUF 设置发送缓冲区的大小

SO_SNDLOWAT 发送缓冲区的低水位标志,当发送缓冲区变满,经TCP不断的发送出去数据,缓冲区可用空间不断增大,当达到低水位标志的大小时,就触发可写,默认大小为2048

MSG_OOB 发送带外数据

一个简单的缩略的send函数调用示意图 为了让大家对send调用有个简单的初步概念,我画了个简单的示意图如上 对照上图,当程序执行send调用时首先会把用户态的内容如上图1,2,3块拷贝到内核态的TCP发送缓冲区,然后内核协议栈调用tcp_push将发送缓冲区中的数据送至IP层,之后send调用就会返回,因此,send调用返回并不代表我们的数据已经被接收方收到,它只是被送到IP层,送到IP层内核就会想你表态他会尽量将数据发给接收方。

与那些新人和旧人们共同经历吧!

浅析linux服务端socket编程

相关文章:

你感兴趣的文章:

标签云: