【Linux】Linux网络编程(含常见服务器模型,下篇) - Go语言中文社区

【Linux】Linux网络编程(含常见服务器模型,下篇)


上一篇文章:【Linux】Linux网络编程(含常见服务器模型,上篇)

 

高级嵌套字函数

前面介绍的一些函数(read、write等)都是网络程序里最基本的函数,也是最原始的通信函数。下面介绍一下几个网络编程的高级函数:

recv()函数

int recv(int s, void *buf, int len, unsigned int flags);

函数说明:经socket接收数据,recv()用来接收远程主机指定的socket传来的数据,并把数据存到参数buf指向的内存空间,参数len为可接收数据的最大长度。参数flags一般设为0,其数值定义如下:

MSG_OOB:接收以out-of-band送出的数据;MSG_PEEK:返回来的数据并不会在系统内删除,如果再调用recv()会返回相同的数据内容;MSG_WAITALL:强迫接收到len大小的数据后才能返回,除非有错误或信号发生;MSG_NOSIGNAL:此操作不愿被SIGPIPE信号中断。

返回值:成功则返回接收到的字符数,失败则返回-1,错误码保存在errno中。

send()函数

int send(int s, const void *msg, int len, unsigned int flags);

函数说明:经socket发送数据,send()用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,参数msg指向欲连线的数据内容,参数len则为数据长度,参数flags一般设为0,其数值定义如下:

MSG_OOB:传送的数据以out-of-band送出;MSG_DONTROUTE:取消路由表查询;MSG_DONTWAIT:设置为不可阻断运作;MSG_NOSIGNAL:此动作不愿被SIGPIPE信号中断。

返回值:成功则返回接收到的字符数,失败则返回-1。

recvmsg()函数

int recvmsg(int s, struct msghdr *msg, unsigned int flags);

函数说明:经socket接收数据,recvmsg()用来接收远程主机指定的socket传来的数据。参数s为已建立好连线的socket,如果利用UDP协议,则不需要经过连线操作。参数msg指向欲年限的数据结构内容,参数glags一般设置为0。

返回值:成功则返回接收到的字符数,失败则返回-1。

sendmsg()函数

int sendmsg(int s, struct msghdr *msg, unsigned int flags);

函数说明:经socket发送数据,sendmsg()用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议,则不需要经过连线操作。参数msg指向欲连线的数据内容,参数len则为数据长度,参数flags一般设为0。

返回值:成功则返回接收到的字符数,失败则返回-1。

结构msghdr的定义如下:

struct msghdr
{
        void *msg_name;                //发送接收地址
        socklen_t msg_namelen;                //地址长度
        struct iovec *msg_iov;                //发送/接收数据量
        size_t msg_iovlen;                //元素个数
        void *msg_control;                //补充数据
        size_t msg_controllen;                //补充数据缓冲长度
        int msg_flags;                //接收消息标识
};

嵌套字的关闭

关闭嵌套字有两个函数close()和shutdown(),用close()时和用户关闭文件类似。

int shutdown(int s, int how);

函数说明:终止socket通信,shutdown()用来终止参数s所指定的socket连线。参数how有三种情况:

how=0:终止读取操作;how=1:终止传送操作;how=2:终止读取及传送操作。

返回值:成功则返回0,失败则返回-1。

close()和shutdown()的区别:

  • 对应的系统调用不同,close()函数对应的系统调用是sys_close(),在fs/open.c中定义。shutdown()函数对应的系统调用是sys_shutdown(),在net/socket.c中定义;
  • shutdown()只能用于套接字文件,close()可以用于所有文件类型;
  • shutdown()可以选择关闭全双工连接的读通道或者写通道,并没有释放文件描述符,close()会同时关闭全双工连接的读写通道,除了关闭连接外,还会释放套接字占用的文件描述符;
  • close()函数会关闭套接字ID,如果有多个进程共享一个套接字,close()每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close(),套接字将被释放。而shutdown()会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零。

 

嵌套字选项

有时用户要控制嵌套字的行为(如修改缓冲区的大小),这个时候用户就要控制嵌套字的选项了。

getsockopt()函数

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

函数说明:取得socket状态,getsockopt()会将参数s所指定的socket状态返回。参数optname代表欲取得何种选项状态,而参数optval则指向欲保存结果的内存地址,参数optlen为该空间的大小。

返回值:成功则返回0,若有错误则返回-1。

例子:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>

int main()
{
        int s,optval,optlen=sizeof(int);
        if((s=socket(AF_INET,SOCK_STREAM,0))<0)
                perror("socket");
        getsockopt(s,SOL_SOCKET,SO_TYPE,&optval,&optlen);
        printf("optval=%dn",optval);
        close(s);
        
        return 0;
}

setsockopt()函数

int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

函数说明:设置socket状态,setsockopt()用来设置参数s所指定的socket状态。参数level代表欲设置的网络层,一般设为SOL_SOCKET以存取socket层,参数optname代表欲设置的选项,有以下几种数值:

SO_DEBUG:打开或关闭排错模式;SO_REUSEADDR:允许在bind()过程中本地地址可重复使用;SO_TYPE:返回socket形态;SO_ERROR:返回socket已发生的错误原因;SO_DONTROUTE:送出的数据包不要利用路由设备来传输;SO_BROADCAST:使用广播方式传送;SO_SNDBUF:设置送出的暂存区大小;SO_RCVBUF:设置接收的暂存区大小;SO_KEEPALIVE:定期确认连线是否已终止;SO_OOBINLINE:当接收到OOB数据时会马上送至标准输入设备;SO_LINGER:确保数据安全且可靠地传送出去。

返回值:成功则返回0,若有错误则返回-1。

ioctl()函数

int ioctl(int handle, int cmd, [int *argdx, int argcx]);

函数说明:ioctl()是设备驱动程序中对设备的IO通道进行管理的函数。所谓对IO通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等。它的调用格式如下:

int ioctl(int fd, int cmd, ...);

其中:fd就是用户程序打开设备时使用open()函数返回的文件标识符,cmd就是用户程序对设备的控制命令,后面的省略号是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl()函数是文件结构中的一个属性分量,也就是说,如果驱动程序提供了对ioctl()的支持,用户就能在用户程序中使用ioatl()函数控制设备的IO通道。

返回值:成功则返回0,若有错误则返回-1。

 

服务器模型

循环服务器:UDP服务器

UDP循环服务器的实现非常简单。UDP服务器每次从嵌套字上读取一个客户端的请求并处理,然后将结果返回给客户端。可以用下面的算法来实现:

socket(...);
bind(...);

while(1){
        recvfrom(...);
        ...                //服务器处理函数
        sendto(...);
}

因为UDP是面向非连接的,没有一个客户端可以总是占住服务端,只要处理过程不是死循环,服务器对于每个客户端的请求总是能够满足。

循环服务器:TCP服务器

TCP服务器接收一个客户端的连接,然后处理,完成该客户端的所有请求后,断开连接。算法如下:

socket(...);
bind(...);
listen(...);

while(1){
        accept(...);
        
        while(1){
                read(...);
                ...                    //服务器处理函数
                write(...);
        }
}

TCP循环服务器一次只能处理一个客户端的请求。只有在该客户端的所有请求都满足后,服务器才可以继续响应后面的请求。如果有一个客户端占住服务器不释放时,其他的客户端都不能工作了。因此,TCP服务器很少采用循环服务器模型。

并发服务器:TCP服务器

为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型。并发服务器的思想是,每一个客户端的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。算法如下:

socket(...);
bind(...);
listen(...);

while(1){
        accept(...);

        if(fork(...)==0){                //子进程
                while(1){
                        read(...);
                        ...                //服务器处理函数
                        write(...);
                }
                close(...);
                exit(...);
        }

        close(...);
}

TCP并发服务器可以解决TCP循环服务器中客户端独占服务器的情况。不过也同时带来一个较大的问题,为了响应客户端的请求,服务器要创建子进程来处理,而创建子进程是一个非常消耗资源的操作。

并发服务器:多路复用IO

为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用IO模型。最常用的函数为select。

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数说明:IO多工机制,select()用来等待文件描述符状态的改变。参数n代表最大的文件描述符加1,参数readfds、writefds、exceptfds称为描述词组,是用来回传该描述词的读、写或例外的状况,参数timeout为结构timeval,用来设置select()的等待时间。下列宏提供了处理这三种描述词组的方式:

FD_CLR:用来清除描述词组set中相关fd的位;FD_ISSET:用来测试描述词组set中相关fd的位是否为真;FD_SET:用来设置描述词组set中相关fd的位;FD_ZERO:用来清除描述词组set全部的位。

返回值:如果参数timeout设为NULL则表示select()没有timeout。

常见的程序片段为fs_set readset:

FD_ZERO(&readset);
FD_SET(fd,&readset);
   
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)){
        ...
}

使用select后用户的服务器程序就变成:

初始化(socket,bind,listen);

while(1){
        设置监听读写文件描述符(FD_*);
        调用select;
        
        如果倾听嵌套字就绪,就说明一个新的连接请求建立{
                建立连接(accept);
                加入到监听文件描述符中去;
        }否则说明是一个已经连接过的描述符{
                进行操作(read或者write);
        }
}

多路复用可以解决资源限制的问题。该模型实际上是将UDP循环模型用在了TCP上面。这也就带来了一些问题,比如,由于服务器依次处理客户端的请求,所以可能会导致有的客户端会等待很久。

并发服务器:UDP服务器

人们把并发的概念用于UDP就得到了并发UDP服务器模型。并发UDP服务器模型其实很简单。和并发的TCP服务器模型类似,都是创建一个子进程来处理。

除非服务器处理客户端请求所用的时间比较长,人们实际上很少使用这种模型。

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_38410730/article/details/82287913
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 23:25:04
  • 阅读 ( 1000 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢