Linux网络IPC: Socket套接字的介绍和使用

不同主机间的相互通信:❤️

  • 进 程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施:

    • UNIX BSD有:管道(pipe)、命名管道(named pipe)软中断信号(signal)
    • UNIX system V有:消息(message)、共享存储区(shared memory)和信号量(semaphore)等.
  • 上述的进程间通信的方式都是依靠Liunx内核进行的,所以只能用在本机进程之间通信。网间进程通信就解决了不同主机进程间的相互通信的问题

  • IP地址:在Internet上为每台计算机指定的唯一的32位地址称为”IP地址“,也称”网际结构“。IP地址具有固定、规范的格式,它由32位二进制数组成,分成4段,其中每8位构成一段,这样,每段所能表示的二进制数的范围最大不超过255,段与段之间用”."隔开。为了便于标识和表达,IP地址以十进制形式表示,每8位二进制数用一个十进制数表示。

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    int inet_aton(const char* straddr,struct in_addr *addrp);//点分十进制转二进制,第一个参数为十进制IP,第二个参数指向二进制的存放
    char* inet_ntoa(struct in_ addr inaddr);//二进制转点分十进制
    
  • TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

  • TCP/IP协议存在于OS中,网络服务通过OS提供,在OS中增加支持TCP/IP的系统调用——Berkeley套接字,如Socket,Connect,Send,Recv等

  • UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种:

在这里插入图片描述

  • TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
    在这里插入图片描述

套接字socket:❤️

  • socket即是一种特殊的文件(S),可以用“打开open –> 读写write/read –> 关闭close”模式来操作
  • socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
  • 其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。

网络字节序与主机字节序:❤️

  • 字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序

  • 主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

    • Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
    • Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
      图片 上图来自网络
      在这里插入图片描述
  • 网络字节序:4个字节的32bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。

  • 字节序转换函数

    #include <arpa/inet.h>
    
    uint32_t htonl(uint32_t hostlong);
    
    uint16_t htons(uint16_t hostshort);
    
    uint32_t ntohl(uint32_t netlong);
    
    uint16_t ntohs(uint16_t netshort);
    //h表示host,主机,n表示network,网络,l表示32位长整数(long),s表示16位短整数(short)。
    

Socket编程:❤️

Socket编程的一般步骤

服务端:

  • 创建Socket套接字,用Socket ()函数

  • 设置Socket属性

  • 为套接字添加信息(IP地址和端口号)用bind()函数

  • 开启监听,用listen()函数

  • 监听到有客户端接入,接受一个连接,用accept()函数

  • 交换数据,用函数send()和recv(),或者用read(),write()

  • 关闭套接字,断开连接

客户端:

  • 创建一个Socket,用Socket函数
  • 设置Socket属性
  • 连接服务器,用connect()函数
  • 交换数据,用函数send()和recv(),或者用read(),write()
  • 关闭网络连接

List item

socket接口函数

头文件

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

Socket函数

  • 函数原型

    int socket(int domain, int type, int protocol);
    
  • 功能:用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数

  • 参数:

    • protofamily:协议族(family)。常用的协议族有:AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
    • protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
  • 返回值: 成功时返回非负整数(socket描述符),失败返回 -1。

  • socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述符,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述符一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

bind函数

  • 函数原型

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    
  • 功能:绑定IP地址和端口号到Sockfd

  • sockfd: 一个socket描述符,由socket函数调用返回

  • addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要邦定给sockfd的协议地址结构,这个地址结构根据地址创建,socket时的地址协议族的不同而不同。

    • ipv4用的结构体:
    struct sockaddr_in {
        sa_family_t    sin_family; /* address family: AF_INET */
        in_port_t      sin_port;   /* port in network byte order */
        struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    
  • addrlen: 第二个参数中结构体的大小

  • 返回值:成功返回0,出错返回-1

listen和connect函数

  • 函数原型

    int listen(int sockfd, int backlog);//把进程变为一个服务器,并指定相应的套接字变为被动连接。规定内核为相应套接字排队的最大连接数
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定之后client端(客户端),与服务器建立连接参数
    
  • 如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

  • listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

  • connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

  • 返回值:成功返回0,出错返回-1

accept函数

  • 函数原型

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd
    
  • TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

  • 参数:

    • sockfd:监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。

    • addr:这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。

    • len:addr结构的大小的,它指明addr结构所占有的字节个数。可以被设置为NULL。

  • 返回值:accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。失败返回-1。

  • 如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。


代码示例:❤️

  • 客户端client.c:

    #include <stdio.h>
    #include <sys/types.h> 
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/wait.h>
    
    int main(int argc,char**argv)
    {
    	int n_read;
    	char readBuf[128] = {0};
    	char data[64]={0};
    	int c_fd = socket(AF_INET,SOCK_STREAM,0); 	
    	if(c_fd == -1){
    		perror("socket error");
    		exit(-1);
    	}
    
    	struct sockaddr_in c_addr;
    	c_addr.sin_family = AF_INET;
    	c_addr.sin_port = htons(atoi(argv[2]));
    	inet_aton(argv[1],&c_addr.sin_addr);
    	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(c_addr)) == -1){
    		perror("connect error");
    		exit(-1);
    	}
    
    	if(fork() == 0){
    		while(1){
    			printf("put:");	
    			memset(data, 0, strlen(data));
    			gets(data);
    			write(c_fd,data,strlen(data));
    	
    
    			n_read = read(c_fd,readBuf,128);
    			if(n_read == -1){
    				perror("read error");
    				exit(-1);
    			}else{
    				printf("get server data %dbyte:%s\n",n_read,readBuf);
    			}
    		}
    	}
    
    	wait(NULL);	
    
    	return 0;
    }
    
  • 服务端server.c:

    #include <stdio.h>
    #include <sys/types.h> 
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/wait.h>
    
    int main(int argc,char**argv)
    {
    	int n_read;
    	char readBuf[128] = {0};
    	int c_fd;
    	int s_fd = socket(AF_INET,SOCK_STREAM,0); 	
    	if(s_fd == -1){
    		perror("socket error");
    		exit(-1);
    	}
    
    	struct sockaddr_in s_addr;
    	s_addr.sin_family = AF_INET;
    	s_addr.sin_port = htons(atoi(argv[2]));
    	inet_aton("argv[1]",&s_addr.sin_addr);
    	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(s_addr));
    
    	listen(s_fd,10);
    
    	struct sockaddr_in c_addr;
    	int c_size = sizeof(c_addr);
    	
    	
    	
    	while(1){
    		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_size);
    		if(c_fd == -1){
    			perror("accept error");
    			exit(-1);
    		}	
    		printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
    
    		if(fork() == 0){
    			while(1){				
    				memset(readBuf, 0, strlen(readBuf));
    				n_read = read(c_fd,readBuf,128);
    				if(n_read == -1){
    					perror("read error");
    					exit(-1);
    				}else{
    					printf("get data %dbyte:%s\n",n_read,readBuf);
    					write(c_fd,"I get it!",strlen("I get it!"));
    				}
    				
    			}			
    		}
    	}
    	wait(NULL);
    	return 0;
    }
    
  • 终端输入(第二个参数为IP地址,127.0.0.1是本地环回;第三个参数为端口号,没有被占用的端口可以随便写):

    ./client.out 127.0.0.1 6666
    ./server.out 127.0.0.1 6666
    
  • 客户端:
    在这里插入图片描述

  • 服务端:
    在这里插入图片描述


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

money的大雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值