linux高性能服务器编程阅读笔记/socket地址API

@linux高性能服务器编程阅读笔记/socket地址API

一、字节序问题

1字节=8位二进制,也就是两位的16进制,例如
     11111111B=0xFFH
  计算机中一个内存地址可以存放8位1个字节的数据,以一个16位的2字节整形数据,在存取这2个字节时,由于大端字节序和小端字节序的不同,我们就需要考虑4个字节取出的顺序。其中大端把高位字节存放内存低地址处,小端把高位字节放在内存高地址处。,例如采取小端存储时,对于0x0F1F的一个数据,低位字节1F存放在内存低地址处,高位字节0F存放在内存高地址处。
  现代PC一般使用小端字节序,而在网络传输中一般使用大端字节序。因此接收方在接受数据时,需要采用一些函数将数据转换成自己需要的类型。
 字节序转换函数

二、基础知识

socket地址结构体中存放的是socket通信用的地址,公用socket地址结构体sockaddr和sockaddr_storage不够方便,在获取ip地址和端口时都需要进行位操作。因此专用socket地址结构体便产生了。下面是ipv4示例socksddr_in,在使用时还是要强制类型转换为sockaddr。
  在这里插入图片描述
tcp连接状态与socket编程对应状态可参考下面这张图,三次握手,四次挥手
在这里插入图片描述
1.listen监听服务端socket收到的客户端连接请求
下面测试监听socket,我准备了两台ubuntu虚拟机,fei作为服务器安装了squid代理服务器,wang作为客户端,使用telnet命令连接服务器。

下面是在服务器端编写的一个简单的socket监听程序testsocket.cpp,用于监听客户端的连接请求。

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
static bool stop=false;
static void handle_term(int sig)
{
        stop=true;
}
int main()
{
		//信号句柄
        signal(SIGTERM,handle_term);
        //定义监听地址
        const char* ip="192.168.8.109";
        int port=12345;
        int backlog=5;
        //创建socket
        int sock=socket(AF_INET,SOCK_STREAM,0);
        //创建socket地址结构体
        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);
        //绑定socket和socket地址结构体
        int ret=bind(sock,(struct sockaddr*)&address,sizeof(address));
        //监听socket地址结构体指定的IP地址个端口,一般就是服务器自己的ip地址。
        ret=listen(sock,backlog);
        while(!stop)
        {
                sleep(1);
        }
        close(sock);
        return 0;
}

在服务器fei编译生成test程序,执行命令:./test,运行监听程序
在客户端wang执行命令:telnet 192.168.3.109 12345,向服务器建立tcp连接,执行多次。
在客户端执行命令:netstat -nt |grep 12345
可以观察到已经建立的tcp连接(estalished),由于listen监听队列已满(队列大小可以再listen函数中设置),因此有一个tcp连接没有成功建立。
在这里插入图片描述
2.接受服务端收到的客户端连接请求accept
下面测试服务端接受socket连接,并返回请求连接的客户端地址,在服务端编写testsocket1.cpp,

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
        const char* ip="192.168.8.109";
        int port=12345;
        int backlog=5;
        int sock=socket(AF_INET,SOCK_STREAM,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,backlog);
        struct sockaddr_in client;
        socklen_t client_len=sizeof(client);
        int ret1=accept(sock,(struct sockaddr*)&client,&client_len);
        if(ret1>=0)
        {
                char remote[100];
                printf("ip:%s\n",inet_ntop(AF_INET,&client.sin_addr,remote,100));
                close(ret1);
        }
        close(sock);
        return 0;

在服务端执行此程序,在客户端执行telnet命令,服务端程序输出请求连接的客户端的IP地址。结果如下图。
在这里插入图片描述
查看tcp传输的状态,由于打印ip之后关闭了监听的socket,断开连接,四次挥手,客户端状态变成了TIME_WAIT。
在这里插入图片描述
3.服务端可以通过listen被动监听客户端的连接,客户端则可以通过connect主动请求与服务端连接

下面是客户端请求连接的测试程序client.cpp,连接成功则打印connect success。

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>

int main()
{
        const char* ip="192.168.8.109";
        int port=12345;
        int sockserver=socket(AF_INET,SOCK_STREAM,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=connect(sockserver,(struct sockaddr*)&address,sizeof(address));
		if(ret==0)
		{
			cout<<"connect success"<<endl;
		}
        close(sockserver);
        return 0;
}

前面在客户端执行telnet命令,系统会自动帮助我们向服务端发送连接请求,但是在这个程序中,我们直接自己申请连接服务端。
在客户端编写此程序并编译,在服务端首先运行我们前面写好的监听程序,再到客户端中运行连接程序,结果如下:
在这里插入图片描述可以看到客户端通过这个程序也与服务端建立了一个tcp连接,由于最后我们关闭了客户端的socket,因此tcp连接结束,变为time_wait状态,要等待一段时间(大概是2MSL,MSL:报文最大生存时间)才会变为closed状态。
需要说明的是,为什么不是立即变成closed状态呢?有两个原因:
第一个是为了接收到服务端发送来的结束连接报文,并给以确认。
第二个是为了避免其他应用程序立即建立连接生成一个连接的化身(同ip与端口),会接收到原链接的数据报。原则上一个tcp端口是不能被打开多次的。

4.测试客户端send函数和服务端recv函数
首先是客户端程序send.cpp

#include<sys/socket.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
int main()
{
        const char* ip="192.168.8.109";
        int port=12345;
        int sockclient=socket(AF_INET,SOCK_STREAM,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=connect(sockclient,(struct sockaddr*)&address,sizeof(address));
		if(ret==0)
		{
			const char* data1="abc";
			const char*data2="12";
			int ret1=send(sockclient,data1,strlen(data1),0);
			if(ret1!=-1)
			{
				cout<<"send "<<ret1<<" byte successful"<<endl;
			}
			int ret2=send(sockclient,data2,strlen(data2),0);
			if(ret2!=-1)
			{
				cout<<"send "<<ret2<<" byte successful"<<endl;
			}
		}
        close(sockclient);
        return 0;
}

然后是服务端程序recv.cpp

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
        const char* ip="192.168.8.109";
        int port=12345;
        int backlog=5;
        int sock=socket(AF_INET,SOCK_STREAM,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,backlog);
        struct sockaddr_in client_address;
        socklen_t client_len=sizeof(client_address);
        int client_socket=accept(sock,(struct sockaddr*)&client_address,&client_len);
        if(client_socket>=0)
        {
			sleep(10);//测试使用
	        char buffer[1024]={0};
			recv(client_socket,buffer,1024,0);
			cout<<"recv:"<<buffer<<endl;
			recv(client_socket,buffer,1024,0);
			cout<<"recv:"<<buffer<<endl;
        }
        close(sock);
        return 0;
}

客户端输出结果:
在这里插入图片描述
服务端输出结果:
在这里插入图片描述
客户端发送了两次数据,服务端接受了两次数据,分别做了一下几次试验:
1)两次send不加延时,recv两次接受也不加延时,此时接受的数据分别是abc,12c。
2)两次send不加延时,recv两次接受之前加10s延时,此时接受的数据分别是abc12,abc12。如上面的程序。
3)两次send之间加10s延时,recv两次接受之间不加延时,此时接受的数据分别是abc,12c。

可以看出:
0.recv属于一被调用就去读缓冲区中的数据。send一被调用就会将数据发送缓冲区。
1.send函数和recv函数只是并不直接参与网络数据的传输,send只是将数据送到内核发送缓冲区,recv只是将数据从内核接收缓冲区读取出来。真正在网络
2.对于实验1),第一次send发送abc,recv立马接收到abc,第二次send12,由于abc已经被recv读取,所以12会从头写入缓冲区覆盖掉ab,变成12c。
3.对于实验2),第一次send发送abc,recv由于延时10s,数据没有被接收,第二次send12,recv等到延时结束,此时两次都从从缓冲区中读取abc12。
4.对于实验3),第一次send发送abc,recv立马接收到abc,第二次send覆盖缓冲区,读取成为12c。
5.send和recv都是阻塞的,函数调用但不会立刻返回,需要等待操作完成。

5.sendfile函数的使用,从两个描述符之间高速拷贝数据,读取的是真实的文件描述符,写入的是socket的描述符。利用sendfile函数将服务端一个文件发送到客户端。

下面的是sendfile测试程序。

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
int main()
{
        const char* ip="192.168.8.109";
        int port=12345;
        int backlog=5;
	
	int filefd=open("recv.cpp",O_RDONLY);
	struct stat stat_buf;
	fstat(filefd,&stat_buf);

        int sock=socket(AF_INET,SOCK_STREAM,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,backlog);

        struct sockaddr_in client_address;
        socklen_t client_len=sizeof(client_address);

        int client_socket=accept(sock,(struct sockaddr*)&client_address,&client_len);
        if(client_socket>=0)
        {
		cout<<"start"<<endl;
                sendfile(client_socket,filefd,NULL,stat_buf.st_size);
		cout<<"end"<<endl;
		close(client_socket);
        }
        close(sock);
        return 0;
}

服务端编译运行,客户端telnet 192.168.8.109 12345,会展现发送过来的文件。
在这里插入图片描述
6.dup函数会复制产生一个新的描述符,但是描述符的值是当前最小的空闲值,但是指向的还是原来的那个文件,以此实现CGI服务器。
具体做法就是先关掉标准输出文件符STDOUT_FILENO(值为1),再dup客户端socket描述符,新生成的描述符值将为1,当调用输出语法printf和cout时,输出将定位到复制的客户端socket描述符中。

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
int main()
{
        const char* ip="192.168.0.109";
        int port=12345;
        int backlog=5;
	
	int filefd=open("recv.cpp",O_RDONLY);
	struct stat stat_buf;
	fstat(filefd,&stat_buf);

        int sock=socket(AF_INET,SOCK_STREAM,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,backlog);

        struct sockaddr_in client_address;
        socklen_t client_len=sizeof(client_address);

        int client_socket=accept(sock,(struct sockaddr*)&client_address,&client_len);
        if(client_socket>=0)
        {
		cout<<"start"<<endl;
                close(STDOUT_FILENO);
		dup(client_socket);
		printf("abc\n");
		cout<<"end"<<endl;
		close(client_socket);
        }
        close(sock);
        return 0;
}

客户端telnet如下图,printf和cout的输出结果都进入了客户端socket描述符中。
在这里插入图片描述

7.splice函数也是零拷贝在描述符之间移动数据,且有一个必须是管道描述符。
由splice函数实现一个回射服务器,将客户端发送的数据返回去。

服务端splice.cpp的代码:

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
int main()
{
        const char* ip="192.168.0.109";
        int port=12345;
        int backlog=5;
	
        int sock=socket(AF_INET,SOCK_STREAM,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,backlog);

        struct sockaddr_in client_address;
        socklen_t client_len=sizeof(client_address);

        int client_socket=accept(sock,(struct sockaddr*)&client_address,&client_len);
        if(client_socket>=0)
        {
		cout<<"start"<<endl;

                int pipefd[2];
		ret=pipe(pipefd);
		ret=splice(client_socket,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);
		ret=splice(pipefd[0],NULL,client_socket,NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);	
		
		close(client_socket);
        }
        close(sock);
        return 0;
}

客户端splice_.cpp的代码:

#include<iostream>
using namespace std;
#include<sys/socket.h>
#include<signal.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>

int main()
{
        const char* ip="192.168.0.109";
        int port=12345;
        int sockclient=socket(AF_INET,SOCK_STREAM,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=connect(sockclient,(struct sockaddr*)&address,sizeof(address));
	if(ret==0)
	{
		const char* data1="abc";
		send(sockclient,data1,strlen(data1),0);
		char buffer[1024];
		recv(sockclient,buffer,1024,0);
		cout<<buffer<<endl;
	}
        close(sockclient);
        return 0;
}

运行结果如下图,发送给服务端的数据通过pipe管道和splice函数重新回到了客户端socket的描述符中。
在这里插入图片描述
8.tee函数实现从键盘输入到屏幕和文件,tee函数接受的只能是两个管道描述符。
STDIN_FILENO—键盘输入
STDOUT_FILENO—屏幕输出
下面是tee.cpp的代码

#include<iostream>
using namespace std;
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
     
	int filefd=open("tee.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
	
        int pipefd_stdout[2];
	pipe(pipefd_stdout);

	int pipefd_file[2];
	pipe(pipefd_file);
	cout<<"123"<<endl;
	int ret=splice(STDIN_FILENO,NULL,pipefd_stdout[1],NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);
	tee(pipefd_stdout[0],pipefd_file[1],32768,SPLICE_F_NONBLOCK);
	splice(pipefd_file[0],NULL,filefd,NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);
	splice(pipefd_stdout[0],NULL,STDOUT_FILENO,NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);	
		
	close(filefd);
	close(pipefd_stdout[0]);
	close(pipefd_stdout[1]);
	close(pipefd_file[0]);
	close(pipefd_file[1]);	
        return 0;
}

执行结果如下图。键盘输入1,屏幕也打印1,并且将1写入到了tee.txt中。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值