百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Linux网络编程——详解SOCKET

bigegpt 2024-08-05 11:46 2 浏览

一、预备知识

大端模式、小端模式

  • 大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;
  • 小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处。

网络字节序

  • 我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理
  • 网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,因此网络数据流应采用大端字节序,即低地址高字节

可以调用以下库函数做网络字节序和主机字节序的转换

#include <arpa/inet.h>

//这些函数调用成功后返回处理后的值,调用失败则返回-1
uint32_t htonl(uint32_t hostlong);	//主机字节顺序转换为网络字节顺序 对无符号长型进行操作4bytes
uint16_t htons(uint16_t hostshort); //主机字节顺序转换为网络字节顺序 对无符号短型进行操作2bytes  

uint32_t ntohl(uint32_t netlong);   //网络字节顺序转换为主机字节顺序 对无符号长型进行操作4bytes
uint16_t ntohs(uint16_t netshort);  //网络字节顺序转换为主机字节顺序 对无符号短型进行操作2bytes

IP地址转换函数

  • Linux提供了用于将点分十进制表示的IP地址与二进制表示的IP地址相互转换的函数族

早期

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//数字加点类型 转换成 将32位的IP    IP地址存放在参数straddr中,返回结果存放在addrptr中 
int inet_aton(const char *straddr, struct in_addr *addrptr);

//将32位的IP 转换成 数字加点类型
char *inet_ntoa(struct in_addr straddr);

//数字加点类型 转换成 将32位的IP
in_addr_t inet_addr(const char *cp);

/*只能处理IPv4的ip地址
不可重入函数
注意参数是struct in_addr*/

现在

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

/*支持IPv4和IPv6
可重入函数*/

Linuxc/c++服务器开发高阶视频,电子书学习资料后台私信【架构】获取

sockaddr数据结构

  • Linux中定义了一种通用的套接字结构类型strcut sockaddr,以供不同的协议调用
  • strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结
    构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个地址类型是sockaddr_in还是sockaddr_in6(文章没有列举出),由地址族确定,然后函数内部再强制类型转化为所需的地址类型
#include <sys/socket.h>

struct sockaddr
 {
	unsigned short sa_family; /* address族, AF_xxx */
	char sa_data[14]; 	      /* 14 bytes的协议地址 */
};

参数sa_family可选择如下

  • AF_INET IPv4协议
  • AF_INET6 IPv6协议
  • AF_LOCAL UNIX协议
  • AF_LINK 链路地址协议
  • AF_KEY 密钥套接字

除了sockaddr以外,Linux中还定义了另外一种结构类型sockaddr_in,它和sockaddr等效且可以互相转换(需要显式转换),通常在涉及TCP/IP的编程协议中使用

#include <netinet/in.h>

struct sockaddr_in
 {
	short int sin_family; 			/* Internet地址族 */
	unsigned short int sin_port;    /* 端口号 */
	struct in_addr sin_addr; 		/* Internet地址 */
	unsigned char sin_zero[8]; 		/* 添0(和struct sockaddr一样大小)*/
};

//其中in_addr由于历史设计原因导致结构体多余
struct in_addr
{
__be32 s_addr;//32位IPv4地址,网络字节序
};

网络设计模式

c/s 客户端/服务器

  • 需要开发客户端服务器,采用自定义协议
  • 必须先下载客户端,数据提前缓冲好
  • 需要考虑安全问题
  • 开发工作量大

b/s web/服务器

  • 不需要安装软件,点击浏览器就可以看到
  • 工作量小,客户端基本浏览器方式
  • 缺点:必须遵循http协议,动态加载数据

二、SOCKET

概述

  • linux中的网络编程通过socket接口实现。socket既是一种特殊的IO,它也是一种文件描述符

socket可以简单理解成为一个插座和插排,那么如何匹配?


就是通过IP+端口号进行匹配,匹配之后可以通过socket进行数据的发送和接收(socket本质是文件描述符fd)


具体的流程如下

socket创建

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

int socket(int domain, int type, int protocol);
  • domain:
    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    AF_INET6 与上面类似,不过是采用IPv6的地址
    AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
  • type:
    (1)SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
    (2)SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
    (3)SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的
    接受才能进行读取。
    (4)SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使
    用该协议)
    (5)SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数
    据包的顺序
  • protocol:
    0 默认协议
  • 返回值
    成功返回一个新的文件描述符(也叫监听套接字),失败返回-1

bind绑定

  • 在创建了套接字之后需要IP和端口号和套接字绑定在一起( IP地址:在网络环境中,唯一标识一台主机,端口号:在主机中唯一标识一个进程)
  • 前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd
    socket文件描述符
  • addr:
    构造出IP地址加端口号
  • addrlen:
    sizeof(addr)长度
  • 返回值
    成功返回0,失败返回-1, 设置errno

例如

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));//清0结构体
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8000);

listen

  • 创建了套接字之后通常需要等待客户端的连接,此时可以使用listen函数将该套接字转换为倾听套接字。
  • 可以指定同时连接的最大客户端数量
  • 若达到数量上限,新客户端等待其它已链接的客户端链接结束
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • sockfd:
    socket文件描述符
  • backlog:
    排队建立3次握手队列和刚刚建立3次握手队列的连接数和
  • 返回值
    成功返回0,失败返回-1

accept

  • 当服务器倾听到一个连接之后,可以使用函数accept从倾听套接字的完成连接队列中接收一个连接,如果这个完成连接队列为空,则会使得这个进程进入睡眠状态
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockdf:
    socket文件描述符
  • addr:
    传出参数,返回链接客户端地址信息,含IP地址和端口号
  • addrlen:
    传入传出参数,传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
  • 返回值
    成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

connect客户端连接函数

  • 客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址
#include <sys/types.h> 
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockdf:
    socket文件描述符
  • addr:
    传入参数,指定服务器端地址信息,含IP地址和端口号
  • addrlen:
    传入参数,传入sizeof(addr)大小
  • 返回值
    成功返回0,失败返回-1,设置errno

读写函数

<unistd.h>

int read(int fd, char *buf, int len);
int write(int fd, char *buf, int len);
  • fd
    套接字描述符;
  • buf
    指定数据缓冲区;
  • len
    指定接收或发送的数据量大小(以字节为单位)。
  • 返回值
    返回读/写成功的数据量大小,失败则返回-1。

关闭函数

<unistd.h>

int close(int fd);
  • fd
    套接字描述符;

写一个服务器例子和客户端例子

服务器

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define Port 6666 //端口号
#define MAXCLIENT 10 //最大客户端数量

int main(int argc, char argv[])
{
	int socket_fd, client_fd;
	int ret;
	int addr_size;
	struct sockaddr_in server_addr;   
	struct sockaddr_in client_addr; 
	
	int read_size;
	char buffer[1024]; 
	
	//创建socket
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if( socket_fd == -1)
	{
		printf("socket error\n");
		exit(1);
	}
	
	//绑定bind
	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据
	
	server_addr.sin_family = AF_INET;//IPv4
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将主机IP转换为网络IP
	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port	
	
	ret = bind(socket_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
	if(ret == -1)
	{
		printf("bind error\n");
		exit(1);
	}
	
	//监听
	ret = listen(socket_fd, MAXCLIENT);
	if(ret == -1)
	{
		printf("listen error\n");
		exit(1);
	}
	
	while(1)
	{
		//accept
		addr_size = sizeof(struct sockaddr_in);
		client_fd = accept(socket_fd, (struct sockaddr *)(&client_addr), &addr_size);
		if(client_fd == -1)
		{
			printf("accept error\n");
			exit(1);
		}
		//打印客户端IP   将网络地址转换成 .字符串 
		printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
			
		if((read_size = read(client_fd, buffer, 1024)) == -1)    
		{     
			printf("Read Error\n");     
			exit(1);    
		} 
  	     
		buffer[read_size]='\0';   
		printf("Server received %s\n",buffer); 
			
		close(client_fd);    /* 循环下一个 */   		
	}
	
	close(socket_fd);   	
	return 0;
}

客户端

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define Port 6666

int main(int argc, char argv[])
{
	int socket_fd;
	int ret;
	char buff[1024];
	struct sockaddr_in server_addr;
	
	char* str_IP = "172.21.252.7";
	
	//创建客户端socket
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if( socket_fd == -1)
	{
		printf("socket error\n");
		exit(1);
	}
	
	//连接connect
	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据
	
	server_addr.sin_family = AF_INET;//IPv4
	server_addr.sin_addr.s_addr = inet_addr(str_IP);//将主机IP转换为网络IP
	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port

	ret = connect(socket_fd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr_in));
	if(ret == -1)
	{
		printf("connect error\n");
		exit(1);
	}
	
	while(1)
	{
		//连接成功了,发送数据
		printf("Please input char:\n");     
		fgets(buff, 1024, stdin);   
		write(socket_fd, buff, strlen(buff)); 
	}
	
	close(socket_fd);
	return 0;
}

运行结果如下

注意

可通过nc指令测试服务器是否有误

相关推荐

程序员请收好:10个非常有用的 Visual Studio Code 插件

一个插件列表,可以让你的程序员生活变得轻松许多。作者|Daan译者|Elle出品|CSDN(ID:CSDNnews)以下为译文:无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发人...

PADS在WIN10系统中菜单显示不全的解决方法

决定由AD转PADS,打开发现菜单显示不正常,如下图所示:这个是由于系统的默认字体不合适导致,修改一下系统默认字体即可,修改方法如下:打开开始菜单-->所有程序-->Windows系统--...

一文讲解Web前端开发基础环境配置

先从基本的HTML语言开始学习。一个网页的所有内容都是基于HTML,为了学好HTML,不使用任何集成工具,而用一个文本编辑器,直接从最简单的HTML开始编写HTML。先在网上下载notepad++文...

TCP/IP协议栈在Linux内核中的运行时序分析

本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看。1Linux概述  1.1Linux操作系统架构简介Linux操作系统总体上由Linux...

从 Angular Route 中提前获取数据

#头条创作挑战赛#介绍提前获取意味着在数据呈现在屏幕之前获取到数据。本文中,你将学到,在路由更改前怎么获取到数据。通过本文,你将学会使用resolver,在AngularApp中应用re...

边做游戏边划水: 基于浅水方程的水面交互、河道交互模拟方法

以下文章来源于腾讯游戏学堂,作者Byreave篇一:基于浅水方程的水面交互本文主要介绍一种基于浅水方程的水体交互算法,在基本保持水体交互效果的前提下,实现了一种极简的水面模拟和物体交互方法。真实感的...

Nacos介绍及使用

一、Nacos介绍Nacos是SpringCloudAlibaba架构中最重要的组件。Nacos是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供注册中心、配置中心和动态DNS...

Spring 中@Autowired,@Resource,@Inject 注解实现原理

使用案例前置条件:现在有一个Vehicle接口,它有两个实现类Bus和Car,现在还有一个类VehicleService需要注入一个Vehicle类型的Bean:publicinte...

一文带你搞懂Vue3 底层源码

作者:妹红大大转发链接:https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA前言vue3出来有一段时间了。今天正式开始记录一下梗vue3.0.0-be...

一线开发大牛带你深度解析探讨模板解释器,解释器的生成

解释器生成解释器的机器代码片段都是在TemplateInterpreterGenerator::generate_all()中生成的,下面将分小节详细展示该函数的具体细节,以及解释器某个组件的机器代码...

Nacos源码—9.Nacos升级gRPC分析五

大纲10.gRPC客户端初始化分析11.gRPC客户端的心跳机制(健康检查)12.gRPC服务端如何处理客户端的建立连接请求13.gRPC服务端如何映射各种请求与对应的Handler处理类14.gRP...

聊聊Spring AI的Tool Calling

序本文主要研究一下SpringAI的ToolCallingToolCallbackorg/springframework/ai/tool/ToolCallback.javapublicinter...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockers...

在MySQL登录时出现Access denied for user ~~ (using password: YES)

Windows~~~在MySQL登录时出现Accessdeniedforuser‘root‘@‘localhost‘(usingpassword:YES),并修改MySQL密码目录适用...

mysql 8.0多实例批量部署script

背景最近一个项目上,客户需要把阿里云的rdsformysql数据库同步至线下,用作数据的灾备,需要在线下的服务器上部署mysql8.0多实例,为了加快部署的速度,写了一个脚本。解决方案#!/bi...