Linux高性能服务器编程——数据读写 day2

TCP数据的读写

用于TCP流数据读写的系统调用是:
在这里插入图片描述
recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,flags参数通常为0.recv成功时返回实际读取到的数据的长度,它可能小于我们期望的长度,因此多次调用。
flags参数提供了额外的控制
在这里插入图片描述
举例说明,MSG_OOB选项给应用程序提供了发送和接收带外数据的方法

//发送带外数据
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

int main(int argc,char* argv[])
{
    if(argc<=2)
        return 1;

    const char* ip=argv[1];
    int port=atoi(argv[2]);

    struct sockaddr_in server_address;
    bzero(&server_address,sizeof(server_address));
    server_address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    server_address.sin_port=htons(port);

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);
    if(connect(sockfd,(struct sockaddr*)&server_address,
               sizeof(server_address))<0)
    {
        printf("connection failed\n");
    }
    else
    {
        const char* oob_data="abc";
        const char* normal_data="123";
        send(sockfd,normal_data,strlen(normal_data),0);
        send(sockfd,oob_data,strlen(oob_data),MSG_OOB);
        send(sockfd,normal_data,strlen(normal_data),0);
    }
    close(sockfd);
    return 0;
}
//接收带外数据
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define BUF_SIZE 1024

int main(int argc,char* argv[])
{

    if(argc<=2)
        return 1;

    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);

    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port=htons(port);

    int ret=bind(sock,(struct sockaddr*)&address,sizeof(address));

    ret=listen(sock,5);
    assert(ret!=-1);

    struct sockaddr_in client;
    socklen_t client_addrlength=sizeof(client);
    int connfd=accept(sock,(struct sockaddr*)&client,&client_addrlength);
    if(connfd<0)
        printf("errno is: %d\n",errno);
    else
    {
        char buffer[BUF_SIZE];

        memset(buffer,'\0',BUF_SIZE);
        ret=recv(connfd,buffer,BUF_SIZE-1,0);
        printf("got %d bytes of normal data '%s'\n",ret,buffer);

        memset(buffer,'\0',BUF_SIZE);
        ret=recv(connfd,buffer,BUF_SIZE-1,MSG_OOB);
        printf("got %d bytes of normal data '%s'\n",ret,buffer);

        memset(buffer,'\0',BUF_SIZE);
        ret=recv(connfd,buffer,BUF_SIZE-1,0);
        printf("got %d bytes of normal data '%s'\n",ret,buffer);

        close(connfd);
    }
    close(sock);
    return 0;
}

在这里插入图片描述

UDP数据的读写

在这里插入图片描述
UDP通信没有连接的概念,每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定改地址的长度。
recvfrom、sendto系统调用也可以用于面向连接的socket数据读写,只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket地址。

通用数据读写函数

在这里插入图片描述
sockfd参数指定被操作的目标socket,msg参数是msghdr结构体类型的指针
在这里插入图片描述
msg_name成员指向一个socket地址结构变量。它指定通信对方socket地址。对于面向连接的TCP协议,该成员没有意义,必须设置为NULL,因为对方地址已知。msg_namelen指定了msg_name所指socket地址的长度
msg_iov成员是iovec结构体类型的指针
在这里插入图片描述

带外标记

在这里插入图片描述
sockatmark判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark返回1,此时我们就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,返回0

地址信息函数

在这里插入图片描述
getsockname获取sockfd对应的本端socket地址,存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于address所指内存区的大小,那么该socket地址将被截断。成功返回0
getpeername获取sockfd对应的远端socket地址

socket选项

专门用来读取和设置socket文件描述符属性的方法
在这里插入图片描述
level参数指定要操作哪个协议的选项,如IPv4、IPv6、TCP等。option_name参数则指定选项的名字。option_value和option_len分别是被操作选项的值和长度
在这里插入图片描述
对服务器而言,有部分socket选项只能在调用listen系统调用前针对监听socket设置才有效。因为连接socket只能由accept调用返回,而accept从listen监听队列中接受的连接至少已经完成了TCP三次握手的前两个步骤(因为listen监听队列中的连接至少已进入SYN_RCVD状态)说明服务器已经往被接受连接上发送出了TCP同步报文段

SO_REUSEADDR选项

服务器程序可以通过设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址,具体实现方法

//重用本地地址
    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);
    int reuse=1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

即使sock处于TIME_WAIT状态,与之绑定的socket地址也可以立即被重用。此外也可以修改内核参数快速收回被关闭的socket,从而获得TCP连接根本就不进入TIME_WAIT状态,进而允许应用程序立即重用本地的socket地址

SO_RCVBUF和SO_SNDBUF选项

分别表示TCP接收缓冲区和发送缓冲区大小,当我们用setsocketopt设置TCP缓冲区大小时,系统都会将其加倍,并且不小于某个值,TCP接收缓冲区最小值是256字节,发送缓冲区最小值是20448字节。主要是确保一个TCP连接拥有足够的空闲缓冲区来处理拥塞

//设置缓冲区大小并读取
    int sendbuf=atoi(argv[3]);
    int len=sizeof(sendbuf);
    setsockopt(sock,SOL_SOCKET,SO_SNDBUF,&sendbuf,sizeof(sendbuf));
    getsockopt(sock,SOL_SOCKET,SO_SNDBUF,&sendbuf,(socklen_t*)&len);

SO_RCVLOWAT和SO_SNDLOWAT选项

分别表示TCP接收缓冲区和发送缓冲区的低水位标记,它们一般被I/O复用系统调用,用来判断socket是否可读或可写。当TCP接收缓冲区中可读数据的总数大于其低水位标记时,I/O复用系统调用将通知应用程序可以从对应的socket上读取数据;当TCP发送缓冲区的空闲空间大于其低水位标记时,I/O复用系统调用将通知应用程序可以往对应的socket上写入数据。默认情况下均为1字节

SO_LENGER选项

该选项用于控制close系统调用在关闭TCP连接时的行为。默认情况下使用close关闭一个socket,close将立即返回,TCP模块负责把该socket对应的TCP发送缓冲区中残留的数据发送给对方
设置SO_LINGER选项时需要给系统调用传递一个linger类型的结构体

网络信息API

在这里插入图片描述

网络信息API

gethostbyname和gethostbyaddr

gethostbyname是通过主机名称获取主机的完整信息,gethostbyaddr根据IP地址获取主机的完整信息。gethostbyname通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器
在这里插入图片描述
name参数指定目标主机的主机名,addr指定目标主机的ip地址,len指定addr所指ip地址的长度,type指定addr所指IP地址的类型:AF_INET、AF_INET6
hostent定义

getservbyname和getservbyport

getservbyname根据名称获取某个服务器的完整信息,getservbyport函数根据端口号获取某个服务的完整信息
在这里插入图片描述
name指定目标服务的名字,port指定目标服务对应的端口号。proto指定服务类型,传递“tcp”表示获取流服务,传递“udp”表示获取数据报服务,传递NULL表示获取所有类型的服务
在这里插入图片描述

//访问daytime服务
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <assert.h>
#include <netdb.h>

int main(int argc,char* argv[])
{
    assert(argc==2);
    char *host=argv[1];
    /*获取目标主机信息*/
    struct hostent* hostinfo=gethostbyname(host);
    assert(hostinfo);
    /*获取daytime服务信息*/
    struct servent* servinfo=getservbyname("daytime","tcp");
    assert(servinfo);
    printf("daytime port is %d\n",ntohs(servinfo->s_port));

    struct sockaddr_in address;
    address.sin_family=AF_INET;
    address.sin_port=servinfo->s_port;
    address.sin_addr=*(struct in_addr*)*hostinfo->h_addr_list;

    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    int result=connect(sockfd,(struct sockaddr*)&address,sizeof(address));
    assert(result!=-1);

    char buffer[128];
    result=read(sockfd,buffer,sizeof(buffer));
    assert(result>0);
    buffer[result]='\0';
    printf("the day time is: %s",buffer);
    close(sockfd);
    return 0;
}

上面讨论的四个函数都是不可重入的,即非线程安全的。不过netdb.h头文件给出了他们的可重入版本,这些函数的函数名是在原函数名尾部加上_r

getaddrinfo

getaddrinfo函数既能通过主机名获得IP地址(内部使用gethostbyname),也能通过服务名获得端口号(内部使用getservbyname)
在这里插入图片描述
hostname可以接收主机名,也可以接收字符串表示的IP地址,同样service可以接收服务名和字符串表示的十进制端口号。hint是应用程序给getaddrinfo的一个提示,对输出进行更加精确的控制,hints可以被设置为NULL,表示允许getaddrinfo反馈任何可用的结果,result指向一个链表,用于存储反馈结果:
在这里插入图片描述
该结构体中,ai_protocol成员是指具体的网络协议,其含义和socket系统调用的第三个参数相同,通常被设置为0,ai_flags成员可以取表中的标志的按位或
在这里插入图片描述
当使用hints参数的时候,可以设置其ai_flags,ai_family,ai_socktype和ai_protocol四个字段。其他字段则必须被设置为NULL。

//获取主机上的“daytime”流服务信息
    struct addrinfo hints;
    struct addrinfo* res;
    
    bzero(&hints,sizeof(hints));
    hints.ai_socktype=SOCK_STREAM;
    getaddrinfo("sjh-virtual-machine","daytime",&hints,&res);

getaddrinfo将隐式的分配堆内存,因为res指针原本是没有指向一块合法内存的,所以调用结束后,释放内存
在这里插入图片描述

getnameinfo

该函数能通过socket地址同时获得以字符串表示的主机名(内部使用gethostbyaddr函数)和服务名(getservbyport)。定义如下:
在这里插入图片描述
getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中,hostlen和servlen参数分别指定这两块缓存的长度。flag参数选项如下:
在这里插入图片描述
在这里插入图片描述

Linux下的strerror函数能将数值错误码errno转换成易读的字符串形式。同样,下面的函数可将错误码转换成其字符串形式
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值